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:
+17
-5
@@ -23,8 +23,6 @@ RATIFIED = {
|
||||
"mem.used_pct": (80.0, 92.0),
|
||||
"disk.used_pct": (80.0, 90.0),
|
||||
"cpu.temp_c": (75.0, 90.0),
|
||||
"gpu.busy_pct": (85.0, 95.0),
|
||||
"gpu.mem_used_pct": (85.0, 95.0),
|
||||
"llm.kv_cache_usage_pct": (75.0, 90.0),
|
||||
"ups.charge_pct": (50.0, 20.0),
|
||||
"ups.battery_runtime": (600.0, 300.0),
|
||||
@@ -109,6 +107,22 @@ def test_unknown_metric_is_none_rather_than_an_exception() -> None:
|
||||
assert classify("", 100) is None
|
||||
|
||||
|
||||
def test_gpu_usage_carries_no_verdict_at_any_level() -> None:
|
||||
"""Phase 06: these are LLM servers — a busy GPU is the job, not a fault.
|
||||
|
||||
The table has **no `gpu.*` row at all** (not a raised ceiling): every utilisation
|
||||
level classifies to `None`, so the card renders uncoloured rather than inventing
|
||||
amber for the workload the machines exist to run. Deleting the row is not deleting
|
||||
the data — the `gpu.busy_pct@N` series are still stored and charted.
|
||||
"""
|
||||
assert not any(metric.startswith("gpu.") for metric in THRESHOLDS)
|
||||
assert classify("gpu.busy_pct", 0) is None
|
||||
assert classify("gpu.busy_pct", 99) is None
|
||||
assert classify("gpu.mem_used_pct", 99) is None
|
||||
assert classify("gpu.mem_used_pct", 100) is None
|
||||
assert classify("gpu.busy_pct@0", 95) is None, "streams inherit the absent row: nothing"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metric", sorted(THRESHOLDS))
|
||||
def test_none_is_never_ok(metric: str) -> None:
|
||||
assert classify(metric, None) is None
|
||||
@@ -123,10 +137,8 @@ def test_non_finite_values_are_treated_as_missing(metric: str, value: float) ->
|
||||
def test_per_device_streams_inherit_their_base_metric() -> None:
|
||||
assert classify("disk.used_pct@/srv", 95) == "fail"
|
||||
assert classify("disk.used_pct@/srv", 10) == "ok"
|
||||
assert classify("gpu.busy_pct@0", 84) == "ok"
|
||||
assert classify("gpu.busy_pct@0", 90) == "warn"
|
||||
assert classify("gpu.busy_pct@1", 95) == "fail"
|
||||
assert classify("cpu.usage_pct@core7", 80) == "warn"
|
||||
assert classify("mem.used_pct@any", 95) == "fail"
|
||||
|
||||
|
||||
def test_vocabulary_is_exactly_ok_warn_fail_or_none() -> None:
|
||||
|
||||
Reference in New Issue
Block a user