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

A board's Server-Timing names its phases: session, board, grid, render #3252

Merged opened this

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

Merged

scout/forge:scout/3250-a-board-s-server-timing-names-its-phases → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3250-a-board-s-server-timing-names-its-phases was merged into main as 499eb92.

Review

Sign in to review this change.
2 files changed +66 −3
src/web/pages/boards-view.ts +9 −3
@@ -13,6 +13,7 @@ import { CHART_NAMES } from "../../boards/chart.ts";
1313 import { MAX_H, MAX_PANELS, MAX_TITLE, MAX_W, RANGES, type Board, type Panel } from "../../boards/store.ts";
1414 import type { App } from "../../app.ts";
1515 import type { Ctx } from "../../http/context.ts";
16 import { notePhase, takeSessionTime, timePhase, timePhaseAsync } from "../../core/route_timing.ts";
1617 import { liveRegionsJs } from "../client.ts";
1718 import { notFoundPage } from "../chrome.ts";
1819 import { html, raw, when, type Html } from "../html.ts";
@@ -177,18 +178,23 @@ function manage(slug: string, board: Board, adding: Html, pins: ReturnType<typeo
177178 // GET /boards/:slug
178179 export async function boardShow(ctx: Ctx): Promise<Response> {
179180 const slug = ctx.params.slug ?? "";
180 const shown = await showBoard(ctx.app, ctx.auth.user, slug);
181 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
182 const shown = await timePhaseAsync(ctx.request, "board", () => showBoard(ctx.app, ctx.auth.user, slug));
181183 if (!shown.ok) return notFoundPage(ctx);
182184 const board = shown.value;
183 return page({
185 const panels = timePhase(ctx.request, "grid", () => grid(ctx.app, slug, board));
186 const rendering = performance.now();
187 const response = page({
184188 ctx,
185189 title: board.title,
186190 body: html`<div class="page-head"><h1>${board.title}</h1>
187191 ${btn("All boards", { href: "/boards", variant: "ghost" })}</div>
188 ${grid(ctx.app, slug, board)}
192 ${panels}
189193 ${manage(slug, board, queryFields(readParams(ctx.url.searchParams)), listPins(ctx.app.db, ctx.auth.user?.id ?? ""))}`,
190194 scriptJs: liveRegionsJs,
191195 });

Render timing measures only page(); that matches the sibling pulls, but note it excludes serialisation. Fine as is.

196 notePhase(ctx.request, "render", performance.now() - rendering);
197 return response;
192198 }
193199
194200 // GET /boards/:slug/live
test/board-timing.test.ts added +57
@@ -0,0 +1,57 @@
1 /** A board's Server-Timing names its phases: session, board, grid, render (#3250). */
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 { writeBoard } from "../src/actions/boards.ts";
7 import { createApp } from "../src/app.ts";
8 import { createToken } from "../src/core/tokens.ts";
9 import { createUser } from "../src/core/users.ts";
10 import { serve } from "../src/http/server.ts";
11
12 let dir = "";
13 let app: ReturnType<typeof createApp>;
14 let servers: Awaited<ReturnType<typeof serve>>;
15 let bearer = "";
16
17 beforeAll(async () => {
18 dir = await mkdtemp(join(tmpdir(), "forge-board-timing-"));
19 app = createApp({ dataDir: dir, port: 0 });
20 servers = await serve(app);
21 const made = await createUser(app.db, {
22 origin: "seed", trusted: true, username: "alice", email: "[email protected]",
23 password: "a-good-passphrase", adminIfFirst: true,
24 });
25 if (!made.ok) throw new Error(made.error);
26 bearer = createToken(app.db, made.value.id, "t", "admin").token;
27 const w = await writeBoard(app, made.value, "ops", {
28 title: "Ops", range: "all", refresh: "off",
29 panels: [{ title: "Runs", chart: "line", query: { source: "runs", aggregate: "count", groupBy: "kind" }, w: 1, h: 1 }],
30 });
31 if (!w.ok) throw new Error(JSON.stringify(w.error));
32 });
33
34 afterAll(async () => {
35 servers.stop();
36 app.close();
37 await rm(dir, { recursive: true, force: true });
38 });
39
40 const get = (path: string) => fetch(servers.url + path, { headers: { authorization: `Bearer ${bearer}` } });
41
42 test("a board names session, board, grid and render once each", async () => {
43 const res = await get("/boards/ops");
44 expect(res.status).toBe(200);
45 expect(await res.text()).toContain("Runs");
46 const parts = (res.headers.get("server-timing") ?? "").split(", ");
47 const names = parts.map((p) => p.split(";")[0]!);
48 for (const n of ["session", "board", "grid", "render"]) {
49 expect(names.filter((x) => x === n).length).toBe(1);
50 const part = parts.find((p) => p.startsWith(`${n};`))!;
51 expect(Number(/dur=([\d.]+)/.exec(part)?.[1])).not.toBeNaN();
52 }
53 });
54
55 test("a missing board is still a 404", async () => {
56 expect((await get("/boards/nope")).status).toBe(404);
57 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs