Performance HUD
The Performance HUD is an in-app overlay that shows the runtime’s view of itself: frame time, diff size, output bytes, and the current degradation tier. It is designed to be cheap to render, deterministic, and safe to leave on during normal use — so you can reach for it any time you suspect a regression without worrying that the overlay itself is distorting the measurement.
Source: crates/ftui-demo-showcase/src/app.rs (HUD widget + JSONL
emitter) and crates/ftui-widgets/ (observability widgets).
When it is useful
- After a refactor. You changed something in the rendering path; the HUD confirms the p99 didn’t creep.
- During a UX tune. Gradients look nice but cost bytes; the HUD shows exactly how many.
- While reproducing a bug. A user says “the inline log viewer feels stuttery”; turning on the HUD tells you which tier you’re in and whether the cascade is flapping.
- Before opening a PR. A quick sanity check that your branch still hits the SLO budgets.
Quick start
Run the demo showcase on the Performance screen and toggle the HUD:
cargo run -p ftui-demo-showcase -- --screen=10Keybindings inside the demo:
| Key | Effect |
|---|---|
Ctrl+P | Toggle the Performance HUD. |
? | Toggle the help overlay. |
F12 | Toggle the debug overlay (strategy internals, not just HUD). |
Ctrl+K | Open the command palette — cmd:toggle_perf_hud works from there too. |
Tab / Shift+Tab | Cycle screens. |
q, Ctrl+C | Quit. |
The demo script
scripts/perf_hud_demo.sh wraps the above with convenient defaults.
./scripts/perf_hud_demo.sh # Alt-screen, defaults
./scripts/perf_hud_demo.sh --inline # Inline mode
./scripts/perf_hud_demo.sh --inline --ui-height 12
./scripts/perf_hud_demo.sh --auto-exit 1500 # Quit after 1.5 s
./scripts/perf_hud_demo.sh --pty # Wrap in a PTY (useful in CI)
./scripts/perf_hud_demo.sh --no-mouse # Disable mouse (cleaner captures)All flags pass through to ftui-demo-showcase. See
E2E scripts for the full E2E-gate version
(scripts/perf_hud_e2e.sh).
What the HUD shows
The HUD is built from the observability widget family. A compact layout at the top-right corner surfaces:
| Row | Meaning |
|---|---|
| Tick / FPS | Observed tick rate and frames per second over the rolling window. |
| Frame time | p50 / p95 / p99 in microseconds over the last few hundred frames. |
| Budget | Remaining time in the current frame’s SLO budget. |
| Tier | Current degradation tier — Full, SimpleBorders, NoColors, TextOnly. |
| Diff | Number of changed cells + run count for the last diff. |
| Output | Bytes emitted this frame + bytes per changed cell. |
On a very small terminal the HUD falls back to a single-line
minimal view: FPS 58 p99 3.4ms tier=Full diff=42c.
Emitting JSONL from the HUD
Every HUD update can also be tee’d to an append-only JSONL file — the same file CI replays use — by setting:
FTUI_PERF_HUD_JSONL=/tmp/perf_hud.jsonl cargo run -p ftui-demo-showcase -- --screen=10Schema:
{"schema_version":"perf-hud-v1","run_id":"…","seq":N,"event":"perf_hud","frame_time_p99_us":3412,"tier":"Full","diff_cells":42,"output_bytes":2180}Two emit functions inside app.rs (emit_perf_hud_jsonl for string
fields and emit_perf_hud_jsonl_numeric for numeric ones) produce
these lines. They share the monotonic sequence counter used by the
evidence sink, so lines from the HUD and
lines from the runtime interleave cleanly.
Tick stall detection
The HUD’s emitter also includes a stall watchdog:
TICK_STALL_WARN_AFTER= 750 ms. If no ticks are observed for longer than this, a warning is logged.TICK_STALL_LOG_INTERVAL= 2000 ms. Subsequent stall warnings are rate-limited to one every 2 s to avoid drowning the ledger.
A stall warning usually means one of: an update took too long, a
subscription died, or the clock source changed (see
determinism fixtures).
Determinism guarantees
The HUD is part of the kernel’s determinism contract:
- Output is deterministic given identical inputs. The same input
sequence produces the same HUD text at the same frames, under
E2E_DETERMINISTIC=1. - Missing data renders as
n/a, never panics. If the rolling window hasn’t accumulated enough samples to produce a p99, the HUD showsp99 n/a— a deliberate rendering, not a crash. - Tiny terminals fall back to a single-line view; the overlay is never truncated into garbage.
Troubleshooting
The HUD isn’t appearing. Press Ctrl+P, or open the command
palette with Ctrl+K and run cmd:toggle_perf_hud. If keybindings
don’t land, your terminal may be swallowing Ctrl+P — try the
command palette route.
The HUD overlaps my widgets. Increase your terminal size, or
switch to inline mode (./scripts/perf_hud_demo.sh --inline --ui-height 18).
The HUD says n/a for p99. Not enough samples yet. Let the app
render for a few seconds, or generate activity (scroll a list,
resize the window).
Pitfalls
Don’t benchmark with the HUD on. It adds a small but real render cost. For baseline measurement (e.g. to feed benchmark gate budgets) disable it.
Inline mode changes the available area. Leaving the HUD on while
running the demo in inline mode can collide with your logs. Use
--ui-height N to reserve enough rows.
Tests
The HUD has targeted snapshot and E2E tests:
cargo test -p ftui-demo-showcase perf_hud
cargo test -p ftui-demo-showcase --test perf_hud_e2eAnd the E2E gate that exercises the HUD in a PTY:
./scripts/perf_hud_e2e.sh