Add broker and web throughput diagnostics
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile actual transport callbacks using the existing serial/store doubles."""
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
BASE = ROOT / 'tests/web_session_store'
|
||||
sys.path.insert(0, str(BASE))
|
||||
from run import HEADERS
|
||||
from serial_headers import SERIAL_HEADERS
|
||||
os.environ['CCACHE_DISABLE'] = '1'
|
||||
with tempfile.TemporaryDirectory(prefix='web-performance-') as directory:
|
||||
tmp = pathlib.Path(directory)
|
||||
headers = HEADERS | SERIAL_HEADERS
|
||||
headers['freertos/FreeRTOS.h'] = headers['freertos/FreeRTOS.h'].replace(
|
||||
'extern int host_lock_depth;', 'extern int host_lock_depth; void host_before_lock(void);').replace(
|
||||
'(void)(m); assert(host_lock_depth++', '(void)(m); host_before_lock(); assert(host_lock_depth++')
|
||||
for name, text in headers.items():
|
||||
path = tmp / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
fixture = (BASE / 'serial_test.c').read_text().replace('int main(void)', 'int serial_tests(void)')
|
||||
fixture = fixture.replace('#include "test.c"', '#include "' + str(BASE / 'test.c') + '"')
|
||||
fixture = fixture.replace('../../src/web_serial_transport.c', str(ROOT / 'src/web_serial_transport.c'))
|
||||
(tmp / 'fixture.c').write_text(fixture)
|
||||
console = (ROOT / 'src/web_console.c').read_text()
|
||||
start = console.index('static void print_performance_time(')
|
||||
end = console.index('static int command_web(', start)
|
||||
(tmp / 'console.inc').write_text(
|
||||
'static const char *esp_err_to_name(esp_err_t e) { (void)e; return "error"; }\n'
|
||||
+ console[start:end])
|
||||
flags = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if '--sanitize' in sys.argv else []
|
||||
subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-g', *flags,
|
||||
'-I'+str(tmp), '-I'+str(ROOT / 'src'), '-ffunction-sections', '-fdata-sections',
|
||||
'-Wl,--gc-sections', str(HERE / 'test.c'), str(ROOT / 'src/web_session_store.c'),
|
||||
str(ROOT / 'src/web_auth_parse.c'), '-o', str(tmp / 'test')], check=True, timeout=30)
|
||||
subprocess.run([str(tmp / 'test')], check=True, timeout=15)
|
||||
@@ -0,0 +1,172 @@
|
||||
/* Production queue/callback/drain paths; no payload is printed. */
|
||||
#include "fixture.c"
|
||||
#include "console.inc"
|
||||
static void (*queued)(void *), (*send_hook)(void), (*queue_hook)(void), (*read_hook)(void);
|
||||
static void *queued_arg;
|
||||
static bool early;
|
||||
static esp_err_t queue_result, send_result;
|
||||
static size_t read_bytes;
|
||||
static unsigned sends;
|
||||
static unsigned lock_delay;
|
||||
static bool disconnect_stalled;
|
||||
void host_before_lock(void) { now += lock_delay; lock_delay = 0; }
|
||||
void vTaskDelay(TickType_t ticks)
|
||||
{
|
||||
assert(!host_lock_depth); now += ticks * 1000;
|
||||
if (!disconnect_stalled) process_broker_disconnect(&s_slots[0]);
|
||||
}
|
||||
session_broker_client_id_t session_broker_get_writer_id(void) { return SESSION_BROKER_NO_CLIENT; }
|
||||
esp_err_t session_broker_force_writer(session_broker_client_id_t id) { (void)id; return ESP_OK; }
|
||||
static void detach(void) { assert(web_serial_transport_detach_server(request.handle) == ESP_OK); }
|
||||
static void reuse_during_read(void) { ++s_slots[0].generation; }
|
||||
|
||||
esp_err_t httpd_queue_work(httpd_handle_t h, void (*fn)(void *), void *arg)
|
||||
{
|
||||
(void)h; assert(!host_lock_depth);
|
||||
queued = fn; queued_arg = arg;
|
||||
if (queue_hook) queue_hook();
|
||||
if (early && queue_result == ESP_OK) { now += 7; fn(arg); }
|
||||
return queue_result;
|
||||
}
|
||||
esp_err_t httpd_ws_send_frame_async(httpd_handle_t h, int fd, httpd_ws_frame_t *f)
|
||||
{
|
||||
(void)h; (void)fd; assert(!host_lock_depth); assert(f->payload == s_slots[0].tx_data);
|
||||
++sends; now += 11;
|
||||
if (send_hook) send_hook();
|
||||
return send_result;
|
||||
}
|
||||
esp_err_t httpd_sess_update_lru_counter(httpd_handle_t h, int fd)
|
||||
{ (void)h; (void)fd; assert(!host_lock_depth); now += 3; return ESP_OK; }
|
||||
esp_err_t session_broker_read(session_broker_client_id_t id, uint8_t *data, size_t n, size_t *out)
|
||||
{
|
||||
(void)id; (void)data; assert(!host_lock_depth && read_bytes <= n);
|
||||
if (read_hook) read_hook();
|
||||
*out = read_bytes; return ESP_OK;
|
||||
}
|
||||
static web_serial_slot_t *setup(void)
|
||||
{
|
||||
serial_reset(); issued_t a = mint(&alice); web_serial_slot_t *s = connect_session(&a);
|
||||
early = false; disconnect_stalled = false; lock_delay = 0; queue_result = send_result = ESP_OK;
|
||||
send_hook = queue_hook = read_hook = NULL; read_bytes = 0; sends = 0;
|
||||
assert(web_serial_performance_enable(false) == ESP_OK);
|
||||
assert(web_serial_performance_clear() == ESP_OK);
|
||||
return s;
|
||||
}
|
||||
static void enable(void) { assert(web_serial_performance_enable(true) == ESP_OK); }
|
||||
static void clear(void) { assert(web_serial_performance_clear() == ESP_OK); }
|
||||
static void disable(void) { assert(web_serial_performance_enable(false) == ESP_OK); }
|
||||
static void cycle(void) { disable(); enable(); }
|
||||
static void run_work(void) { assert(queued); queued(queued_arg); }
|
||||
static void queue_binary(web_serial_slot_t *s)
|
||||
{ assert(queue_slot_frame(s, s->generation, HTTPD_WS_TYPE_BINARY, 512) == ESP_OK); }
|
||||
static web_serial_performance_session_t row(void)
|
||||
{
|
||||
web_serial_performance_snapshot_t s;
|
||||
assert(web_serial_performance_snapshot(&s) == ESP_OK);
|
||||
return s.sessions[0];
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
assert(serial_tests() == 0);
|
||||
web_serial_slot_t *s = setup();
|
||||
queue_binary(s); run_work(); assert(row().sent_frames == 0); /* default off */
|
||||
enable(); early = true; queue_binary(s);
|
||||
assert(row().queue_wait.sum_us == 7 && row().send_call.sum_us == 11);
|
||||
assert(row().sent_frames == 1 && row().sent_bytes == 512 && !row().pending);
|
||||
now += 20; read_bytes = 512; early = false;
|
||||
drain_binary_output(s, s->generation, s->broker_client_id);
|
||||
assert(row().completion_first_nonempty.count == 1 && row().completion_attempt.sum_us == 23);
|
||||
now += 100; assert(row().pending_age_us == 100 && row().measured_pending);
|
||||
run_work(); now += 10; read_bytes = 0;
|
||||
drain_binary_output(s, s->generation, s->broker_client_id);
|
||||
uint64_t first = row().completion_attempt.sum_us;
|
||||
now += 1000000; read_bytes = 512;
|
||||
drain_binary_output(s, s->generation, s->broker_client_id);
|
||||
assert(row().completion_attempt.sum_us == first);
|
||||
assert(row().completion_first_nonempty.count == 1 && row().completion_nonempty.max_us > 1000000);
|
||||
run_work();
|
||||
/* Control sends must not contribute or erase the binary completion chain. */
|
||||
assert(queue_slot_frame(s, s->generation, HTTPD_WS_TYPE_TEXT, 4) == ESP_OK); run_work();
|
||||
drain_binary_output(s, s->generation, s->broker_client_id);
|
||||
assert(row().completion_nonempty.count == 3);
|
||||
|
||||
s = setup(); enable(); queue_binary(s); now += 5; lock_delay = 100;
|
||||
run_work(); assert(row().queue_wait.sum_us == 5); /* before validation lock */
|
||||
s = setup(); enable(); queue_result = ESP_FAIL;
|
||||
assert(queue_slot_frame(s, s->generation, HTTPD_WS_TYPE_BINARY, 8) == ESP_FAIL);
|
||||
assert(row().queue_errors == 1 && !row().pending && s->close_requested);
|
||||
s = setup(); enable(); send_result = ESP_FAIL; queue_binary(s); run_work();
|
||||
assert(row().send_errors == 1 && row().send_call.count == 1 && row().sent_frames == 0);
|
||||
s = setup(); enable(); queue_binary(s); s->close_requested = true; run_work();
|
||||
assert(!sends && row().retired == 1 && !s->work_pending);
|
||||
|
||||
void (*hooks[])(void) = {clear, disable, cycle};
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
s = setup(); enable(); queue_binary(s); hooks[i](); run_work();
|
||||
assert(row().sent_frames == 0 && row().queue_wait.count == 0);
|
||||
s = setup(); enable(); send_hook = hooks[i]; queue_binary(s); run_work();
|
||||
assert(row().sent_frames == 0 && row().send_call.count == 0);
|
||||
s = setup(); enable(); queue_hook = hooks[i]; early = true; queue_binary(s);
|
||||
assert(row().sent_frames == 0 && row().queue_wait.count == 0);
|
||||
s = setup(); enable(); queue_hook = hooks[i]; queue_result = ESP_FAIL;
|
||||
assert(queue_slot_frame(s, s->generation, HTTPD_WS_TYPE_BINARY, 8) == ESP_FAIL);
|
||||
assert(row().queue_errors == 0 && s->close_requested);
|
||||
}
|
||||
s = setup(); enable(); queue_binary(s); run_work(); disable();
|
||||
uint64_t frozen = row().sent_frames; queue_binary(s); run_work();
|
||||
assert(row().active && row().sent_frames == frozen && !row().measured_pending);
|
||||
enable(); queue_binary(s); run_work(); assert(row().sent_frames == frozen + 1);
|
||||
read_hook = clear; read_bytes = 512;
|
||||
drain_binary_output(s, s->generation, s->broker_client_id);
|
||||
assert(row().completion_nonempty.count == 0);
|
||||
|
||||
s = setup(); enable(); queue_binary(s); run_work();
|
||||
read_hook = reuse_during_read; read_bytes = 512;
|
||||
drain_binary_output(s, s->generation, s->broker_client_id);
|
||||
assert(row().completion_nonempty.count == 0 && !s->work_pending);
|
||||
s = setup(); enable(); queue_binary(s); detach(); run_work();
|
||||
assert(!sends && row().sent_frames == 0);
|
||||
assert(web_serial_transport_attach_server(request.handle) == ESP_OK);
|
||||
s = setup(); enable(); send_hook = detach; queue_binary(s); run_work();
|
||||
assert(sends == 1 && row().sent_frames == 0 && !s->work_pending);
|
||||
s = setup(); enable(); queue_binary(s); disconnect_stalled = true;
|
||||
assert(web_serial_transport_detach_server(request.handle) == ESP_ERR_TIMEOUT);
|
||||
run_work(); assert(!sends && row().sent_frames == 0);
|
||||
|
||||
s = setup(); enable(); queue_binary(s); request.sess_ctx = &s_slots[1];
|
||||
run_work(); assert(!sends && row().send_errors == 1 && !row().send_call.count);
|
||||
|
||||
/* A copied/stale callback argument cannot own a reused slot. */
|
||||
s = setup(); enable(); queue_binary(s); web_serial_work_t stale = s->work;
|
||||
web_serial_send_work(&stale); assert(!sends && s->work_pending);
|
||||
++s->generation; run_work(); assert(!sends && row().sent_frames == 0);
|
||||
s = setup(); enable(); queue_binary(s); web_serial_session_free(s);
|
||||
run_work(); assert(!sends && !s->work_pending); process_broker_disconnect(s);
|
||||
assert(s->state == WEB_SERIAL_SLOT_FREE);
|
||||
uint32_t generation; s = reserve_slot(request.handle, 10, &generation);
|
||||
assert(s && !s->performance.sent_frames && !s->performance_epoch);
|
||||
|
||||
s = setup(); enable(); s->performance.sent_bytes = UINT64_MAX - 1;
|
||||
queue_binary(s); run_work(); assert(row().sent_bytes == UINT64_MAX && row().saturated);
|
||||
clear(); assert(!row().saturated);
|
||||
s->performance.queue_wait.count = UINT64_MAX;
|
||||
s->performance.queue_wait.sum_us = UINT64_MAX - 1;
|
||||
queue_binary(s); now += 9; run_work();
|
||||
assert(row().queue_wait.count == UINT64_MAX && row().queue_wait.sum_us == UINT64_MAX);
|
||||
assert(row().queue_wait.max_us == 9 && row().saturated);
|
||||
clear();
|
||||
assert(performance_command("show") == 0);
|
||||
assert(performance_command("disable") == 0 && !performance_gate());
|
||||
assert(performance_command("enable") == 0 && performance_gate());
|
||||
assert(performance_command("clear") == 0);
|
||||
assert(performance_command("bogus") == 1);
|
||||
s_performance_epoch = UINT32_MAX - 1; enable();
|
||||
assert(web_serial_performance_clear() == ESP_ERR_INVALID_STATE && !performance_gate());
|
||||
assert(!host_lock_depth);
|
||||
printf("PASS: performance production queue/send/drain, epochs, idle, retirement, saturation\n");
|
||||
printf("host slot diagnostics storage=%zu bytes (two slots); target ABI may differ\n",
|
||||
2 * (sizeof(s->performance) + sizeof(s->performance_epoch) + sizeof(s->completion_epoch)
|
||||
+ sizeof(s->queued_us) + sizeof(s->completed_us) + sizeof(s->completion_waiting)
|
||||
+ sizeof(s->completion_attempted)));
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user