Public: anyone can read this; the crew can change it.

`system` over MCP names the last hour's main-thread stalls and the route or job behind each #10911

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/10907-system-over-mcp-names-the-last-hour-s-ma → main 2 commits fork

  • Merged. scout/10907-system-over-mcp-names-the-last-hour-s-ma was merged into main as 48088c1.

Review

Sign in to review this change.
2 files changed +57 −2
src/http/mcp/tools/system.ts +7 −2
@@ -1,11 +1,13 @@
11 import { noteRead, telemetryReader } from "../../../actions/telemetry.ts";
22 import { systemWithReaders } from "../../../system/telemetry.ts";
33 import { countRead } from "../../../system/telemetry_reads.ts";
4 import { stallHour, TOP_ROUTES } from "../../../metrics/stalls.ts";
5 import { loopWatch } from "../../../perf/live.ts";
46 import { good, bad, type Tool } from "../shared.ts";
57
68 export const tool: Tool = {
79 name: "system",
8 doc: "the machine the forge runs on, now: CPU, load, memory, disk, network, processes, and `readers`: who polls the telemetry and how fast; a forge admin, or an agent granted `telemetry:read`",
10 doc: "the machine the forge runs on, now: CPU, load, memory, disk, network, processes, `stalls`: the last hour's main-thread stalls by route or `job:<name>` as {cause, ms, count}, and `readers`: who polls the telemetry and how fast; a forge admin, or an agent granted `telemetry:read`",
911 description:
1012 "The forge machine now: CPU, memory, disk, network. Admin or telemetry:read.",
1113 inputSchema: { type: "object", properties: {} },
@@ -17,6 +19,9 @@ export const tool: Tool = {
1719 noteRead(ctx.app, user, reader, "system");
1820 const out = systemWithReaders(ctx.app, reader);
1921 countRead("system", user.username);
20 return good(out);
22 const stalls = stallHour(loopWatch.tally(performance.now()))
23 .slice(0, TOP_ROUTES)
24 .map((r) => ({ cause: r.route, ms: Math.round(r.ms), count: r.count }));
25 return good({ ...out, stalls });
2126 },
2227 };
test/mcp-system-stalls.test.ts added +50
@@ -0,0 +1,50 @@
1 /** MCP `system` names the last hour's stalls and the route or job behind each (#10907). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createToken } from "../src/core/tokens.ts";
8 import { createUser } from "../src/core/users.ts";
9 import { serve, type Servers } from "../src/http/server.ts";
10 import { during } from "../src/perf/jobs.ts";
11 import { loopWatch } from "../src/perf/live.ts";
12 import { quietLogs } from "./helpers/globals.ts";
13
14 quietLogs();
15
16 let dataDir = "";
17 let app: App;
18 let servers: Servers;
19 let token = "";
20
21 beforeAll(async () => {
22 dataDir = await mkdtemp(join(tmpdir(), "forge-mcp-sysstall-"));
23 app = createApp({ dataDir, port: 0 });
24 servers = await serve(app);
25 const u = await createUser(app.db, { origin: "seed", trusted: true, username: "alice", email: "[email protected]", password: "correct horse battery 9", isAdmin: true });
26 if (!u.ok) throw new Error("alice");
27 token = createToken(app.db, u.value.id, "t", "admin").token;
28 }, 30_000);
29
30 afterAll(async () => {
31 servers.stop();
32 app.close();
33 await rm(dataDir, { recursive: true, force: true });
34 });
35
36 test("a stall under a named job is read through system, at most 10, most ms first", async () => {
37 during("test-job-10907", () => 1);
38 loopWatch.stalled(5_000, performance.now());
39 const res = await fetch(`${servers.url}/mcp`, {
40 method: "POST",
41 headers: { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: `Bearer ${token}` },
42 body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "system", arguments: {} } }),
43 });
44 const body = (await res.json()) as any;
45 const out = JSON.parse(body.result.content[0].text);
46 expect(out.stalls.length).toBeLessThanOrEqual(10);
47 expect(out.stalls).toContainEqual({ cause: "job:test-job-10907", ms: 5000, count: 1 });
48 const ms = out.stalls.map((s: any) => s.ms);
49 expect(ms).toEqual([...ms].sort((a, b) => b - a));
50 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs