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

/search's Server-Timing names its phases: session, each read, render #3287

Merged opened this

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

Merged

scout/forge:scout/3274-search-s-server-timing-names-its-phases → main 1 commit fork

  • Merged. scout/3274-search-s-server-timing-names-its-phases was merged into main as a5ab3a3.

Review

Sign in to review this change.
2 files changed +71 −6
src/web/pages/search.ts +16 −6
@@ -26,6 +26,7 @@ import {
2626 type SearchResults,
2727 } from "../../core/search.ts";
2828 import type { Ctx } from "../../http/context.ts";
29 import { notePhase, takeSessionTime, timePhase, timePhaseAsync } from "../../core/route_timing.ts";
2930 import { notFoundPage, pageHead } from "../chrome.ts";
3031 import { html, when, type Html } from "../html.ts";
3132 import { icon } from "../icons.ts";
@@ -339,6 +340,7 @@ async function find(
339340 * connected origin, which is not a thing to do on every keystroke.
340341 */
341342 export async function searchPage(ctx: Ctx): Promise<Response> {
343 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
342344 const params = ctx.url.searchParams;
343345 const a: Asked = {
344346 query: cleanQuery(params.get("q") ?? ""),
@@ -373,7 +375,8 @@ export async function searchPage(ctx: Ctx): Promise<Response> {
373375 </form>`;
374376
375377 if (a.query === "") {
376 return page({
378 const rendering = performance.now();
379 const blank = page({
377380 ctx,
378381 title: "Search",
379382 body: html`${pageHead("Search")} ${form}
@@ -383,11 +386,14 @@ export async function searchPage(ctx: Ctx): Promise<Response> {
383386 note: "An app or repository by name or description, a word from what was told or changed, or a line of the code itself — across everything you can see, here and on every origin you have connected.",
384387 })}`,
385388 });
389 notePhase(ctx.request, "render", performance.now() - rendering);
390 return blank;
386391 }
387392
388 const { found, busy } = await find(ctx, a);
389 const seen = appsOf(ctx, ctx.auth.user);
390 const apps: Apps = {
393 const { found, busy } = await timePhaseAsync(ctx.request, "search", () => find(ctx, a));
394 const apps: Apps = timePhase(ctx.request, "apps", () => {
395 const seen = appsOf(ctx, ctx.auth.user);
396 return {
391397 byId: seen.records,
392398 names: new Set(seen.repos.map((r) => `${r.ownerName}/${r.name}`)),
393399 enterable: enterableApps(
@@ -399,7 +405,9 @@ export async function searchPage(ctx: Ctx): Promise<Response> {
399405 ctx.auth.user,
400406 { named: false },
401407 ),
402 };
408 };
409 });
410 const rendering = performance.now();
403411
404412 const chips = seg(
405413 WORDS.map(([kind, label]) => ({
@@ -464,7 +472,9 @@ export async function searchPage(ctx: Ctx): Promise<Response> {
464472 ([kind]) => kind !== "all" && (a.kind === "all" || a.kind === kind),
465473 )
466474 .map((w) => section(a, w, ...rows[w[0]]()))}`;
467 return page({ ctx, title: `Search · ${a.query}`, body });
475 const response = page({ ctx, title: `Search · ${a.query}`, body });
476 notePhase(ctx.request, "render", performance.now() - rendering);
477 return response;
468478 }
469479
470480 // ---- confirmation ----
test/search-timing.test.ts added +55
@@ -0,0 +1,55 @@
1 /** /search's Server-Timing names its phases: session, search, apps, render (#3274). */
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 } from "../src/app.ts";
7 import { createToken } from "../src/core/tokens.ts";
8 import { createUser } from "../src/core/users.ts";
9 import { serve } from "../src/http/server.ts";
10
11 let dir = "";
12 let app: ReturnType<typeof createApp>;
13 let servers: Awaited<ReturnType<typeof serve>>;
14 let bearer = "";
15
16 beforeAll(async () => {
17 dir = await mkdtemp(join(tmpdir(), "forge-search-timing-"));
18 app = createApp({ dataDir: dir, port: 0 });
19 servers = await serve(app);
20 const made = await createUser(app.db, {
21 origin: "seed", trusted: true, username: "alice", email: "[email protected]",
22 password: "a-good-passphrase", adminIfFirst: true,
23 });
24 if (!made.ok) throw new Error(made.error);
25 bearer = createToken(app.db, made.value.id, "t", "admin").token;
26 });
27
28 afterAll(async () => {
29 servers.stop();
30 app.close();
31 await rm(dir, { recursive: true, force: true });
32 });
33
34 const get = (path: string) => fetch(servers.url + path, { headers: { authorization: `Bearer ${bearer}` } });
35
36 const phases = (res: Response) =>
37 (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!);
38
39 test("a search names session, search, apps and render once each, with durations", async () => {
40 const res = await get("/search?q=forge");
41 expect(res.status).toBe(200);
42 expect(await res.text()).toContain("Results for");
43 const header = res.headers.get("server-timing") ?? "";
44 for (const n of ["session", "search", "apps", "render"]) {
45 expect(phases(res).filter((x) => x === n).length).toBe(1);
46 const part = header.split(", ").find((p) => p.startsWith(`${n};`))!;
47 expect(Number(/dur=([\d.]+)/.exec(part)?.[1])).not.toBeNaN();
48 }
49 });
50
51 test("the empty search names session and render", async () => {
52 const res = await get("/search");
53 expect(res.status).toBe(200);
54 expect(phases(res)).toEqual(expect.arrayContaining(["session", "render"]));
55 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs