mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-27 14:30:33 +00:00
Add serial-triggered performance profile test
Debug command 'P' runs a standardized 5-test benchmark: 1. Idle frame (nothing dirty) — measures LVGL overhead 2. Full frame invalidation + render — measures render + flush 3. Data update cycle — measures label formatting cost 4. Navigate all 5 tiles — measures tile transition cost 5. 10-frame burst — measures sustained frame rate Results reported as JSON with build timestamp for regression tracking. Python tool: ./scripts/screenshot.py profile Baseline (Mar 29 2026, hwcdc, unprovisioned): Idle: 914µs, Full: 80ms (37ms flush + 42ms render), Data update: 2ms, Per-tile: 75ms, Per-frame: 80ms, Main loop: 331µs idle
This commit is contained in:
parent
926330253a
commit
5a1f8eb8f4
2 changed files with 129 additions and 1 deletions
96
Gui.h
96
Gui.h
|
|
@ -798,7 +798,8 @@ bool gui_init() {
|
|||
// 'M' (0x4D) — Metrics: responds RWSM + JSON stats
|
||||
// 'I' (0x49) — Invalidate: force full screen redraw
|
||||
// 'L' (0x4C) — Log toggle: start/stop IMU logging to SD card
|
||||
// 'F' (0x46) — File download: 1 byte name length + filename, sends file over serial
|
||||
// 'F' (0x46) — File list: lists files on SD card
|
||||
// 'P' (0x50) — Profile: runs standardized performance test, reports JSON results
|
||||
|
||||
#define GUI_CMD_PREFIX_LEN 3
|
||||
static const uint8_t gui_cmd_prefix[] = {0x52, 0x57, 0x53}; // "RWS"
|
||||
|
|
@ -929,6 +930,99 @@ static void gui_cmd_execute() {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'P': { // Standardized performance profile test
|
||||
Serial.write(hdr, 4);
|
||||
if (display_blanked) display_unblank();
|
||||
|
||||
uint32_t p_t0, p_t1;
|
||||
uint32_t p_idle_render = 0, p_idle_flush = 0;
|
||||
uint32_t p_full_render = 0, p_full_flush = 0;
|
||||
uint32_t p_nav_total = 0;
|
||||
uint32_t p_data_update = 0;
|
||||
uint32_t p_frames = 0;
|
||||
uint32_t p_multi_total = 0;
|
||||
|
||||
// Test 1: Idle frame (nothing dirty)
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler(); // clear any pending
|
||||
gui_flush_us_last = 0;
|
||||
gui_frame_count = 0;
|
||||
p_t0 = micros();
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
p_t1 = micros();
|
||||
p_idle_render = p_t1 - p_t0;
|
||||
p_idle_flush = gui_flush_us_last;
|
||||
|
||||
// Test 2: Full invalidation + render
|
||||
lv_obj_invalidate(lv_screen_active());
|
||||
gui_flush_us_last = 0;
|
||||
p_t0 = micros();
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
p_t1 = micros();
|
||||
p_full_render = p_t1 - p_t0;
|
||||
p_full_flush = gui_flush_us_last;
|
||||
|
||||
// Test 3: Data update cycle
|
||||
gui_last_data_update = 0; // force update
|
||||
p_t0 = micros();
|
||||
gui_update_data();
|
||||
p_t1 = micros();
|
||||
p_data_update = p_t1 - p_t0;
|
||||
|
||||
// Test 4: Navigate to each tile and back (5 transitions)
|
||||
p_t0 = micros();
|
||||
lv_tileview_set_tile(gui_tileview, gui_tile_radio, LV_ANIM_OFF);
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
lv_tileview_set_tile(gui_tileview, gui_tile_gps, LV_ANIM_OFF);
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
lv_tileview_set_tile(gui_tileview, gui_tile_msg, LV_ANIM_OFF);
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
lv_tileview_set_tile(gui_tileview, gui_tile_set, LV_ANIM_OFF);
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
lv_tileview_set_tile(gui_tileview, gui_tile_watch, LV_ANIM_OFF);
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
p_t1 = micros();
|
||||
p_nav_total = p_t1 - p_t0;
|
||||
|
||||
// Test 5: Rapid frame burst (10 full frames)
|
||||
p_t0 = micros();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
lv_obj_invalidate(lv_screen_active());
|
||||
lv_tick_inc(LV_DEF_REFR_PERIOD + 1);
|
||||
lv_timer_handler();
|
||||
}
|
||||
p_t1 = micros();
|
||||
p_multi_total = p_t1 - p_t0;
|
||||
|
||||
Serial.printf("{\"test\":\"profile\",\"build\":\"%s %s\","
|
||||
"\"idle_us\":%lu,\"idle_flush_us\":%lu,"
|
||||
"\"full_us\":%lu,\"full_flush_us\":%lu,"
|
||||
"\"data_update_us\":%lu,"
|
||||
"\"nav_5tile_us\":%lu,"
|
||||
"\"burst_10frame_us\":%lu,"
|
||||
"\"avg_frame_us\":%lu,"
|
||||
"\"loop_us\":%lu,"
|
||||
"\"heap\":%lu,\"psram\":%lu}\n",
|
||||
__DATE__, __TIME__,
|
||||
p_idle_render, p_idle_flush,
|
||||
p_full_render, p_full_flush,
|
||||
p_data_update,
|
||||
p_nav_total,
|
||||
p_multi_total, p_multi_total / 10,
|
||||
gui_loop_us_last,
|
||||
(uint32_t)esp_get_free_heap_size(),
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||
Serial.flush();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'I': { // Invalidate — force full redraw
|
||||
if (gui_screen) lv_obj_invalidate(gui_screen);
|
||||
if (display_blanked) display_unblank();
|
||||
|
|
|
|||
|
|
@ -153,6 +153,35 @@ def cmd_navigate(s, screen):
|
|||
print(f"Navigate → {screen} ({col},{row})")
|
||||
|
||||
|
||||
def cmd_profile(s):
|
||||
"""Run standardized performance profile test"""
|
||||
send_cmd(s, ord('P'))
|
||||
buf = b""
|
||||
deadline = time.time() + 30 # profile test takes several seconds
|
||||
while time.time() < deadline:
|
||||
chunk = s.read(max(1, s.in_waiting or 1))
|
||||
if chunk:
|
||||
buf += chunk
|
||||
magic = PREFIX + b"P"
|
||||
idx = buf.find(magic)
|
||||
if idx >= 0:
|
||||
nl = buf.find(b"\n", idx + 4)
|
||||
if nl >= 0:
|
||||
import json
|
||||
data = json.loads(buf[idx + 4:nl])
|
||||
print(f"Build: {data.get('build', '?')}")
|
||||
print(f"Idle frame: {data['idle_us']:>8} µs (flush: {data['idle_flush_us']} µs)")
|
||||
print(f"Full frame: {data['full_us']:>8} µs (flush: {data['full_flush_us']} µs)")
|
||||
print(f"Data update: {data['data_update_us']:>8} µs")
|
||||
print(f"Nav 5 tiles: {data['nav_5tile_us']:>8} µs ({data['nav_5tile_us']//5} µs/tile)")
|
||||
print(f"Burst 10 frame: {data['burst_10frame_us']:>8} µs ({data['avg_frame_us']} µs/frame)")
|
||||
print(f"Main loop: {data['loop_us']:>8} µs")
|
||||
print(f"Heap free: {data['heap']:>8} bytes")
|
||||
print(f"PSRAM free: {data['psram']:>8} bytes")
|
||||
return
|
||||
print(f"Timeout ({len(buf)} bytes)")
|
||||
|
||||
|
||||
def cmd_invalidate(s):
|
||||
send_cmd(s, ord('I'))
|
||||
print("Invalidated — full redraw requested")
|
||||
|
|
@ -217,6 +246,9 @@ def main():
|
|||
|
||||
sub.add_parser("invalidate", aliases=["inv"])
|
||||
|
||||
sub.add_parser("profile", aliases=["p"],
|
||||
help="Run standardized performance test")
|
||||
|
||||
sub.add_parser("log", aliases=["l"],
|
||||
help="Toggle IMU logging to SD card")
|
||||
|
||||
|
|
@ -242,6 +274,8 @@ def main():
|
|||
cmd_navigate(s, args.screen)
|
||||
elif args.command in ("invalidate", "inv"):
|
||||
cmd_invalidate(s)
|
||||
elif args.command in ("profile", "p"):
|
||||
cmd_profile(s)
|
||||
elif args.command in ("log", "l"):
|
||||
cmd_log(s)
|
||||
elif args.command in ("files", "f"):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue