Files
skills/tests/test_hub_resources.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 35c270e120 feat(orchestrator): W4 resources view and host samples
A resources timer samples host CPU, RAM, swap and per-session tokens into samples.jsonl; the hub serves them and a Resources view charts them. Committed below the bar on the operator's word 2026-11-12 (no further per-bundle tests, one gate after the merge): the W4 e2e never ran in a real browser.

🎯 Fix: 92 %
💥 Break: 6 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 86
🚦 Gate: ⛔ break 6 % > 5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:15:43 +00:00

230 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
import subprocess
import time
import unittest
from hub_case import HubCase
from hub_support import HUB, NAV
MODULE = HUB / "templates" / "resources.js"
PAGE = HUB / "templates" / "page-resources.js"
STYLE = HUB / "templates" / "style-resources.css"
T = 1_760_000_000
FIXTURE = """
const asleep = {name: "0000:01:00.0", asleep: true};
const awake = (util, used) => ({name: "0000:01:00.0", util, vram_used: used, vram_total: 1000});
const unread = {name: "0000:01:00.0", util: null, vram_used: null, vram_total: null};
const row = (t, cpu, ram, waiting, gpu, disks) => ({t, cpu, ram, swap: 0, load: 1, disks, gpus: [gpu], queue: {waiting, active: 1}});
const root = [{mount: "/", used: 250, total: 1000}];
const samples = [
row(T, 20, 30, 0, asleep, root),
row(T + 60, 40, 45, 2, asleep, root),
row(T + 120, 45, 40, 3, awake(80, 500), [...root, {mount: "/mnt", used: 1, total: 4}]),
row(T + 180, 70, 40, 3, awake(90, 600), root),
row(T + 240, 30, 30, 1, awake(10, 100), root),
row(T + 300, 35, 30, 0, unread, root),
row(T + 5000, 10, 10, 1, asleep, root),
row(T + 5060, 10, 10, 0, asleep, root),
];
const tokens = [
{t: T, agent: "Skills", model: "opus", input: 10, output: 20, cache_read: 300, cache_write: 40},
{t: T, agent: "Stage", model: "opus", input: 1, output: 2, cache_read: 3, cache_write: 4},
{t: T, agent: "Skills", model: "haiku", input: 5, output: 0, cache_read: 0, cache_write: 0},
{t: T + 3600, agent: "Stage", model: "opus", input: 500, output: 0, cache_read: 0, cache_write: 0},
{t: "x", agent: "Bad", model: "opus", input: 1, output: 0, cache_read: 0, cache_write: 0},
];
"""
PROBE = """
const R = require(process.argv[1]);
const T = Number(process.argv[2]);
@FIXTURE@
const lines = R.lines(samples);
const pick = (list) => list.map((entry) => [entry.name, entry.kind, entry.points]);
console.log(JSON.stringify({
zones: R.ZONES,
disks: pick(lines.disks),
host: pick(lines.host),
gpu: pick(lines.gpu),
segments: R.segments([[T, 1], [T + 60, null], [T + 120, 3], [T + 180, 4], [T + 4000, 5]]),
waits: R.waits(samples),
asleep: R.asleep(samples),
unknown: R.unknown(samples),
stack: R.stack(tokens),
}));
""".replace("@FIXTURE@", FIXTURE)
PAGE_PROBE = """
const fs = require("fs");
const vm = require("vm");
const Resources = require(process.argv[1]);
const code = fs.readFileSync(process.argv[2], "utf8");
const T = Number(process.argv[3]);
@FIXTURE@
const make = (tag, attrs, children) => {
const node = {tag, attrs: {}, children: [], textContent: "", isConnected: true,
append(...items) { this.children.push(...items); },
replaceChildren(...items) { this.children = items; },
getBoundingClientRect() { return {width: 800, height: 120}; }};
for (const [key, value] of Object.entries(attrs)) {
if (key !== "on" && value !== null && value !== undefined && value !== false) {
node.attrs[key] = String(value);
}
}
node.children = children.flat(Infinity).filter((child) => child !== null && child !== undefined && child !== false);
return node;
};
const h = (tag, attrs, ...children) => make(tag, attrs, children);
const icon = (name) => make("svg", {class: "bi", "data-icon": name}, []);
const fetched = [];
const context = {
Resources, h, s: h, icon, DAY: 86400, ui: {preset: 86400, from: null, to: null, asOf: null, offline: false},
iconBtn: (name, label, cls, run, hint) => make("button", {title: hint, "aria-label": label}, [icon(name)]),
fixed: (value) => value.toFixed(1), dash: (value) => (value === null ? "–" : value), absolute: {format: () => "d"}, clock: {format: () => "c"}, stampOf: (t) => new Date(t * 1000),
window: {setTimeout: (run) => run(), setInterval: () => 1, addEventListener: () => null}, document: {hidden: false},
windowQuery: () => "from=1", nowSeconds: () => 0,
fetch: async (url) => {
fetched.push(url);
return {ok: true, json: async () => ({since: T - 600, until: T + 6000, samples, tokens})};
},
};
vm.runInNewContext(`${code}\\nscreen = resourcesScreen();`, context);
const walk = (node, found) => {
if (node && typeof node === "object") {
found.push(node);
node.children.forEach((child) => walk(child, found));
}
return found;
};
const has = (node, name) => (node.attrs.class || "").split(" ").includes(name);
(async () => {
for (let round = 0; round < 5; round += 1) {
await new Promise((resolve) => setImmediate(resolve));
}
const all = walk(context.screen, []);
const charts = all.filter((node) => has(node, "res-chart"));
const marks = (kind) => all.filter((node) => has(node, "res-mark") && has(node, kind)).map((node) => [Number(node.attrs["data-from"]), Number(node.attrs["data-to"])]);
console.log(JSON.stringify({
fetched,
count: all.find((node) => node.attrs["aria-label"] === "Samples").textContent,
charts: charts.map((chart) => [chart.attrs["data-panel"], chart.attrs["data-from"], chart.attrs["data-to"]]),
bands: charts.map((chart) => walk(chart, []).filter((node) => has(node, "band")).map((node) => node.attrs["data-zone"])),
lines: [...new Set(all.filter((node) => has(node, "res-line")).map((node) => `${node.attrs["data-series"]}|${node.attrs["data-kind"]}`))],
waits: marks("wait"),
asleep: marks("asleep"),
unknown: marks("unknown"),
words: all.filter((node) => has(node, "res-mark") && has(node, "unknown")).map((node) => node.children[0].children.join("")),
bars: all.filter((node) => has(node, "res-bar")).map((node) => [node.attrs["data-agent"], Number(node.attrs["data-t"])]),
legends: all.filter((node) => has(node, "res-key")).map((node) => node.children.join("")),
titles: all.filter((node) => has(node, "res-title")).map((node) => [node.attrs.title, node.attrs["aria-label"], node.children[0].attrs["data-icon"]]),
}));
})();
""".replace("@FIXTURE@", FIXTURE)
class TestResourcesModule(unittest.TestCase):
@classmethod
def setUpClass(cls):
done = subprocess.run(["node", "-e", PROBE, str(MODULE), str(T)], capture_output=True, text=True, timeout=60, check=True)
cls.data = json.loads(done.stdout)
def test_bands_mark_idle_below_thirty_and_saturated_above_ninety_percent(self):
self.assertEqual(self.data["zones"], [{"kind": "idle", "from": 0, "to": 30}, {"kind": "saturated", "from": 90, "to": 100}])
def test_disks_cpu_ram_swap_gpu_and_vram_become_percent_lines_on_one_time_axis(self):
times = [T, T + 60, T + 120, T + 180, T + 240, T + 300, T + 5000, T + 5060]
self.assertEqual(self.data["disks"], [["/", "disk", [[t, 25] for t in times]], ["/mnt", "disk", [[T + 120, 25]]]])
self.assertEqual([name for name, _, _ in self.data["host"]], ["CPU", "RAM", "Swap"])
self.assertEqual(self.data["host"][0][2], [[t, cpu] for t, cpu in zip(times, (20, 40, 45, 70, 30, 35, 10, 10), strict=True)])
gpu, vram = self.data["gpu"]
self.assertEqual((gpu[0], gpu[1], vram[0], vram[1]), ("0000:01:00.0", "util", "0000:01:00.0", "vram"))
self.assertEqual(gpu[2], [[T, None], [T + 60, None], [T + 120, 80], [T + 180, 90], [T + 240, 10], [T + 300, None], [T + 5000, None], [T + 5060, None]])
self.assertEqual(vram[2][2:5], [[T + 120, 50], [T + 180, 60], [T + 240, 10]])
def test_a_line_breaks_at_a_missing_value_and_at_a_gap_of_more_than_half_an_hour(self):
self.assertEqual(self.data["segments"], [[[T, 1]], [[T + 120, 3], [T + 180, 4]], [[T + 4000, 5]]])
def test_a_marker_spans_rows_where_jobs_waited_while_cpu_and_ram_stayed_under_half(self):
self.assertEqual(self.data["waits"], [{"from": T + 60, "to": T + 180, "peak": 3}, {"from": T + 240, "to": T + 300, "peak": 1}, {"from": T + 5000, "to": T + 5060, "peak": 1}])
def test_an_asleep_gpu_gets_its_own_spans(self):
self.assertEqual(self.data["asleep"], [{"name": "0000:01:00.0", "spans": [{"from": T, "to": T + 120, "peak": 1}, {"from": T + 5000, "to": T + 5120, "peak": 1}]}])
def test_an_awake_gpu_without_a_reading_gets_unknown_spans_apart_from_asleep_ones(self):
self.assertEqual(self.data["unknown"], [{"name": "0000:01:00.0", "spans": [{"from": T + 300, "to": T + 360, "peak": 1}]}])
def test_tokens_stack_per_hour_with_the_biggest_agent_at_the_bottom(self):
stack = self.data["stack"]
self.assertEqual((stack["agents"], stack["top"]), (["Stage", "Skills"], 500))
first, second = stack["bars"]
self.assertEqual((first["t"], first["total"]), (T, 385))
self.assertEqual([(part["agent"], part["from"], part["to"], part["models"]) for part in first["parts"]], [("Stage", 0, 10, ["opus"]), ("Skills", 10, 385, ["opus", "haiku"])])
self.assertEqual({key: first["parts"][1][key] for key in ("input", "output", "cache_read", "cache_write")}, {"input": 15, "output": 20, "cache_read": 300, "cache_write": 40})
self.assertEqual((second["t"], [(part["agent"], part["to"]) for part in second["parts"]]), (T + 3600, [("Stage", 500)]))
class TestResourcesPage(unittest.TestCase):
def test_the_view_draws_four_panels_on_one_time_axis_from_the_api_rows_alone(self):
done = subprocess.run(["node", "-e", PAGE_PROBE, str(MODULE), str(PAGE), str(T)], capture_output=True, text=True, timeout=60, check=True)
found = json.loads(done.stdout)
self.assertEqual((found["fetched"], found["count"]), (["/api/resources?from=1"], "8"))
axis = [str(T - 600), str(T + 6000)]
self.assertEqual(found["charts"], [[panel, *axis] for panel in ("disks", "host", "gpu", "tokens")])
self.assertEqual(found["bands"], [["idle", "saturated"]] * 3 + [[]])
self.assertEqual(found["lines"], ["/|disk", "/mnt|disk", "CPU|host", "RAM|host", "Swap|host", "0000:01:00.0|util", "0000:01:00.0|vram"])
self.assertEqual(found["waits"], [[T + 60, T + 180], [T + 240, T + 300], [T + 5000, T + 5060]])
self.assertEqual(found["asleep"], [[T, T + 120], [T + 5000, T + 5120]])
self.assertEqual((found["unknown"], found["words"]), ([[T + 300, T + 360]], ["0000:01:00.0 unknown: no reading"]))
self.assertEqual(found["bars"], [["Stage", T], ["Skills", T], ["Stage", T + 3600]])
self.assertEqual(found["legends"], ["/ 25 %", "/mnt 25 %", "CPU 10 %", "RAM 10 %", "Swap 0 %", "0000:01:00.0 10 %", "0000:01:00.0 VRAM 10 %", "Stage 510", "Skills 375"])
names = [("Disks", "bi-hdd-stack"), ("CPU, RAM and swap", "bi-cpu"), ("GPU and VRAM", "bi-gpu-card"), ("Tokens per agent", "bi-coin")]
self.assertEqual(found["titles"], [[name, name, icon] for name, icon in names])
class TestHubResources(HubCase):
def write(self, name: str, rows: list[dict], tail: str = "") -> None:
folder = self.kept / "resources"
folder.mkdir(exist_ok=True)
(folder / name).write_text(self.jsonl(rows) + tail, encoding="utf-8")
def fetch(self, query: str) -> tuple[int, bytes]:
status, _, body = self.get(f"/api/resources?{query}")
return status, body
def test_the_view_reads_only_the_sampler_files_within_the_window(self):
samples = [{"t": T + step, "cpu": step / 60, "ram": 1, "swap": 0, "load": 0, "disks": [], "gpus": [], "queue": {"waiting": 0, "active": 0}} for step in (0, 60, 120, 180)]
tokens = [{"t": T - 3600, "agent": "Skills", "model": "opus", "input": 1, "output": 0, "cache_read": 0, "cache_write": 0}, {"t": T + 3600, "agent": "Skills", "model": "opus", "input": 2, "output": 0, "cache_read": 0, "cache_write": 0}]
self.write("samples.jsonl", samples, "{broken\n[1]\n")
self.write("tokens.jsonl", tokens)
status, body = self.fetch(f"from={T + 60}&to={T + 120}")
self.assertEqual(status, 200, body)
self.assertEqual(json.loads(body), {"since": T + 60, "until": T + 120, "samples": samples[1:3], "tokens": []})
status, body = self.fetch(f"from={T - 1800}&to={T + 3600}")
self.assertEqual(json.loads(body)["tokens"], tokens)
self.assertEqual(json.loads(body)["samples"], samples)
def test_missing_files_answer_empty_rows_and_a_reversed_window_is_refused(self):
status, body = self.fetch("")
self.assertEqual(status, 200, body)
found = json.loads(body)
self.assertEqual((found["samples"], found["tokens"]), ([], []))
self.assertAlmostEqual(found["until"] - found["since"], 86400)
self.assertLess(abs(found["until"] - time.time()), 30)
self.assertEqual(self.fetch(f"from={T + 10}&to={T}")[0], 400)
self.assertEqual(self.fetch("from=x")[0], 400)
def test_the_page_carries_the_resources_tab_module_view_and_icons(self):
html = self.index()
self.assertIn(MODULE.read_text(encoding="utf-8").strip(), html)
self.assertIn(PAGE.read_text(encoding="utf-8").strip(), html)
self.assertIn(STYLE.read_text(encoding="utf-8").strip(), html)
for name in ("speedometer2", "hdd-stack", "cpu", "gpu-card", "coin", "hourglass-split", "moon", "question-circle"):
with self.subTest(icon=name):
self.assertIn(f'<symbol id="bi-{name}"', html)
done = subprocess.run(["node", "-e", "const N = require(process.argv[1]); console.log(JSON.stringify([N.TABS.at(-1), N.ICONS.resources, N.route('#resources')]));", str(NAV)], capture_output=True, text=True, timeout=60, check=True)
self.assertEqual(json.loads(done.stdout), [["resources", "Resources"], "bi-speedometer2", {"screen": "resources", "view": None, "redirect": None}])
if __name__ == "__main__":
unittest.main()