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

Explore answers under 50 ms and says so in Server-Timing, measured by a test #10955

Merged opened this

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

Merged

torque/forge:torque/10946-explore-answers-under-50-ms-and-says-so → main 7 commits fork 2 threads · 2 unresolved

  • Merged. torque/10946-explore-answers-under-50-ms-and-says-so was merged into main as 095a7d8.

Review

Sign in to review this change.
6 files changed +101 −8
src/perf/budgets.ts +5
@@ -12,6 +12,8 @@ export const BUDGETS: readonly Budget[] = [
1212 { pattern: "/", words: "home p95 under 800 ms warm", p95Ms: 800 },
1313 { pattern: "/boards", words: "boards p95 under 800 ms warm", p95Ms: 800 },
1414 { pattern: "/explore", words: "explore p95 under 800 ms warm", p95Ms: 800 },
15 // The `read` phase of /explore's Server-Timing (#10946): a phase of that route, not a route; its p95 is the same read's.
16 { pattern: "/explore#read", words: "the ranked explore read over 200 repositories p95 under 50 ms", p95Ms: 50 },
1517 { pattern: "/api/v1/health", words: "health p95 under 100 ms", p95Ms: 100 },
1618 { pattern: "/:owner/:name", words: "repo page p95 under 800 ms warm", p95Ms: 800 },
1719 { pattern: "/:owner/:name/*", words: "repo sub-page p95 under 1000 ms warm", p95Ms: 1000 },
@@ -45,3 +47,6 @@ export function gitBudgetFor(key: string): Budget {
4547
4648 /** What a real visitor's page is held to (#10536): the browser's time to first byte at p95, and its largest paint at p95. */
4749 export const PAGE_BUDGET = { serverP95Ms: 150, paintP95Ms: 1000 } as const;
50
outdated

Not registered: BUDGETS already has {pattern:"/explore", p95Ms:800}; this duplicate is read by nobody but the test. Add the entry to BUDGETS with a pattern that does not collide, and import it from there. Also 'p95Ms' for a cold single read is a misnomer.

51 /** The ranked read behind /explore, /api/v1/explore and the MCP `explore` tool, as registered in BUDGETS. */
52 export const EXPLORE_READ_BUDGET: Budget = budgetFor("/explore#read")!;
src/web/studio/explore.ts +1 −1
@@ -146,7 +146,7 @@ export async function explorePage(ctx: Ctx): Promise<Response> {
146146 const suggestion = exploreSuggestion(line);
147147 const ranking = performance.now();
148148 const rows = await explore(ctx.app, ctx.auth.user, query);
149 notePhase(ctx.request, "rank", performance.now() - ranking);
149 notePhase(ctx.request, "read", performance.now() - ranking);
150150 const rendering = performance.now();
151151 const kinds = [
152152 { label: "All", kind: "all" as const },
test/explore-budget.test.ts added +84
@@ -0,0 +1,84 @@
1 /** The ranked read behind explore answers under its budget over 200 repositories, and never shares a private row (#10946). */
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 { explore, parseExploreQuery } from "../src/actions/explore.ts";
8 import { createRepo } from "../src/actions/repos.ts";
9 import { createApp as insertApp } from "../src/core/apps.ts";
10 import { createUser, type User } from "../src/core/users.ts";
11 import { BUDGETS, budgetFor } from "../src/perf/budgets.ts";
12 import { quietLogs } from "./helpers/globals.ts";
13
14 quietLogs();
15
16 let dir: string;
17 let app: App;
18 let owner: User;
19 let stranger: User;
20 const all = parseExploreQuery("");
21 const budget = budgetFor("/explore#read")!;
22 /** The best of a few reads, so one stall on a loaded machine is not a finding; a slow read is slow every time. */
23 async function best(read: () => Promise<unknown>, runs = 3) {
24 let low = Infinity;
25 for (let i = 0; i < runs; i++) {
26 const t = performance.now();
27 await read();
28 low = Math.min(low, performance.now() - t);
29 }
30 return low;
31 }
32
33 async function person(username: string, admin: boolean) {
34 const made = await createUser(app.db, {
35 origin: "seed", trusted: true, username, email: `${username}@forge.local`,
36 password: "a-good-passphrase", adminIfFirst: admin,
37 });
38 if (!made.ok) throw new Error(String(made.error));
39 return made.value;
40 }
41
42 beforeAll(async () => {
43 dir = await mkdtemp(join(tmpdir(), "forge-explore-budget-"));
44 app = createApp({ dataDir: dir, port: 0 });
45 owner = await person("olive", true);
46 stranger = await person("pablo", false);
47 for (let i = 0; i < 200; i++) {
48 const access = i % 4 === 0 ? "private" : "public";
49 const repo = await createRepo(app, owner, { name: `app-${i}`, visibility: access });
50 if (!repo.ok) throw new Error(String(repo.error));
51 const made = insertApp(app.db, { repoId: repo.value.id, slug: `olive-app-${i}`, access });
outdated

Single wall-clock sample at the full 50 ms is a flake risk on a loaded check machine; take the best of a few reads or leave headroom.

52 if (!made.ok) throw new Error(made.error);
53 }
54 }, 300_000);
55 afterAll(async () => {
56 app.close();
57 await rm(dir, { recursive: true, force: true });
58 });
59
60 test("the ranked read over 200 repositories is under the registered budget", async () => {
61 expect(BUDGETS).toContain(budget);
62 expect(budget.p95Ms).toBe(50);
63 const rows = await explore(app, stranger, all);
64 expect(rows.length).toBeGreaterThan(40);
65 // Different words rank afresh; the best of three of them is the read without a stall.
66 const ms = await best(() => explore(app, stranger, parseExploreQuery(`kind:app sort:forks app-${Math.random()}`)), 3);
67 expect(ms).toBeLessThan(budget.p95Ms);
68 });
69
70 test("a read is under the budget for every sort", async () => {
71 for (const sort of ["score", "forks", "ships", "likes", "use", "fresh", "name"]) {
72 const q = parseExploreQuery(`kind:app sort:${sort}`);
73 expect(await best(() => explore(app, stranger, q))).toBeLessThan(budget.p95Ms);
74 }
75 });
76
77 test("a private app is not shown to another viewer after the owner's read is warm", async () => {
78 await explore(app, owner, all);
79 const mine = (await explore(app, owner, all)).map((r) => r.name);
80 const theirs = (await explore(app, stranger, all)).map((r) => r.name);
81 expect(mine).toContain("olive/app-0");
82 expect(theirs).not.toContain("olive/app-0");
83 expect((await explore(app, undefined, all)).map((r) => r.name)).not.toContain("olive/app-0");
84 });
test/explore-timing.test.ts +3 −3
@@ -1,4 +1,4 @@
1 /** `/explore`'s Server-Timing names its phases: session, rank, render (#3230). */
1 /** `/explore`'s Server-Timing names its phases: session, read, render (#3230). */
22 import { afterAll, beforeAll, expect, test } from "bun:test";
33 import { snappyFixture, type SnappyFixture } from "../bench/snappy.ts";
44
@@ -12,12 +12,12 @@ afterAll(async () => {
1212 });
1313
1414 for (const path of ["/explore", "/explore?q=" + encodeURIComponent("kind:app sort:forks")]) {
15 test(`GET ${path} names session, rank and render beside the route total`, async () => {
15 test(`GET ${path} names session, read and render beside the route total`, async () => {
1616 const res = await fetch(`${f.base}${path}`);
1717 await res.arrayBuffer();
1818 expect(res.status).toBe(200);
1919 const parts = (res.headers.get("server-timing") ?? "").split(", ");
20 for (const name of ["app", "session", "rank", "render"])
20 for (const name of ["app", "session", "read", "render"])
2121 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
2222 });
2323 }
test/mcp-system-stalls.test.ts +2 −2
@@ -34,8 +34,8 @@ afterAll(async () => {
3434 });
3535
3636 test("a stall under a named job is read through system, at most 10, most ms first", async () => {
37 // Let every request other tests in this process finished fall outside the 300 ms window, so only the job ran across it.
38 await Bun.sleep(320);
37 // A stall is blamed on a request first: let any request an earlier file in this process ran fall out of the stall's window.
38 await Bun.sleep(400);
3939 during("test-job-10907", () => 1);
4040 loopWatch.stalled(300, performance.now());
4141 const res = await fetch(`${servers.url}/mcp`, {
test/mcp-write-scope.test.ts +6 −2
@@ -42,6 +42,11 @@ const ADMIN_ARGS: Record<string, Record<string, unknown>> = {
4242 rollback: { sha: "a".repeat(40), dryRun: false },
4343 };
4444
45 /** Valid arguments for a tool whose write is one call among reads and plans (`readsWhen`): the generic arguments would only read. */
46 const WRITE_ARGS: Record<string, Record<string, unknown>> = {
47 flow: { kind: "drift", action: "update", repo: "alice/game", pull: 1 },
48 };
49
4550 /** What `visible()` answers for a tool the token's scope does not open. */
4651 const notListed = (body: any): boolean =>
4752 body.error?.code === -32602 && String(body.error.message).startsWith("no tool named");
@@ -99,8 +104,7 @@ describe("a token that may only read cannot write through any tool", () => {
99104 /** Minimal arguments that make the call a real write, not a plan or a list. */
100105 const writing = (t: ReturnType<typeof toolRegistry>[number]) => {
101106 if (t.name in ADMIN_ARGS) return ADMIN_ARGS[t.name]!;
102 // `flow` reads unless it is asked to update a drifting pull (#10959).
103 if (t.name === "flow") return { action: "update", kind: "drift", repo: "alice/game", pull: 1 };
107 if (t.name in WRITE_ARGS) return WRITE_ARGS[t.name]!;
104108 const args: Record<string, unknown> = { dryRun: false, action: "x" };
105109 if (t.readsWithout !== undefined) args[[t.readsWithout].flat()[0]!] = "x";
106110 return args;

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs