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:
@@ -143,6 +143,9 @@ def test_the_discrete_gpu_capture_supplies_real_numbers(
|
||||
assert gpu["busy_pct"] == pytest.approx(snapshot["gpus"][0]["busy_pct"])
|
||||
assert gpu["vram_used_pct"] == pytest.approx(
|
||||
gpu["vram_used_bytes"] / gpu["vram_total_bytes"] * 100.0)
|
||||
# the hero's footnote is the agent's own max over this card's hwmon labels (phase 06),
|
||||
# so the card and the `gpu.temp_c@N` history series can never disagree
|
||||
assert gpu["temp_c"] == max(snapshot["gpus"][0]["temps"].values())
|
||||
|
||||
# one scrape is not a rate: usage and throughput are honestly n/a until the second
|
||||
assert payload["cpu"]["usage_pct"] is None
|
||||
@@ -177,11 +180,34 @@ def test_each_card_number_travels_with_its_own_verdict(host_scrape: Any,
|
||||
"mem.used_pct", payload["memory"]["used_pct"])
|
||||
assert payload["disks"]["severity"]["worst_used_pct"] == classify(
|
||||
"disk.used_pct", payload["disks"]["worst_used_pct"])
|
||||
|
||||
|
||||
def test_the_gpu_card_carries_no_verdict_at_all(host_scrape: Any,
|
||||
agent_snapshot: dict[str, Any]) -> None:
|
||||
"""Phase 06: a fully-worked GPU is the job on an LLM server, not a fault.
|
||||
|
||||
The card prints busy % / VRAM with no colour class at any level, so the payload
|
||||
carries no `severity` dict to colour from — the absence is the contract (pushed past
|
||||
every old bound in the fixture, so a resurrected threshold row would trip and fail).
|
||||
"""
|
||||
def work_the_card(snap: dict[str, Any]) -> None:
|
||||
gpu = snap["gpus"][0]
|
||||
gpu["busy_pct"] = 100.0
|
||||
for used, total in (("vram_used_bytes", "vram_total_bytes"),
|
||||
("gtt_used_bytes", "gtt_total_bytes")):
|
||||
if gpu.get(total) is not None:
|
||||
gpu[used] = gpu[total]
|
||||
|
||||
payload = host_scrape(agent_snapshot, cpu_busy=33.0, mutate=work_the_card)
|
||||
gpu = payload["gpus"][0]
|
||||
assert gpu["severity"] == {
|
||||
"busy_pct": classify("gpu.busy_pct", gpu["busy_pct"]),
|
||||
"mem_used_pct": classify("gpu.mem_used_pct", gpu["mem_used_pct"]),
|
||||
}
|
||||
assert gpu["busy_pct"] == 100.0, "the numbers themselves stay on the card"
|
||||
assert gpu["mem_used_pct"] > 90.0, "the whole pool is worked too"
|
||||
assert "severity" not in gpu, (
|
||||
"a GPU verdict came back — classify('gpu.busy_pct', …) must be None and the row "
|
||||
"must not carry a dict for the page to colour from"
|
||||
)
|
||||
assert classify("gpu.busy_pct", gpu["busy_pct"]) is None
|
||||
assert classify("gpu.mem_used_pct", gpu["mem_used_pct"]) is None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -208,6 +234,26 @@ def test_the_apu_reports_vram_and_the_whole_addressable_pool(
|
||||
assert gpu["model"], "the card needs a name to put beside the numbers"
|
||||
|
||||
|
||||
def test_a_gpu_with_no_temperature_input_reports_none_not_zero(agent_snapshot: dict[str, Any],
|
||||
) -> None:
|
||||
"""Phase 06 put `gpus[].temp_c` on the hero footnote, so its absence must stay honest.
|
||||
|
||||
A driver exposing no hwmon temperature has to come back as `None` (the page prints no
|
||||
temperature at all) rather than `0 °C`, the same rule
|
||||
`tests/test_host_source.py::test_missing_sensors_stay_missing` holds for the flat sensor
|
||||
list — and no `gpu.temp_c@N` sample may be written for a number nobody measured.
|
||||
"""
|
||||
snapshot = copy.deepcopy(agent_snapshot)
|
||||
snapshot["gpus"][0]["temps"] = {}
|
||||
result = parse_host_snapshot("driveripper", snapshot, make_collector(), now=1_000.0)
|
||||
gpu = result.payload["gpus"][0]
|
||||
assert gpu["temp_c"] is None, "an unreadable sensor is not a cold card"
|
||||
assert gpu["temps"] == {}
|
||||
assert_card_contract(result.payload)
|
||||
assert not [s for s in result.samples if s.metric.startswith("gpu.temp_c@")], (
|
||||
"a None temperature must not reach the history as a number")
|
||||
|
||||
|
||||
def test_paths_survive_a_host_that_reports_almost_nothing(agent_snapshot: dict[str, Any]) -> None:
|
||||
"""Keys present, values None: the card must render `n/a` without a KeyError."""
|
||||
snapshot = copy.deepcopy(agent_snapshot)
|
||||
@@ -229,9 +275,10 @@ def test_paths_survive_a_host_that_reports_almost_nothing(agent_snapshot: dict[s
|
||||
assert payload["disks"]["severity"]["worst_used_pct"] is None
|
||||
assert payload["network"]["rx_bytes_per_s"] is None
|
||||
gpu = payload["gpus"][0]
|
||||
assert gpu["busy_pct"] is None and gpu["severity"]["busy_pct"] is None
|
||||
assert gpu["busy_pct"] is None
|
||||
assert gpu["vram_used_pct"] is None, "no total, so no percentage — not 0 %"
|
||||
assert gpu["mem_used_pct"] is None
|
||||
assert gpu["temp_c"] is not None, "the card still has its temperature to print"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
Reference in New Issue
Block a user