Add broker and web throughput diagnostics

This commit is contained in:
2026-09-08 22:40:58 +02:00
parent 36d41be422
commit 042499e4d6
20 changed files with 899 additions and 5 deletions
+148
View File
@@ -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) {