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

The family page's Server-Timing names its phases: session, each read, render #3490

Merged opened this

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

Merged

scout/forge:scout/3458-the-family-page-s-server-timing-names-it → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3458-the-family-page-s-server-timing-names-it was merged into main as a3830ff.

Review

Sign in to review this change.
2 files changed +62 −2
src/web/pages/family.ts +8 −2
@@ -7,6 +7,7 @@
77 * reads "quiet": a fact about the clock, not a fault.
88 */
99
10 import { notePhase, takeSessionTime, timePhaseAsync } from "../../core/route_timing.ts";
1011 import type { Ctx } from "../../http/context.ts";
1112 import type { ChildAppsSummary } from "../../spawn/child-apps.ts";
1213 import { familyView, followLine, type FamilyChild } from "../../spawn/family.ts";
@@ -93,7 +94,10 @@ export async function familyPage(
9394 ): Promise<Response> {
9495 const app = ctx.app;
9596 const owns = ctx.auth.user?.isAdmin === true;
96 const view = await familyView(app, owns, appsOf);
97 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
98 // One read: the parent, and for an admin the children's standings.
99 const view = await timePhaseAsync(ctx.request, "family", () => familyView(app, owns, appsOf));

A single phase named family is honest for one read. If the children standings are the slow part for an admin, this is the line to split once familyView exposes them separately.

100 const rendering = performance.now();
97101 const parent = view.parent;
98102 const kids = { items: view.children, more: view.more };
99103
@@ -120,7 +124,7 @@ export async function familyPage(
120124 };
121125
122126 const alone = parent === null && kids.items.length === 0;
123 return page({
127 const response = page({
124128 ctx,
125129 title: "Family",
126130 body: html`<div class="narrow-wide">
@@ -145,4 +149,6 @@ export async function familyPage(
145149 )}
146150 </div>`,
147151 });
152 notePhase(ctx.request, "render", performance.now() - rendering);
153 return response;
148154 }
test/family-timing.test.ts added +54
@@ -0,0 +1,54 @@
1 /** `/family` names session, family and render in Server-Timing (#3458). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createUser } from "../src/core/users.ts";
8 import { serve, type Servers } from "../src/http/server.ts";
9 import { proveAll } from "./helpers/verified.ts";
10 import { quietLogs } from "./helpers/globals.ts";
11
12 quietLogs();
13
14 let root = "";
15 let app: App;
16 let servers: Servers;
17
18 beforeAll(async () => {
19 root = mkdtempSync(join(tmpdir(), "forge-family-timing-"));
20 app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" });
21 const u = await createUser(app.db, {
22 origin: "seed",
23 trusted: true,
24 username: "alice",
25 email: "[email protected]",
26 password: "a-good-passphrase",
27 adminIfFirst: true,
28 });
29 if (!u.ok) throw new Error(u.error);
30 proveAll(app);
31 servers = await serve(app);
32 });
33 afterAll(() => {
34 servers.stop();
35 app.close();
36 rmSync(root, { recursive: true, force: true });
37 });
38
39 test("GET /family names its phases", async () => {
40 const signin = await fetch(`${servers.url}/signin`, {
41 method: "POST",
42 headers: { origin: servers.url },
43 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
44 redirect: "manual",
45 });
46 const cookie = (signin.headers.get("set-cookie") ?? "").split(";")[0]!;
47 const res = await fetch(`${servers.url}/family`, { headers: { cookie } });
48 const body = await res.text();
49 expect(res.status).toBe(200);
50 expect(body).toContain("Family");
51 const parts = (res.headers.get("server-timing") ?? "").split(", ");
52 for (const name of ["app", "session", "family", "render"])
53 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
54 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs