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

One commit's page names its Server-Timing phases: session, git, render #3639

Merged opened this

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

Merged

scout/forge:scout/3632-one-commit-s-page-names-its-server-timin → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3632-one-commit-s-page-names-its-server-timin was merged into main as 7880832.

Review

Sign in to review this change.
2 files changed +98 −1
src/web/pages/commits.ts +10 −1
@@ -14,6 +14,7 @@ import { diffCommit, type Diff } from "../../git/diff.ts";
1414 import { isSafeOid, isSafeRef, isSafeTreePath } from "../../git/guard.ts";
1515 import { commitLog } from "../../git/read.ts";
1616 import { repoDir } from "../../git/repos.ts";
17 import { notePhase, takeSessionTime } from "../../core/route_timing.ts";
1718 import type { Ctx } from "../../http/context.ts";
1819 import type { Commit } from "../../view/source.ts";
1920 import { loadRepo, notFoundPage, scopeHeader } from "../chrome.ts";
@@ -244,6 +245,7 @@ export async function commitsPage(ctx: Ctx): Promise<Response> {
244245
245246 // GET /:owner/:name/commit/:oid — one commit's metadata and its diff.
246247 export async function commitPage(ctx: Ctx): Promise<Response> {
248 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
247249 const repo = loadRepo(ctx);
248250 if (!repo) return notFoundPage(ctx);
249251 const scope = localScope(ctx, repo);
@@ -251,28 +253,35 @@ export async function commitPage(ctx: Ctx): Promise<Response> {
251253 if (!isSafeOid(rev) && !isSafeRef(rev)) return notFoundPage(ctx);
252254 const dir = repoDir(ctx.app.reposRoot, repo.ownerName, repo.name);
253255
256 const reading = performance.now();
254257 const log = await commitLog(dir, rev, { limit: 1 });
255258 if (!log.ok || log.value.length === 0) return notFoundPage(ctx);
256259 const commit = log.value[0]!;
257260
258261 const diffResult = await diffCommit(dir, commit.oid);
262 notePhase(ctx.request, "git", performance.now() - reading);
259263 const diff = diffResult.ok
260264 ? diffResult.value
261265 : { files: [], additions: 0, deletions: 0 };
262266 // Old side is the first parent (or the empty tree for a root commit, where
263267 // every file is an addition and no old blob is read).
264268 const parent = commit.parents[0] ?? commit.oid;
269 const highlighting = performance.now();
265270 const syntax = await diffSyntax(dir, diff, parent, commit.oid);
271 notePhase(ctx.request, "highlight", performance.now() - highlighting);
266272
273 const rendering = performance.now();
267274 const body = html`${scopeHeader(ctx, scope, "commits", { compact: true })}
268275 ${renderCommit(scope, commit, diff, syntax)}`;
269 return page({
276 const response = page({
270277 ctx,
271278 title: `${commit.subject} · ${repo.name}`,
272279 crumbs: scope.crumbs,
273280 palette: scope.palette,
274281 body,
275282 });
283 notePhase(ctx.request, "render", performance.now() - rendering);
284 return response;

Minor: the render phase is closed after page() returns, so it covers the build only, which is what you want. Keep it this way when browseCommit gets the same phases.

276285 }
277286
278287 // ---- the live-browse pages ----
test/commit-page-timing.test.ts added +88
@@ -0,0 +1,88 @@
1 /** One commit's page names session, git, highlight and render in Server-Timing (#3632). */
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 { gitClientEnv } from "./helpers/git-client.ts";
9 import { adminCookie } from "./helpers/admin.ts";
10
11 let dataDir = "";
12 let work = "";
13 let app: App;
14 let servers: Servers;
15 let oid = "";
16
17 const env = gitClientEnv({
18 GIT_AUTHOR_NAME: "Ada",
19 GIT_AUTHOR_EMAIL: "[email protected]",
20 GIT_COMMITTER_NAME: "Ada",
21 GIT_COMMITTER_EMAIL: "[email protected]",
22 });
23
24 async function sh(args: string[], cwd: string): Promise<string> {
25 const proc = Bun.spawn(args, { cwd, env, stdout: "pipe", stderr: "pipe" });
26 const [out, err, code] = await Promise.all([
27 new Response(proc.stdout).text(),
28 new Response(proc.stderr).text(),
29 proc.exited,
30 ]);
31 if (code !== 0) throw new Error(`${args.join(" ")} failed: ${err}`);
32 return out.trim();
33 }
34
35 beforeAll(async () => {
36 dataDir = await mkdtemp(join(tmpdir(), "forge-commit-timing-"));
37 work = await mkdtemp(join(tmpdir(), "forge-commit-timing-work-"));
38 app = createApp({ dataDir, port: 0 });
39 servers = await serve(app);
40 const cookie = await adminCookie(app, {
41 username: "ada",
42 email: "[email protected]",
43 password: "analytical-engine",
44 });
45 await fetch(`${servers.url}/new`, {
46 method: "POST",
47 headers: { cookie },
48 body: new URLSearchParams({ name: "engine", visibility: "public", description: "d" }),
49 redirect: "manual",
50 });
51 const minted = await fetch(`${servers.url}/api/v1/tokens`, {
52 method: "POST",
53 headers: { "content-type": "application/json" },
54 body: JSON.stringify({
55 adminConfirm: true,
56 username: "ada",
57 password: "analytical-engine",
58 name: "cli",
59 scope: "rw",
60 }),
61 });
62 const token = ((await minted.json()) as { token: string }).token;
63 const remote = `${servers.url.replace("http://", `http://ada:${token}@`)}/ada/engine.git`;
64 await sh(["git", "clone", remote, "engine"], work);
65 const dir = join(work, "engine");
66 await Bun.write(join(dir, "a.ts"), "export const a = 1;\n");
67 await sh(["git", "add", "."], dir);
68 await sh(["git", "commit", "-m", "add a"], dir);
69 await sh(["git", "push", "origin", "main"], dir);
70 oid = await sh(["git", "rev-parse", "HEAD"], dir);
71 });
72
73 afterAll(async () => {
74 servers.stop();
75 app.close();
76 await rm(dataDir, { recursive: true, force: true });
77 await rm(work, { recursive: true, force: true });
78 });
79
80 test("GET /:owner/:name/commit/:oid names its phases", async () => {
81 const res = await fetch(`${servers.url}/ada/engine/commit/${oid}`);
82 const body = await res.text();
83 expect(res.status).toBe(200);
84 expect(body).toContain("add a");
85 const parts = (res.headers.get("server-timing") ?? "").split(", ");
86 for (const name of ["app", "session", "git", "highlight", "render"])
87 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
88 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs