<!-- nornir:class: guide -->
# 📡 Bench LIVE pane — surface a bench *as it runs* (P1.3 / C6d)
> Design + guide for the **LIVE telemetry panel** in the `nornir viz` **Bench**
> tab (`src/viz/bench_live.rs`, rendered first inside `Tab::Bench` by
> `src/viz/ops_tabs.rs::BenchState`). P1.3 turns the bencher's CPU/mem telemetry
> (P1.2's `bench_telemetry` table) into a live surface: as a bench runs the pane
> names the running bench, shows its green/red status, a **cores-busy / N**
> indicator, and a small CPU sparkline — updating as rows land in the warehouse.
> THE_BIG_PLAN LAWs apply: everything rendered is exposed as data
> (`state_json()["bench"]["live"]` + `$NORNIR_VIZ_TRACE` `bench.live`), the test
> inject-and-asserts real values, and a wgpu PNG snapshot is the acceptance
> artifact.
## Why
P1.2 built CPU/mem telemetry *into* the bencher: a 1 Hz `/proc/stat` sampler folds
per-bench min/avg/max **cores-busy** (of N logical cores) + **peak mem** and
streams them — `[cores-busy C/N]` on each `[BENCH]` line, and a dedicated
warehouse `bench_telemetry` table (`run_id, repo, bench, n_cores, cpu_pct_*,
cores_busy_avg/max, mem_peak_mb, mem_pct_max, elapsed_ms`). But the viz **Bench**
tab only charted *historical* metric series — the live footprint never showed.
P1.3 adds the panel that lights up as the bench runs.
## What it shows
A list over the warehouse `bench_telemetry` table (one row per `(run_id, repo,
bench)`), newest run first, each bench unit as one row:
| element | meaning |
|--------------------|---------|
| **status dot** | green ✓ / red ✗ / neutral ·, derived from the run's paired `tests` outcome (see *Status*) |
| **bench NAME** | the bench unit id (`znippy.compress`, `holger.ops`, …) |
| **cores / N** | `proc_cores_max / n_cores` — **this process's** peak occupancy (task-clock ÷ wall), colour-ramped (green few → amber mid → red pegged). NOT `cores_busy_max`: that is `/proc/stat`, the whole box, and reads N/N for a strictly serial arm on any loaded machine (measured on oden 2026-08-03 — `gunnar.walk_1m` at 32/32 while holding 0.56 of one core). A row from before 2026-08-03 has no such measurement and must render as `?`, not `0`. |
| **host-busy / N** | `cores_busy_max / n_cores` — ambient context, explicitly labelled as the host so it is never mistaken for the bench's parallelism |
| **CPU sparkline** | a tiny 3-point trace: `[cpu_pct_avg, cpu_pct_max, peak-busy-fraction]` (0..100) — HOST-WIDE, so it is the shape of how hard *the box* was pushed |
| **cpu %** | `cpu_pct_max` as a numeral (host-wide) |
| **mem badge** | `mem_peak_mb` when > 0 |
| **header strip** | the live `run_id` + green/red/neutral tallies + a ↻ re-read button |
## Status — green/red per bench
The `bench_telemetry` row has no pass/fail; the verdict comes from `bench_runs`.
`bench_status(repo, bench, runs)` pairs a bench with a `TestOutcome` whose `name`
matches the bench **exactly** or against the bench's last `.`-segment
(`znippy.compress` ↔ test `compress`):
* a matching test that **failed** → **red**,
* a matching test that **passed** → **green**,
* no paired test → **neutral** (the bench ran but no test gates it).
## Live update — as rows arrive
**Source of truth = the warehouse** (LAW *persist-to-warehouse-stream-not-tmp*).
Local mode opens the warehouse read-only (lock-tolerant, like the 🧪 Test / 🚀
Release tabs) and reads `bench_telemetry` + `bench_runs`. While the tab is open,
`draw()` re-reads every `RELOAD_EVERY` (~1.5 s) and calls
`request_repaint_after`, so telemetry rows that land mid-run (P1.2 appends per
run) appear without user input. A ↻ button forces an immediate re-read; a
workspace switch / reload (`app.reload()` → `BenchState::reload()`) drops the
cache so the next draw re-hydrates.
Remote (thin-client) mode shows a note — there is no `Bench.Telemetry` RPC yet
(same split as Test / Release / Leaderboard). The existing remote **Bench
history** charts are unchanged and render below the live panel.
## Architecture — pure fold + thin renderer
```
bench_telemetry rows ─┐
├─ fold_live() ─► Vec<LiveBench> ─► draw_live_row() (egui)
bench_runs (tests) ───┘ │
└─► state_json()["bench"]["live"]
$NORNIR_VIZ_TRACE bench.live IN/OUT/END
```
`fold_live()` is pure (telemetry + runs → `Vec<LiveBench>`) and unit-tested in
isolation. `LiveBench::cpu_spark()` / `cores_label()` are the exact values the
cell paints, surfaced verbatim in `state_json` so the headless matrix asserts the
rendered content (LAW #6 — "see what the user sees" as data, not pixels).
Ordering: newest run first (`run_id` descending as a time-prefixed proxy), then
by bench name within a run.
## state_json shape (LAW #6)
`state_json()["bench"]["live"]`:
```json
{
"source": "local /…/warehouse",
"error": null,
"live_run_id": "run-2",
"count": 3,
"green": 2, "red": 1, "neutral": 0,
"benches": [
{
"run_id": "run-2", "repo": "znippy", "bench": "znippy.compress",
"status": "green", "ok": true,
"n_cores": 8, "cores_busy_avg": 6.5, "cores_busy_max": 7,
"cores_label": "cores-busy 6.5/8 (peak 7/8)",
"cpu_pct_avg": 55.2, "cpu_pct_max": 92.0,
"mem_peak_mb": 512.0, "elapsed_ms": 2200.0,
"cpu_spark": [55.2, 92.0, 87.5]
}
]
}
```
`bench.live` IN/OUT/END trace events carry the same `benches` payload + the
green/red/neutral tally.
## Test (inject-and-assert) — `tests/viz_bench_pane.rs`
`bench_pane_exposes_live_telemetry` builds the app, injects concrete
`bench_telemetry` rows + paired `bench_runs` via
`UrdrThreadsApp::inject_bench_telemetry_for_test`, selects the Bench tab, and
asserts `state_json()["bench"]["live"]`:
* the live run is the **newest** (`run-2`), newest-run-first ordering,
* the green bench (`znippy.compress`) reads back name + `status="green"` +
`n_cores=8` + `cores_busy_max=7` + `cores_label="cores-busy 6.5/8 (peak 7/8)"`
+ `cpu_pct_max=92` + `mem_peak_mb=512` + a 3-point `cpu_spark` whose tail is
`87.5` (= 7/8 cores busy),
* the red bench (`holger.ops`) reads back `status="red"`, `ok=false`,
`cores_busy_max=4`.
`viz_snapshot_bench_pane` renders the pane via `egui_kittest` wgpu, asserts the
populated state (`count=3`, `red>=1`), and writes
`tests/snapshots/viz_bench_pane.png` (LAW #4). Unit tests in `bench_live.rs`
cover `fold_live` pairing/ordering and the `state_json` round-trip.
## Files
* `src/viz/bench_live.rs` — `BenchLive` panel: load/fold/draw/state_json + `fold_live`.
* `src/viz/ops_tabs.rs` — `BenchState` embeds `BenchLive`, draws it first, folds
`live` into its `state_json`, exposes `local`/`remote`/`reload`/inject hooks.
* `src/viz/app.rs` — constructs `BenchState::local|remote` per source, reloads on
workspace switch, `inject_bench_telemetry_for_test`.
* `tests/viz_bench_pane.rs` — inject-assert + wgpu snapshot.
* Reads: `src/bench/telemetry.rs` (P1.2 sampler), `src/warehouse/iceberg.rs`
(`bench_telemetry` table + `BenchTelemetryRow` + `query_bench_telemetry`).