Metrics on a thread and port of their own, from memory alone (#194)

/metrics shared the web server's threads, and answered after a token
lookup and three counting queries, so a pool run dry, one of the things it
is there to show, would have stalled it, and a slow scrape held a web
thread. Now [web] metrics_bind starts a listener on its own OS thread with
a current-thread runtime, reading only what is in memory: pool counts,
channel capacity, the daemon runtime's metrics through its handle, /proc.
No sign-in, as exporters have none; publish the port to the LAN only. The
feed, pending and downloaded counts stay with the healthcheck's log lines,
which the dashboard already reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 15:04:51 +00:00
parent 9fc1e7e248
commit 973eeb67c7
8 changed files with 138 additions and 62 deletions

View File

@@ -1390,18 +1390,6 @@ test('/api/status gives a dashboard the counts, with the shared token as a cooki
expect(none.status()).toBe(401);
});
test('/metrics gives Prometheus the daemon\'s state, to someone signed in', async ({ page }) => {
const r = await page.request.get('/metrics', { headers: { cookie: `ipx_token=${TOKEN}` } });
expect(r.status()).toBe(200);
const text = await r.text();
// Every sample a name, optional labels and a number, as Prometheus parses them (#194).
for (const line of text.split('\n').filter(l => l && !l.startsWith('#')))
expect(line).toMatch(/^ipx_[a-z_]+(\{[^}]*\})? -?[0-9.e+]+$/);
for (const name of ['ipx_db_connections{state="in_use"}', 'ipx_db_connections_max', 'ipx_worker_queue', 'ipx_feeds', 'ipx_runtime_tasks', 'ipx_process_resident_bytes'])
expect(text).toContain(name);
expect((await page.request.get('/metrics', { headers: { cookie: '' } })).status()).toBe(401);
});
test('the favicon is the logo, square, from both pages', async ({ page }) => {
await page.evaluate(() => setTheme('modern', 'light'));
await expect(page.locator('#favicon')).toHaveAttribute('href', /^\/favicon\.png\?v=[0-9a-f]{12}$/);