phase: 06_system_page_gpu_verdicts
All criteria verified and the project is fully green.
**Verified (final pass — all 3 phase-06 tasks were already in `complete/`)**
- `classify("gpu.busy_pct", 99)` / `gpu.mem_used_pct` → `None`; `THRESHOLDS` has no `gpu.*` row; `alerts.py`/`api.py` carry no GPU verdict; live browser: deskwork GPU at **91 %** and **98 %** renders `class="hero"`, `color=rgb(22,25,31)` (plain text), no bar, no `.sev-*`.
- GPU temp on the hero `.sub` from live agents: `Radeon 8060S Graphics · VRAM 165.1 MiB / 512.0 MiB · pool 78% · 48 °C`, R9700 `· 40 °C`, gateway prints none (no `n/a`, no `0 °C`).
- Charts collapsed by default: fresh load = 3 panels / 3 toggles / **0 open / 0 `<svg>`**; expand → 3 SVGs (510 px wide), repaints across polls (legend 3.3 %→5.6 %), collapsed siblings keep their own height (803 vs 358/339).
- `gpu.busy_pct@0` still answers from `/api/system/history` (345 points) — verdict gone, data intact.
**Tests / lint / coverage**
- `bash .agents/validate.sh` → **EXIT=0**; `uv run pytest --cov --cov-fail-under=94` → **550 passed, 8 skipped, 97.91 %**; `uv run ruff check src tests agent` → clean; `uv run pyright` → 0 errors; `node --check` all scripts clean; `uv pip list --outdated` empty.
- `uv run pytest -m live --live` → **8 passed** (real agents/engines/upsd); dev server restarted, `/api/health` all sources `ok`.
- New screenshots: `data/logs/phase06-final-collapsed-default.png`, `data/logs/phase06-final-expanded-gpu-busy-uncoloured.png`.
**Completion criteria:** classify→None ✅ · pytest+94 % gate ✅ · ruff+pyright ✅ · validate.sh ✅ · screenshots of the three behaviours on live data ✅ · AGENTS.md severity section claims no GPU rows ✅
**Docs:** fixed 3 items — (1) `AGENTS.md` pytest count stale (546 → 550); (2) `app.js` `severityFor` docstring still claimed "`page-ups.js` still calls it (phase 05 converts it)", contradicting the code/tests/AGENTS.md (comment-only); rest of README/AGENTS reviewed in full (env table vs `config.py`+`.env.example`, agent routes incl. `/schema`, alerts rules, page descriptions, contracts) — consistent.
**Notable:** fixed a real pre-existing defect in `scripts/dev-server.sh` — `stop` slept 1 s and deleted the pidfile without waiting for exit, so `restart` with a browser open overlapped two servers on one SQLite file, producing a `database is locked` store refusal that `/system` rendered as a red event. Now waits (bounded 20 s) then SIGKILLs; verified a restart with live polling logs no such error/event. No live infra config was changed.
**Next pending phase:** `.agents/phases/todo/07_llm_monitor_style_pages`
This commit is contained in:
@@ -139,12 +139,14 @@ store.py ── SQLite (WAL): sample(source,host,metric,res,ts) + event ──
|
||||
pruned, so a year of history is a few hundred MB rather than a disk emergency.
|
||||
* `severity.py` — the single green/amber/red table (`THRESHOLDS`, plus `INVERTED` for the
|
||||
metrics where lower is worse). Rated values in the API payloads travel with a sibling
|
||||
`severity` (`cpu.severity.usage_pct`, `memory.severity`, `disks.severity`,
|
||||
`gpus[].severity`, a UPS card's `severity.charge_pct`, `severity` inside the rated LLM
|
||||
`severity` (`cpu.severity.usage_pct`, `memory.severity`, `disks.severity`, a UPS card's
|
||||
`severity.charge_pct`, `severity` inside the rated LLM
|
||||
cells such as KV pressure, `/api/summary` rows), computed server-side, so every page
|
||||
colours the same way and a value nobody reported renders `n/a` instead of green. A metric
|
||||
published as a 0-1 ratio is rescaled *for the verdict only* — the number printed stays
|
||||
what the upstream published.
|
||||
what the upstream published. There is no `gpu.*` row: these are LLM servers, a busy GPU
|
||||
is the job, so the GPU card (and the fleet GPU column) renders uncoloured at every
|
||||
utilisation — the `gpu.*` history series are still stored and charted.
|
||||
* `alerts.py` — "what is wrong right now", as data. `alerts.build(settings, job_states,
|
||||
host_payloads, engine_payloads, ups_payload, recent_events)` is a pure projection of the
|
||||
state the poller already keeps plus the verdicts `severity.py` and the UPS health checks
|
||||
@@ -171,18 +173,27 @@ store.py ── SQLite (WAL): sample(source,host,metric,res,ts) + event ──
|
||||
charging, dashed outline on battery, a dash through the body when there is no reading) and a
|
||||
`<title>` / `aria-label` that names it ("apc: Charge 100 percent, on line") unless the number
|
||||
beside it already does. Each `/system` host is one panel led
|
||||
by exactly six of them — CPU %, MEM %, TEMP, DISK % (mount underneath), GPU busy (VRAM and
|
||||
the APU pool in the footnote), and NET on two lines (↓ in / ↑ out, never coloured: a byte
|
||||
rate has no good/bad). Under the numbers, **each panel charts only its own host**: utilisation
|
||||
by exactly six of them — CPU %, MEM %, TEMP, DISK % (mount underneath), GPU busy (VRAM, the
|
||||
APU pool and the card's temperature in the footnote — no temperature is printed when the
|
||||
driver exposes no sensor, never `0 °C`; and likewise never coloured and with no bar: a busy
|
||||
GPU is the job on an LLM server), and NET on two lines (↓ in / ↑ out, never coloured: a byte
|
||||
rate has no good/bad). Under the numbers, **each panel charts only its own host** — behind a
|
||||
collapsed **Charts** toggle, because the default view is current stats and a fleet of ten
|
||||
hosts each drawing three plots is noise: the summary reads `▸ Charts` with the window in it
|
||||
(`1h`, `24h` …), and opening it is what builds and paints the charts — instantly, from the
|
||||
response the last refresh already fetched, and repainted on every refresh after that while it
|
||||
stays open. Expanding one panel never stretches its collapsed siblings
|
||||
(`.grid.items-start`). The stack is utilisation
|
||||
(CPU, memory, worst disk), network (↓ in / ↑ out), and GPU busy for a host that reports
|
||||
cards — three 120px SVG charts, each with its own legend and its own `aria-label` naming the
|
||||
host and the plotted metrics (`LineChart`'s `ariaLabel` option), never a second host in sight. The
|
||||
fleet-wide charts, the nine-figure fleet-totals panel and the host/metric detail picker are gone, and
|
||||
so are the per-core charts, while `cpu.usage_pct@coreN` stays in SQLite. Everything else —
|
||||
load, clock, disk I/O, uptime, GPU power/temp, filesystems, processes — folds into a collapsed
|
||||
load, clock, disk I/O, uptime, GPU power and the per-label temp breakdown, filesystems,
|
||||
processes — folds into a collapsed
|
||||
"More detail" disclosure. Each panel pulls its series in **one** `/api/system/history` request
|
||||
(`hosts=<that host>&metrics=…&range=…`), and a host that stops reporting takes its panel and
|
||||
its charts off the page. The LLM page (`/llm`) is per engine too: two big
|
||||
(`hosts=<that host>&metrics=…&range=…`) every refresh, stack open or not, and a host that stops
|
||||
reporting takes its panel, its Charts toggle and its charts off the page. The LLM page (`/llm`) is per engine too: two big
|
||||
throughput numbers (decode, prompt — uncoloured, because 0 tok/s on an idle server is
|
||||
healthy) with their provenance tag (`Δ10.0s`, scraped, estimated), then a compact list of
|
||||
metric rows — sessions, queue, KV pressure (the only rated cell), prefix/spec percentages,
|
||||
|
||||
Reference in New Issue
Block a user