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

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

Merged opened this

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

Merged

scout/forge:scout/3403-the-apps-page-s-server-timing-names-its → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3403-the-apps-page-s-server-timing-names-its was merged into main as 0656ed7.

Review

Sign in to review this change.
2 files changed +78 −6
src/web/pages/apps.ts +21 −6
@@ -41,6 +41,12 @@ import {
4141 } from "../../core/repos.ts";
4242 import type { User } from "../../core/users.ts";
4343 import { safeBack, type Ctx } from "../../http/context.ts";
44 import {
45 notePhase,
46 takeSessionTime,
47 timePhase,
48 timePhaseAsync,
49 } from "../../core/route_timing.ts";
4450 import { isHostPort, resolveAppHostname } from "../../apps/proxy.ts";
4551 import {
4652 loadRepo,
@@ -133,7 +139,8 @@ export function envLabel(env: EnvName): string {
133139 * never sees its crew or its cost.
134140 */
135141 export async function appsList(ctx: Ctx): Promise<Response> {
136 const entries = appsForViewer(ctx);
142 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
143 const entries = timePhase(ctx.request, "apps", () => appsForViewer(ctx));
137144 const liveCount = entries.filter(
138145 (e) => e.production?.state === "running",
139146 ).length;
@@ -169,13 +176,19 @@ export async function appsList(ctx: Ctx): Promise<Response> {
169176 },
170177 ])}
171178 </div>`;
172 const rows = await shelfRows(
173 ctx,
174 shown.map((e) => e.repo),
179 const rows = await timePhaseAsync(ctx.request, "shelf", () =>
180 shelfRows(
181 ctx,
182 shown.map((e) => e.repo),
183 ),
175184 );
185 const cost = timePhase(ctx.request, "cost", () =>
186 costLine(ctx.app.db, writableAppsOf(ctx, ctx.auth.user).repos),
187 );
188 const rendering = performance.now();
176189 const body = html`${when(ctx.auth.user, () => homeTabs("apps"))}
177190 ${pageHead("Apps", { actions: create })}
178 ${costLine(ctx.app.db, writableAppsOf(ctx, ctx.auth.user).repos)}
191 ${cost}
179192 ${filters}
180193 ${shown.length === 0
181194 ? empty({
@@ -187,7 +200,9 @@ export async function appsList(ctx: Ctx): Promise<Response> {
187200 action: create,
188201 })
189202 : listCard(rows)}`;
190 return page({ ctx, title: "Apps", body });
203 const response = page({ ctx, title: "Apps", body });
204 notePhase(ctx.request, "render", performance.now() - rendering);
205 return response;
191206 }
192207
193208 // ---- new app ----
test/apps-timing.test.ts added +57
@@ -0,0 +1,57 @@
1 /** `/apps` names session, apps, shelf, cost and render in Server-Timing (#3403). */

The header cites #3403, but this pull is #3410. Use the number of the issue it closes.

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 { createUser } from "../src/core/users.ts";
9 import { serve, type Servers } from "../src/http/server.ts";
10 import { proveAll } from "./helpers/verified.ts";
11 import { quietLogs } from "./helpers/globals.ts";
12
13 quietLogs();
14
15 let root = "";
16 let app: App;
17 let servers: Servers;
18
19 beforeAll(async () => {
20 root = mkdtempSync(join(tmpdir(), "forge-apps-timing-"));
21 app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" });
22 const u = await createUser(app.db, {
23 origin: "seed",
24 trusted: true,
25 username: "alice",
26 email: "[email protected]",
27 password: "a-good-passphrase",
28 adminIfFirst: true,
29 });
30 if (!u.ok) throw new Error(u.error);
31 proveAll(app);
32 const made = await createRepo(app, { id: u.value.id, username: "alice" } as never, { name: "forge" });
33 if (!made.ok) throw new Error(made.error);
34 servers = await serve(app);
35 });
36 afterAll(() => {
37 servers.stop();
38 app.close();
39 rmSync(root, { recursive: true, force: true });
40 });
41
42 test("GET /apps names its phases", async () => {
43 const signin = await fetch(`${servers.url}/signin`, {
44 method: "POST",
45 headers: { origin: servers.url },
46 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
47 redirect: "manual",
48 });
49 const cookie = (signin.headers.get("set-cookie") ?? "").split(";")[0]!;
50 const res = await fetch(`${servers.url}/apps`, { headers: { cookie } });
51 const body = await res.text();
52 expect(res.status).toBe(200);
53 expect(body).toContain("<h1>Apps</h1>");
54 const parts = (res.headers.get("server-timing") ?? "").split(", ");
55 for (const name of ["app", "session", "apps", "shelf", "cost", "render"])
56 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
57 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs