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

The shelf's stretches show in Server-Timing on /, /apps and a person's page #6264

Merged opened this

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

Merged

scout/forge:scout/6178-the-shelf-s-stretches-show-in-server-tim → main 3 commits fork 2 threads · 2 unresolved

  • Merged. scout/6178-the-shelf-s-stretches-show-in-server-tim was merged into main as 9784eca.

Review

Sign in to review this change.
6 files changed +83 −10
src/web/pages/org.ts +1 −1
@@ -309,7 +309,7 @@ export async function ownerProfile(ctx: Ctx): Promise<Response> {
309309 if (!owner || hiddenFrom(owner, ctx.auth.user)) return notFoundPage(ctx);
310310
311311 const isOrg = owner.kind === "org";
312 const access = timePhase(ctx.request, "owner", () =>
312 const access = timePhase(ctx.request, "access", () =>

This fixes the duplicate owner name in one header. The new test's Set size check pins it.

313313 isOrg ? orgAccess(ctx.app.db, owner.id, ctx.auth.user) : "none",
314314 );
315315 const canManage = isOrg && accessAllows(access, "admin");
src/web/studio/home.ts +3 −4
@@ -504,10 +504,9 @@ function frontControls(ctx: Ctx, front: FrontPage): Html {
504504 export function studioDoor(ctx: Ctx): Response {
505505 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
506506 let tiles: Html[] = [];
507 const front = timePhase(ctx.request, "shelf", () => {
508 const found = frontPage(ctx.app);
509 tiles = found.shelf.map(tile);
510 return found;
507 const front = timePhase(ctx.request, "shelf", () => frontPage(ctx.app));
508 timePhase(ctx.request, "tiles", () => {
509 tiles = front.shelf.map(tile);
511510 });
512511 // Render is what is left of the page once the shelf read is taken out.
513512 const rendering = performance.now();
src/web/studio/shelf.ts +1 −1
@@ -182,7 +182,7 @@ export async function shelfRows(
182182 let mark = performance.now();
183183 const lap = (name: string): void => {
184184 const now = performance.now();
185 notePhase(ctx.request, name, now - mark);
185 notePhase(ctx.request, `shelf-${name}`, now - mark);

Good: a hyphen prefix, because notePhase rejects dots. It also keeps rows and records from colliding with any page-level phase.

186186 mark = now;
187187 };
188188 const records = appsForRepos(db, repoIds);
test/front-timing.test.ts +3 −3
@@ -33,15 +33,15 @@ async function read(): Promise<string[]> {
3333 return (res.headers.get("server-timing") ?? "").split(", ");
3434 }
3535
36 test("GET / signed out names session, shelf, render in order and covers the handler's share of app", async () => {
36 test("GET / signed out names session, shelf, tiles, render in order and covers the handler's share of app", async () => {
3737 for (let i = 0; i < 5; i++) await read(); // warm
3838 const shares: number[] = [];
3939 for (let i = 0; i < 15; i++) {
4040 const parts = await read();
4141 const names = parts.map((p) => p.split(";")[0]);
42 expect(names.filter((n) => n !== "app" && n !== "db")).toEqual(["session", "shelf", "render"]);
42 expect(names.filter((n) => n !== "app" && n !== "db")).toEqual(["session", "shelf", "tiles", "render"]);
4343 const total = dur(parts.find((p) => p.startsWith("app;"))!);
44 const named = parts.filter((p) => /^(session|shelf|render);/.test(p)).reduce((s, p) => s + dur(p), 0);
44 const named = parts.filter((p) => /^(session|shelf|tiles|render);/.test(p)).reduce((s, p) => s + dur(p), 0);
4545 shares.push(total === 0 ? 1 : named / total);
4646 }
4747 // The handler's own phases are all of the handler; what `app` also holds
test/shelf-laps.test.ts added +74
@@ -0,0 +1,74 @@
1 /** The shelf's laps reach Server-Timing on /, /apps and /:owner (#6178). */
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 { createRepo } from "../src/actions/repos.ts";
7 import { createApp, type App } from "../src/app.ts";
8 import { createApp as insertApp } from "../src/core/apps.ts";
9 import { createSession } from "../src/core/sessions.ts";
10 import { createUser } from "../src/core/users.ts";
11 import { serve, type Servers } from "../src/http/server.ts";
12 import { promote } from "./helpers/members.ts";
13 import { quietLogs } from "./helpers/globals.ts";
14
15 quietLogs();
16
17 let root = "";
18 let app: App;
19 let servers: Servers;
20 let cookie = "";
21
22 beforeAll(async () => {
23 root = mkdtempSync(join(tmpdir(), "forge-shelf-laps-"));
24 app = createApp({ dataDir: root, port: 0 });
25 servers = await serve(app);
26 const u = await createUser(app.db, {
27 origin: "seed",
28 trusted: true,
29 username: "keeper",
30 email: "[email protected]",
31 password: "a-good-passphrase",
32 adminIfFirst: true,
33 });
34 if (!u.ok) throw new Error(u.error);
35 promote(app.db, u.value);
36 cookie = `forge_session=${createSession(app.db, u.value.id).token}; forge_mode=studio`;
37 for (const name of ["alpha", "beta"]) {
38 const made = await createRepo(app, u.value, { name, visibility: "public" });
39 if (!made.ok) throw new Error(String(made.error));
40 insertApp(app.db, { repoId: made.value.id, slug: name, access: "public" });
41 }
42 }, 30_000);
43 afterAll(() => {
44 servers.stop();
45 app.close();
46 rmSync(root, { recursive: true, force: true });
47 });
48
49 async function names(path: string): Promise<string[]> {
50 const res = await fetch(`${servers.url}${path}`, {
51 headers: path === "/" ? {} : { cookie },
52 });
53 expect(res.status).toBe(200);
54 const header = res.headers.get("server-timing") ?? "";
55 return header.split(",").map((p) => p.trim().split(";")[0]!);
56 }
57
58 for (const path of ["/apps", "/keeper"]) {
59 test(`GET ${path} names the shelf's laps once each`, async () => {
60 const got = await names(path);
61 for (const lap of ["records", "runs", "production", "pulls", "rows"])
62 expect(got).toContain(`shelf-${lap}`);
63 expect(new Set(got).size).toBe(got.length);
64 });
65 }
66
67 // The front door's shelf is the cached front page, not `shelfRows`: its two
68 // stretches are reading the page and drawing the tiles.
69 test("GET / names the front shelf's read and its tiles once each", async () => {
70 const got = await names("/");
71 expect(got).toContain("shelf");
72 expect(got).toContain("tiles");
73 expect(new Set(got).size).toBe(got.length);
74 });
test/shelf-speed.test.ts +1 −1
@@ -84,7 +84,7 @@ test("40 apps are drawn within budget, in the phases Server-Timing names", async
8484 best = Math.min(best, performance.now() - started);
8585 phases = takePhases(c.request).map((p) => p.name);
8686 }
87 expect(phases).toEqual(["records", "runs", "production", "pulls", "rows"]);
87 expect(phases).toEqual(["shelf-records", "shelf-runs", "shelf-production", "shelf-pulls", "shelf-rows"]);
8888 // The yardstick is one batched read of the same shelf, taken in this run.
8989 let against = Infinity;
9090 for (let i = 0; i < 5; i++) {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs