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

The fleet and forge-size reads name their Server-Timing phases: graph, ops, one op, sizes #4992

Merged opened this

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

Merged

scout/forge:scout/4982-the-fleet-and-forge-size-reads-name-thei → main 1 commit fork

  • Merged. scout/4982-the-fleet-and-forge-size-reads-name-thei was merged into main as e8e98c0.

Review

Sign in to review this change.
2 files changed +79 −14
src/http/api.ts +18 −14
@@ -4982,23 +4982,22 @@ export function apiRoutes(router: Router<Handler>): void {
49824982 });
49834983
49844984 router.add("GET", "/api/v1/fleet/ops", (ctx) => {
4985 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
49854986 if (!ctx.auth.user) return unauthorized();
49864987 const q = ctx.url.searchParams;
49874988 const limit = pageLimit(q.has("limit") ? Number(q.get("limit")) : undefined);
4988 const ops = listFleetOps(
4989 ctx.app,
4990 ctx.auth.user,
4991 limit,
4992 q.get("cursor") ?? undefined,
4989 const ops = timePhase(ctx.request, "ops", () =>
4990 listFleetOps(ctx.app, ctx.auth.user!, limit, q.get("cursor") ?? undefined),
49934991 );
49944992 if (!ops.ok) return respondFleet(ops.error);
4995 return json(paged(ops.value, limit, (op) => op.id));
4993 return timePhase(ctx.request, "render", () => json(paged(ops.value, limit, (op) => op.id)));
49964994 });
49974995
49984996 router.add("GET", "/api/v1/fleet/ops/:id", (ctx) => {
4999 const op = getFleetOp(ctx.app, ctx.auth.user, ctx.params.id!);
4997 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
4998 const op = timePhase(ctx.request, "op", () => getFleetOp(ctx.app, ctx.auth.user, ctx.params.id!));
50004999 if (!op.ok) return respondFleet(op.error);
5001 return json(op.value);
5000 return timePhase(ctx.request, "render", () => json(op.value));
50025001 });
50035002
50045003 router.add("POST", "/api/v1/fleet/ops/:id/undo", async (ctx) => {
@@ -5198,11 +5197,14 @@ export function apiRoutes(router: Router<Handler>): void {
51985197
51995198 // What small, medium and large mean: public, and cacheable by ETag.
52005199 router.add("GET", "/api/v1/forges/sizes", (ctx) => {
5201 const text = JSON.stringify(sizesView());
5202 const etag = weakEtag(text);
5203 const headers = { etag, "cache-control": "public, max-age=300" };
5204 if (holds(ctx.request, etag)) return new Response(null, { status: 304, headers });
5205 return new Response(text, { headers: { "content-type": "application/json", ...headers } });
5200 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
5201 const text = timePhase(ctx.request, "sizes", () => JSON.stringify(sizesView()));
5202 return timePhase(ctx.request, "render", () => {
5203 const etag = weakEtag(text);
5204 const headers = { etag, "cache-control": "public, max-age=300" };
5205 if (holds(ctx.request, etag)) return new Response(null, { status: 304, headers });
5206 return new Response(text, { headers: { "content-type": "application/json", ...headers } });
5207 });
52065208 });
52075209
52085210 // A forge's children (docs/SPAWN.md): an admin's, planned before made.
@@ -5861,10 +5863,12 @@ export function apiRoutes(router: Router<Handler>): void {
58615863 // per repository by UTC hour (24 h) and day (14 d), for `?repo=owner/name`
58625864 // repeated, or every repository the caller may write that spent lately.
58635865 router.add("GET", "/api/v1/fleet/graph", (ctx) => {
5866 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
58645867 if (!ctx.auth.user) return unauthorized();
58655868 const asked = fleetSelection(ctx.url.searchParams.getAll("repo"));
58665869 if (!asked.ok) return apiError("invalid_selection", 422, asked.error);
5867 return json(readFleetGraph(ctx.app, ctx.auth.user, asked.value));
5870 const graph = timePhase(ctx.request, "graph", () => readFleetGraph(ctx.app, ctx.auth.user!, asked.value));
5871 return timePhase(ctx.request, "render", () => json(graph));
58685872 });
58695873
58705874 // The address an agent tries first.
test/fleet-timing.test.ts added +61
@@ -0,0 +1,61 @@
1 /** The fleet and forge-size reads name their phases in Server-Timing. */
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 { serve, type Servers } from "../src/http/server.ts";
8 import { seedAdmin } from "./helpers/admin.ts";
9
10 let dataDir = "";
11 let app: App, servers: Servers, bearer = "";
12
13 beforeAll(async () => {
14 dataDir = await mkdtemp(join(tmpdir(), "forge-ftime-"));
15 app = createApp({ dataDir, port: 0 });
16 servers = await serve(app);
17 await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
18 const minted = await fetch(`${servers.url}/api/v1/tokens`, {
19 method: "POST",
20 headers: { "content-type": "application/json" },
21 body: JSON.stringify({ adminConfirm: true, username: "alice", password: "correct-horse-battery", name: "cli", scope: "admin" }),
22 });
23 bearer = ((await minted.json()) as { token: string }).token;
24 });
25
26 afterAll(async () => {
27 servers.stop();
28 app.close();
29 await rm(dataDir, { recursive: true, force: true });
30 });
31
32 async function phases(path: string): Promise<{ status: number; names: string[] }> {
33 const res = await fetch(`${servers.url}${path}`, { headers: { authorization: `Bearer ${bearer}` } });
34 await res.arrayBuffer();
35 const names = (res.headers.get("server-timing") ?? "").split(", ").filter(Boolean).filter((p) => !p.startsWith("app;")).map((p) => p.split(";")[0]!);
36 return { status: res.status, names };
37 }
38
39 test("the fleet graph names session, graph, render", async () => {
40 const r = await phases("/api/v1/fleet/graph");
41 expect(r.status).toBe(200);
42 expect(r.names).toEqual(["session", "graph", "render"]);
43 });
44
45 test("the fleet ops list names session, ops, render", async () => {
46 const r = await phases("/api/v1/fleet/ops");
47 expect(r.status).toBe(200);
48 expect(r.names).toEqual(["session", "ops", "render"]);
49 });
50
51 test("one fleet op names session, op, render; a missing one still names session", async () => {
52 const r = await phases("/api/v1/fleet/ops/nope");
53 expect(r.status).toBeGreaterThanOrEqual(400);
54 expect(r.names.slice(0, 2)).toEqual(["session", "op"]);
55 });
56
57 test("the forge sizes name session, sizes, render", async () => {
58 const r = await phases("/api/v1/forges/sizes");
59 expect(r.status).toBe(200);
60 expect(r.names).toEqual(["session", "sizes", "render"]);
61 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs