From 5ddd99450508e457c94775010c1f1d54c03743ca Mon Sep 17 00:00:00 2001 From: T-Crypt Date: Fri, 16 Oct 2026 17:43:30 -0600 Subject: [PATCH 1/3] docs(readme): correct /api/requests params and the hostinfo skip note The API reads `since` (epoch seconds) and `limit` (default 1000); it does not take a `range` param, so a range request is silently ignored. The test_hostinfo platform gates skip three Windows-only tests on Linux, not two. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4352c5a..b58a22a 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ The collector keeps bounded in-memory buffers: 60 minutes of GPU, host and KPI s | `GET /api/snapshot` | one-shot full JSON (state plus history) | | `GET /api/stream` | SSE: 1 Hz `tick` plus push events | | `GET /api/history?range=…` | rollup rows, minute and hour | -| `GET /api/requests?range=…` | raw request rows, capped at 1000 | +| `GET /api/requests?since=…&limit=…` | raw request rows, newest first, capped at 1000 | | `GET /api/spans?range=…` | model load spans | | `GET /api/leaderboard?range=…` | per-model statistics | | `GET /api/storage` | database size, retention, projections | @@ -228,7 +228,7 @@ python3 -m http.server 8792 # → http://localho The UI files are ES modules, so `node --check` does not apply to them. The smoke scripts import and run them, which is the parse-and-run check. -The two `test_hostinfo.py` skips are platform gates: the `/proc` backend runs on Linux and the Win32 ctypes backend runs only on Windows. On a Linux box the Windows path is read, not executed, so its numbers stay unverified until you run the suite on Windows. +The `test_hostinfo.py` platform gates skip the Windows ctypes backend on Linux and the `/proc` backend on Windows. On a Linux box the Windows path is read, not executed, so its numbers stay unverified until you run the suite on Windows. ## Origin From edf948cb651037cf761488aa39210277acc4eb85 Mon Sep 17 00:00:00 2001 From: T-Crypt Date: Fri, 16 Oct 2026 17:43:30 -0600 Subject: [PATCH 2/3] test(hostinfo): accept a rounded-to-zero RSS on Linux rss_gb is rounded to 2 dp in GB, so a tiny launcher/wrapper process that matches an engine name reports 0.0 and tripped the `> 0` assertion on the GPU box. Allow >= 0 for the matched processes in both Linux process tests. --- collector/test_hostinfo.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/collector/test_hostinfo.py b/collector/test_hostinfo.py index 498283a..0d36124 100755 --- a/collector/test_hostinfo.py +++ b/collector/test_hostinfo.py @@ -89,7 +89,9 @@ def test_processes_shape(self): self.assertEqual(set(p), {"name", "pid", "rss_gb", "cmd"}) self.assertIn(p["name"], ("llama-server", "ninfer-serve", "strata")) self.assertGreater(p["pid"], 0) - self.assertTrue(p["rss_gb"] is None or p["rss_gb"] > 0) + # rss_gb is rounded to 2 dp in GB, so a tiny launcher/wrapper + # process legitimately reports 0.0 + self.assertTrue(p["rss_gb"] is None or p["rss_gb"] >= 0) self.assertLessEqual(len(p["cmd"]), 120) def test_processes_matches_fake_engine(self): @@ -117,7 +119,7 @@ def test_processes_matches_fake_engine(self): p = hit[0] self.assertEqual(p["name"], "llama-server") self.assertIn("llama-server", p["cmd"]) - self.assertTrue(p["rss_gb"] is None or p["rss_gb"] > 0) + self.assertTrue(p["rss_gb"] is None or p["rss_gb"] >= 0) finally: proc.kill() proc.wait() From a0869a8f83aeb2eceb675a387770045ef5091d4c Mon Sep 17 00:00:00 2001 From: T-Crypt Date: Fri, 16 Oct 2026 17:43:30 -0600 Subject: [PATCH 3/3] test(speculum): cover the /api/requests query contract Pin the documented params: default since=0/limit=1000, explicit since/limit pass-through, and the 400 for a non-integer value or limit < 1. --- collector/test_speculum.py | 59 +++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/collector/test_speculum.py b/collector/test_speculum.py index 8426282..a54a0b6 100755 --- a/collector/test_speculum.py +++ b/collector/test_speculum.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Tests for KPI model attribution: engine_model_name, and the tps_models field -kpi_pass emits so the UI can name the model behind the decode rate.""" +kpi_pass emits so the UI can name the model behind the decode rate. Also the +/api/requests query contract (`since`, `limit`, and its 400s).""" import unittest @@ -101,5 +102,61 @@ def test_host_shorthand(self): "http://gpu.lan:8080") +class _FakeHist: + """Records the (since, limit) the API hands to the history layer.""" + + def __init__(self): + self.calls = [] + + def requests(self, since, limit=1000): + self.calls.append((since, limit)) + return [] + + +class _JsonRecorder(speculum.Handler): + """A Handler with its socket stripped out: records what _json would send, + so the request-API query validation runs without a real server.""" + + def __init__(self): + self.responses = [] + + def _json(self, obj, code=200): + self.responses.append((code, obj)) + + +class TestRequestsApiContract(unittest.TestCase): + """GET /api/requests reads `since` (epoch seconds) and `limit`; the README + documents those two params. History.requests then clamps limit to 1000.""" + + def setUp(self): + self._real_hist = speculum.HIST + self.fake = _FakeHist() + speculum.HIST = self.fake + + def tearDown(self): + speculum.HIST = self._real_hist + + def test_defaults_since_zero_and_limit_1000(self): + h = _JsonRecorder() + h._requests_api("") + self.assertEqual(self.fake.calls, [(0, 1000)]) + self.assertEqual(h.responses[-1][0], 200) + + def test_since_and_limit_are_passed_through(self): + h = _JsonRecorder() + h._requests_api("since=1700000000&limit=50") + self.assertEqual(self.fake.calls, [(1700000000, 50)]) + self.assertEqual(h.responses[-1][0], 200) + + def test_non_integer_or_zero_limit_is_rejected(self): + for query in ("since=abc", "limit=x", "limit=0"): + with self.subTest(query=query): + h = _JsonRecorder() + h._requests_api(query) + self.assertEqual(h.responses[-1], (400, { + "error": "since and limit must be integers"})) + self.assertEqual(self.fake.calls, []) # nothing was queried + + if __name__ == "__main__": unittest.main()