Add broker and web throughput diagnostics
This commit is contained in:
@@ -86,6 +86,10 @@ typedef struct web_serial_slot {
|
||||
uint8_t rx_data[WEB_SERIAL_TRANSPORT_MAX_RX_PAYLOAD];
|
||||
uint8_t tx_data[WEB_SERIAL_TRANSPORT_TX_PAYLOAD_SIZE];
|
||||
web_serial_work_t work;
|
||||
web_serial_performance_session_t performance;
|
||||
uint32_t performance_epoch, completion_epoch;
|
||||
int64_t queued_us, completed_us;
|
||||
bool completion_waiting, completion_attempted;
|
||||
} web_serial_slot_t;
|
||||
|
||||
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
@@ -103,6 +107,88 @@ static uint32_t s_inflight_handlers;
|
||||
/* Cancels ticket publication across revocation and server detach/re-attach.
|
||||
* Never wraps: exhaustion disables minting for the remainder of the boot. */
|
||||
static uint64_t s_ticket_epoch;
|
||||
/* Atomic gate permits a true pre-lock callback-entry timestamp. Never reused. */
|
||||
static uint32_t s_performance_gate, s_performance_epoch;
|
||||
|
||||
static uint32_t performance_gate(void)
|
||||
{
|
||||
return __atomic_load_n(&s_performance_gate, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
static void performance_add(web_serial_slot_t *slot, uint64_t *value, uint64_t n)
|
||||
{
|
||||
if (UINT64_MAX - *value < n) {
|
||||
*value = UINT64_MAX;
|
||||
slot->performance.saturated = true;
|
||||
} else *value += n;
|
||||
}
|
||||
|
||||
static void performance_time(web_serial_slot_t *slot,
|
||||
web_serial_performance_timing_t *value,
|
||||
int64_t start, int64_t end)
|
||||
{
|
||||
uint64_t elapsed = end >= start ? (uint64_t)(end - start) : 0;
|
||||
performance_add(slot, &value->count, 1);
|
||||
performance_add(slot, &value->sum_us, elapsed);
|
||||
if (elapsed > value->max_us) value->max_us = elapsed;
|
||||
}
|
||||
|
||||
static esp_err_t performance_control(bool enabled, bool clear)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (clear) enabled = performance_gate() != 0;
|
||||
if (s_performance_epoch == UINT32_MAX) {
|
||||
__atomic_store_n(&s_performance_gate, 0, __ATOMIC_RELAXED);
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
++s_performance_epoch;
|
||||
__atomic_store_n(&s_performance_gate, enabled ? s_performance_epoch : 0,
|
||||
__ATOMIC_RELAXED);
|
||||
for (unsigned i = 0; i < WEB_SERIAL_TRANSPORT_MAX_SESSIONS; ++i) {
|
||||
s_slots[i].completion_waiting = false;
|
||||
if (clear) memset(&s_slots[i].performance, 0, sizeof(s_slots[i].performance));
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_serial_performance_enable(bool enabled)
|
||||
{
|
||||
return performance_control(enabled, false);
|
||||
}
|
||||
|
||||
esp_err_t web_serial_performance_clear(void)
|
||||
{
|
||||
return performance_control(false, true);
|
||||
}
|
||||
|
||||
esp_err_t web_serial_performance_snapshot(web_serial_performance_snapshot_t *out)
|
||||
{
|
||||
if (!out) return ESP_ERR_INVALID_ARG;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
uint32_t gate = performance_gate();
|
||||
int64_t now = gate ? esp_timer_get_time() : 0;
|
||||
*out = (web_serial_performance_snapshot_t){
|
||||
.enabled = gate != 0, .epoch = s_performance_epoch,
|
||||
.epoch_exhausted = s_performance_epoch == UINT32_MAX};
|
||||
for (unsigned i = 0; i < WEB_SERIAL_TRANSPORT_MAX_SESSIONS; ++i) {
|
||||
web_serial_slot_t *slot = &s_slots[i];
|
||||
web_serial_performance_session_t *row = &out->sessions[i];
|
||||
*row = slot->performance;
|
||||
row->active = slot->state == WEB_SERIAL_SLOT_ACTIVE;
|
||||
row->socket_fd = slot->socket_fd;
|
||||
row->generation = slot->generation;
|
||||
row->broker_client_id = slot->broker_client_id;
|
||||
row->pending = slot->work_pending;
|
||||
row->measured_pending = row->pending && gate && slot->performance_epoch == gate;
|
||||
row->executing = row->measured_pending && row->executing;
|
||||
row->pending_age_us = row->measured_pending && now >= slot->queued_us ?
|
||||
(uint64_t)(now - slot->queued_us) : 0;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t identity_is_current(const user_principal_t *principal,
|
||||
web_session_id_t id, bool *current)
|
||||
@@ -377,6 +463,9 @@ static web_serial_slot_t *reserve_slot(httpd_handle_t server, int socket_fd,
|
||||
if (slot->state != WEB_SERIAL_SLOT_FREE) {
|
||||
continue;
|
||||
}
|
||||
memset(&slot->performance, 0, sizeof(slot->performance));
|
||||
slot->performance_epoch = 0;
|
||||
slot->completion_waiting = false;
|
||||
slot->generation = next_generation(slot->generation);
|
||||
slot->state = WEB_SERIAL_SLOT_RESERVED;
|
||||
slot->server = server;
|
||||
@@ -902,6 +991,8 @@ static void finish_closing_slot_locked(web_serial_slot_t *slot)
|
||||
|
||||
static void web_serial_send_work(void *argument)
|
||||
{
|
||||
uint32_t entry_epoch = performance_gate();
|
||||
int64_t entry_us = entry_epoch ? esp_timer_get_time() : 0;
|
||||
web_serial_work_t *work = argument;
|
||||
web_serial_slot_t *slot = work != NULL ? work->slot : NULL;
|
||||
if (!slot_pointer_valid(slot)) {
|
||||
@@ -928,6 +1019,14 @@ static void web_serial_send_work(void *argument)
|
||||
generation = work->generation;
|
||||
type = slot->tx_type;
|
||||
length = slot->tx_length;
|
||||
uint32_t sample_epoch = owned_work && entry_epoch &&
|
||||
entry_epoch == performance_gate() && slot->performance_epoch == entry_epoch &&
|
||||
type == HTTPD_WS_TYPE_BINARY ? entry_epoch : 0;
|
||||
if (sample_epoch) {
|
||||
performance_time(slot, &slot->performance.queue_wait, slot->queued_us, entry_us);
|
||||
slot->performance.executing = valid;
|
||||
if (!valid) performance_add(slot, &slot->performance.retired, 1);
|
||||
}
|
||||
if (owned_work && !valid) {
|
||||
slot->work_pending = false;
|
||||
slot->tx_length = 0U;
|
||||
@@ -943,6 +1042,8 @@ static void web_serial_send_work(void *argument)
|
||||
}
|
||||
|
||||
esp_err_t result = ESP_FAIL;
|
||||
int64_t send_start = 0, send_end = 0;
|
||||
bool send_called = false;
|
||||
void *current_context = httpd_sess_get_ctx(server, socket_fd);
|
||||
if (current_context == slot &&
|
||||
httpd_ws_get_fd_info(server, socket_fd) ==
|
||||
@@ -954,7 +1055,10 @@ static void web_serial_send_work(void *argument)
|
||||
.payload = slot->tx_data,
|
||||
.len = length,
|
||||
};
|
||||
send_called = true;
|
||||
if (sample_epoch && performance_gate() == sample_epoch) send_start = esp_timer_get_time();
|
||||
result = httpd_ws_send_frame_async(server, socket_fd, &frame);
|
||||
if (sample_epoch && performance_gate() == sample_epoch) send_end = esp_timer_get_time();
|
||||
if (result == ESP_OK) {
|
||||
(void)httpd_sess_update_lru_counter(server, socket_fd);
|
||||
}
|
||||
@@ -963,6 +1067,19 @@ static void web_serial_send_work(void *argument)
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (slot->work_pending && slot->generation == generation &&
|
||||
work == &slot->work) {
|
||||
if (sample_epoch && performance_gate() == sample_epoch &&
|
||||
slot->performance_epoch == sample_epoch) {
|
||||
slot->performance.executing = false;
|
||||
if (send_called) performance_time(slot, &slot->performance.send_call, send_start, send_end);
|
||||
if (result == ESP_OK) {
|
||||
performance_add(slot, &slot->performance.sent_frames, 1);
|
||||
performance_add(slot, &slot->performance.sent_bytes, length);
|
||||
slot->completion_epoch = sample_epoch;
|
||||
slot->completed_us = send_end;
|
||||
slot->completion_waiting = true;
|
||||
slot->completion_attempted = false;
|
||||
} else performance_add(slot, &slot->performance.send_errors, 1);
|
||||
}
|
||||
slot->work_pending = false;
|
||||
slot->tx_length = 0U;
|
||||
if (result == ESP_OK) {
|
||||
@@ -986,6 +1103,8 @@ static void web_serial_send_work(void *argument)
|
||||
static esp_err_t queue_slot_frame(web_serial_slot_t *slot, uint32_t generation,
|
||||
httpd_ws_type_t type, size_t length)
|
||||
{
|
||||
uint32_t epoch = performance_gate();
|
||||
int64_t queued_us = epoch ? esp_timer_get_time() : 0;
|
||||
httpd_handle_t server;
|
||||
bool prepared = false;
|
||||
|
||||
@@ -994,6 +1113,13 @@ static esp_err_t queue_slot_frame(web_serial_slot_t *slot, uint32_t generation,
|
||||
slot->generation == generation && !slot->work_pending &&
|
||||
!slot->close_requested && slot->server == s_server &&
|
||||
length <= sizeof(slot->tx_data)) {
|
||||
slot->performance_epoch = type == HTTPD_WS_TYPE_BINARY && epoch == performance_gate() ? epoch : 0;
|
||||
if (slot->performance_epoch) {
|
||||
slot->queued_us = queued_us;
|
||||
slot->performance.executing = false;
|
||||
performance_add(slot, &slot->performance.queued_frames, 1);
|
||||
performance_add(slot, &slot->performance.queued_bytes, length);
|
||||
}
|
||||
slot->tx_type = type;
|
||||
slot->tx_length = length;
|
||||
slot->work.slot = slot;
|
||||
@@ -1025,6 +1151,8 @@ static esp_err_t queue_slot_frame(web_serial_slot_t *slot, uint32_t generation,
|
||||
slot->tx_length = 0U;
|
||||
slot->close_requested = true;
|
||||
++s_counters.queue_failures;
|
||||
if (epoch && performance_gate() == epoch && slot->performance_epoch == epoch)
|
||||
performance_add(slot, &slot->performance.queue_errors, 1);
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
notify_transport_task();
|
||||
@@ -1168,9 +1296,29 @@ static bool prepare_writer_sync(web_serial_slot_t *slot, uint32_t generation,
|
||||
static void drain_binary_output(web_serial_slot_t *slot, uint32_t generation,
|
||||
session_broker_client_id_t client_id)
|
||||
{
|
||||
uint32_t epoch = performance_gate();
|
||||
size_t received = 0U;
|
||||
esp_err_t result = session_broker_read(client_id, slot->tx_data,
|
||||
sizeof(slot->tx_data), &received);
|
||||
int64_t drained_us = epoch && performance_gate() == epoch ? esp_timer_get_time() : 0;
|
||||
if (epoch) {
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (performance_gate() == epoch && slot->completion_epoch == epoch &&
|
||||
slot->generation == generation && slot->broker_client_id == client_id &&
|
||||
slot->state == WEB_SERIAL_SLOT_ACTIVE && slot->completion_waiting) {
|
||||
bool nonempty = result == ESP_OK && received > 0;
|
||||
if (!slot->completion_attempted) {
|
||||
performance_time(slot, &slot->performance.completion_attempt, slot->completed_us, drained_us);
|
||||
if (nonempty) performance_time(slot, &slot->performance.completion_first_nonempty, slot->completed_us, drained_us);
|
||||
}
|
||||
slot->completion_attempted = true;
|
||||
if (nonempty) {
|
||||
performance_time(slot, &slot->performance.completion_nonempty, slot->completed_us, drained_us);
|
||||
slot->completion_waiting = false;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
if (result == ESP_OK && received > 0U) {
|
||||
(void)queue_slot_frame(slot, generation, HTTPD_WS_TYPE_BINARY, received);
|
||||
} else if (result == ESP_ERR_NOT_FOUND) {
|
||||
|
||||
Reference in New Issue
Block a user