Public: anyone can read this; the crew can change it.
Every response's Server-Timing names the worst event-loop lag of the last 10 s, so a stall outside the handler shows #10292
Check · Review · Land · Live done — Landed and live in production.
torque/forge:torque/10288-every-response-s-server-timing-names-the → main 3 commits fork 1 thread · 1 unresolved
82 files
changed +423
−81
AGENTS.md +2
| @@ -16,4 +16,6 @@ A new `/settings…` page route goes in `src/http/settings_routes.ts` (`settings | ||
| 16 | 16 | |
| 17 | 17 | State shared between modules through process memory, not the database, is named here. `src/perf/views_report.ts`: `readVisits` holds each scope and window's visits for 15 seconds (dropped by `flushViews`), so a page and its probe share one read; `src/perf/views_store.ts`: the view buffer, flushed every 10 seconds. The traffic sources view's terminal door is `src/tui/screens/trafficview.ts` (palette entry for admins, per-app command for a repository's admins). `src/runs/held.ts`: each dispatcher pass (`src/crew/dispatch.ts`, `pump`) records which queued agent runs it held and why (valve, pace, limit), and vitals (`src/kernel/vitals.ts`) leaves those out of `queue:agent`. A record older than `HELD_FRESH_MS` is ignored, so a stopped dispatcher cannot hide its queue. |
| 18 | 18 | |
| 19 | `src/perf/loop_worst.ts`: `loopWorst`, the worst event-loop lag of the last 10 s in ten one-second slots, fed by one unref'd 50 ms timer that the first `serve` starts (`startLoopWorst`), and read by every response's `lag;dur` and by `/healthz` asked for JSON (`loopLagMs`). Tests listing a request's phases read `requestTiming` (`test/helpers/request-timing.ts`), which leaves `lag` out. | |
| 20 | ||
| 19 | 21 | `src/core/transfer.ts`: the old owner/name pairs a transfer left behind (`repo_redirect`), held per database after the first request reads them and dropped by `transferRepo`, so a request under a path nothing moved from costs no statement (#569). |
docs/features/performance-watcher.md +1 −1
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | # Performance watcher (#7559) |
| 2 | 2 | |
| 3 | `src/perf/` keeps a capped in-memory record of real requests per route pattern (total, each Server-Timing phase, signed in or out; no paths, ids or queries). `budgets.ts` holds the budgets in words with the number beside them. Once a minute `duty.ts` files one issue per route and phase in the forge's repo when p95 has been over budget for 15 min with 20+ real requests; the finding names the slowest phase, its share and the cache hit rate. `git.ts` keeps the same record for every git process the forge spawns (`git/env.ts`, `git/local.ts`), keyed `git <subcommand>` from a fixed list (never an argument, path or ref; anything else is `git other`), timed from before the spawn gate so a queue counts; a subcommand whose p95 passes its budget the same way (250 ms; 10 s for the pack-moving ones: clone, fetch, push, gc, repack, pack-objects and the like) files one issue naming it, its count, p50 and p95. `loop.ts` names the routes running across an event-loop stall in the `loop` log line; nothing is kept beyond that line. | |
| 3 | `src/perf/` keeps a capped in-memory record of real requests per route pattern (total, each Server-Timing phase, signed in or out; no paths, ids or queries). `budgets.ts` holds the budgets in words with the number beside them. Once a minute `duty.ts` files one issue per route and phase in the forge's repo when p95 has been over budget for 15 min with 20+ real requests; the finding names the slowest phase, its share and the cache hit rate. `git.ts` keeps the same record for every git process the forge spawns (`git/env.ts`, `git/local.ts`), keyed `git <subcommand>` from a fixed list (never an argument, path or ref; anything else is `git other`), timed from before the spawn gate so a queue counts; a subcommand whose p95 passes its budget the same way (250 ms; 10 s for the pack-moving ones: clone, fetch, push, gc, repack, pack-objects and the like) files one issue naming it, its count, p50 and p95. `loop.ts` names the routes running across an event-loop stall in the `loop` log line; nothing is kept beyond that line. `loop_worst.ts` keeps the worst event-loop lag of the last 10 s (ten one-second slots fed by the 50 ms sampler) and every response's Server-Timing names it as `lag;dur`; `/healthz` answers it as `loopLagMs` to `Accept: application/json`, so a slow time to first byte with a small `app;dur` reads as a stall outside the handler. | |
| 4 | 4 | |
| 5 | 5 | Not yet: a synthetic probe for routes with no traffic (a client-written label would let anyone hide requests; it needs an admin-only door first), live route stats in the terminal and MCP (findings reach both doors as issues), browser timings, 24 h/7 d history, the board page, release before/after. |
src/core/route_timing.ts +10
| @@ -210,6 +210,16 @@ export function serverTiming( | ||
| 210 | 210 | ].join(", "); |
| 211 | 211 | } |
| 212 | 212 | |
| 213 | /** | |
| 214 | * `header` with the worst event-loop lag of the last 10 s appended as | |
| 215 | * `lag;dur=<ms>` (#10288): a request that waited on a loop someone else | |
| 216 | * blocked shows it here, though its own `app` was fast. | |
| 217 | */ | |
| 218 | export function withLag(header: string, lagMs: number): string { | |
| 219 | if (!Number.isFinite(lagMs) || lagMs < 0) return header; | |
| 220 | return `${header}, lag;dur=${lagMs.toFixed(1)}`; | |
| 221 | } | |
| 222 | ||
| 213 | 223 | /** One named stretch of a request, in milliseconds. */ |
| 214 | 224 | export interface Phase { |
| 215 | 225 | readonly name: string; |
src/http/health.ts +8 −1
| @@ -87,8 +87,13 @@ export function startDraining(log: Log, why: string, fields: Record<string, numb | ||
| 87 | 87 | * Liveness: `ping` runs one trivial query against the database. However |
| 88 | 88 | * long it takes, an answer is 200; only a throw is 503, logged when it |
| 89 | 89 | * starts and when it ends. |
| 90 | * | |
| 91 | * Given `loopLagMs` (the caller asked for JSON), the 200 is | |
| 92 | * `{"ok":true,"loopLagMs":n}`: the worst event-loop lag of the last 10 s | |
| 93 | * (`perf/loop_worst.ts`, #10288). Otherwise it stays the plain `ok` a | |
| 94 | * probe and a stranger hear (#2917). | |
| 90 | 95 | */ |
| 91 | export function liveResponse(ping: () => unknown, log: Log): Response { | |
| 96 | export function liveResponse(ping: () => unknown, log: Log, loopLagMs?: number): Response { | |
| 92 | 97 | try { |
| 93 | 98 | ping(); |
| 94 | 99 | } catch (error) { |
| @@ -101,6 +106,8 @@ export function liveResponse(ping: () => unknown, log: Log): Response { | ||
| 101 | 106 | } |
| 102 | 107 | if (databaseDown) log.info("forge alive again: the database answers"); |
| 103 | 108 | databaseDown = false; |
| 109 | if (loopLagMs !== undefined && Number.isFinite(loopLagMs)) | |
| 110 | return Response.json({ ok: true, loopLagMs: Math.round(loopLagMs * 10) / 10 }, { status: 200 }); | |
| 104 | 111 | return new Response("ok", { status: 200 }); |
| 105 | 112 | } |
| 106 | 113 |
src/http/server.ts +13 −2
| @@ -480,6 +480,7 @@ import { | ||
| 480 | 480 | noteStart, |
| 481 | 481 | takePhases, |
| 482 | 482 | serverTiming, |
| 483 | withLag, | |
| 483 | 484 | takeDbTime, |
| 484 | 485 | type RouteTiming, |
| 485 | 486 | } from "../core/route_timing.ts"; |
| @@ -493,6 +494,7 @@ import { appSourcesPage } from "../web/pages/sources.ts"; | ||
| 493 | 494 | import { createViewWriter } from "../perf/views_store.ts"; |
| 494 | 495 | import { createReadWriter, readOf } from "../perf/reads.ts"; |
| 495 | 496 | import { parseServerTiming } from "../perf/record.ts"; |
| 497 | import { currentLoopLag, startLoopWorst } from "../perf/loop_worst.ts"; | |
| 496 | 498 | import { methodOf, noteUsage, roleOf, surfaceOf, takeUsage } from "./usage_tag.ts"; |
| 497 | 499 | import { bearerChallenge, oauthRoutes } from "./oauth.ts"; |
| 498 | 500 | import { flushAccess, recordAccess } from "./access_log.ts"; |
| @@ -2357,6 +2359,8 @@ export async function serve(app: App): Promise<Servers> { | ||
| 2357 | 2359 | const featureSchedule = startFeatureSchedule(app); |
| 2358 | 2360 | const pullSchedule = startPullSchedule(app); |
| 2359 | 2361 | const readWriter = createReadWriter(app.db, readLedger, app.log); |
| 2362 | // The worst event-loop lag of the last 10 s, on every response (#10288). | |
| 2363 | startLoopWorst(); | |
| 2360 | 2364 | const counted = ( |
| 2361 | 2365 | owner: App, |
| 2362 | 2366 | request: Request, |
| @@ -2368,7 +2372,8 @@ export async function serve(app: App): Promise<Servers> { | ||
| 2368 | 2372 | finishedStrangerDeep(owner, request); |
| 2369 | 2373 | try { |
| 2370 | 2374 | const header = serverTiming(ms, takeDbTime(request), takePhases(request)); |
| 2371 | response.headers.set("server-timing", header); | |
| 2375 | // `lag` is the loop's, not this request's: kept out of the record's phases. | |
| 2376 | response.headers.set("server-timing", withLag(header, currentLoopLag())); | |
| 2372 | 2377 | const end = performance.now(); |
| 2373 | 2378 | const family = familyOf(request.headers.get("user-agent") ?? ""); |
| 2374 | 2379 | perfRecord.add( |
| @@ -2730,7 +2735,13 @@ async function answerGet( | ||
| 2730 | 2735 | if (url.pathname === "/healthz") { |
| 2731 | 2736 | noteUsage(request, { pattern: "/healthz", role: "anon" }); |
| 2732 | 2737 | const dbStart = performance.now(); |
| 2733 | const live = liveResponse(() => app.db.query("select 1").get(), app.log); | |
| 2738 | // `Accept: application/json` adds the loop's worst lag of the last 10 s (#10288). | |
| 2739 | const wantsJson = (request.headers.get("accept") ?? "").includes("application/json"); | |
| 2740 | const live = liveResponse( | |
| 2741 | () => app.db.query("select 1").get(), | |
| 2742 | app.log, | |
| 2743 | wantsJson ? currentLoopLag() : undefined, | |
| 2744 | ); | |
| 2734 | 2745 | noteDbTime(request, performance.now() - dbStart); |
| 2735 | 2746 | // The release proof learns which release answered, old pod or new; a |
| 2736 | 2747 | // stranger is told only that one did (#2917). |
src/perf/loop_worst.ts added +100
| @@ -0,0 +1,100 @@ | ||
| 1 | /** | |
| 2 | * The worst event-loop lag of the last ten seconds (#10288), written on every | |
| 3 | * response as `lag;dur=<ms>` and told by `/healthz` as `loopLagMs`. A request | |
| 4 | * whose `app;dur` is 4 ms but whose time to first byte is 300 ms waited on a | |
| 5 | * loop blocked by something else; this names how long that block was. | |
| 6 | * | |
| 7 | * Lag is the lateness `kernel/loop_lag.ts` measures: a timer asks to wake | |
| 8 | * every 50 ms, and how much later than asked it wakes is how long the loop | |
| 9 | * was busy. A block of B ms is read as B − (time already waited, 0–50 ms), | |
| 10 | * so at 50 ms the reading undercounts a block by at most one interval. | |
| 11 | * | |
| 12 | * The window is a ring of ten one-second slots, each the worst lag noted in | |
| 13 | * its second: `note` is O(1) (a division, a compare, two array writes) and | |
| 14 | * `worst` reads ten numbers, so a tick costs well under 0.1 ms. A stall | |
| 15 | * stays named for 9–10 s after the wake that saw it, then falls out. | |
| 16 | */ | |
| 17 | ||
| 18 | import { LAG_EVERY_MS, startLoopLag } from "../kernel/loop_lag.ts"; | |
| 19 | ||
| 20 | /** The window `worst` covers, ms. */ | |
| 21 | export const LAG_WINDOW_MS = 10_000; | |
| 22 | /** One slot of the ring, ms: the window's resolution. */ | |
| 23 | export const LAG_SLOT_MS = 1_000; | |
| 24 | ||
| 25 | export interface LoopWorst { | |
| 26 | /** A wake `lateMs` late, seen at `at` (ms, the clock `worst` reads). */ | |
| 27 | note(lateMs: number, at: number): void; | |
| 28 | /** The worst lateness noted in the window ending at `at`, ms; 0 when none. */ | |
| 29 | worst(at: number): number; | |
| 30 | } | |
| 31 | ||
| 32 | export function createLoopWorst( | |
| 33 | windowMs = LAG_WINDOW_MS, | |
| 34 | slotMs = LAG_SLOT_MS, | |
| 35 | ): LoopWorst { | |
| 36 | const n = Math.max(1, Math.round(windowMs / slotMs)); | |
| 37 | // slot[i] is which second (at / slotMs, floored) max[i] holds; -1 is empty. | |
| 38 | const slot = new Float64Array(n).fill(-1); | |
| 39 | const max = new Float64Array(n); | |
| 40 | return { | |
| 41 | note(lateMs, at) { | |
| 42 | if (!Number.isFinite(lateMs) || lateMs < 0 || !Number.isFinite(at)) return; | |
| 43 | const s = Math.floor(at / slotMs); | |
| 44 | const i = ((s % n) + n) % n; | |
| 45 | if (slot[i] !== s) { | |
| 46 | slot[i] = s; | |
| 47 | max[i] = lateMs; | |
| 48 | } else if (lateMs > max[i]!) max[i] = lateMs; | |
| 49 | }, | |
| 50 | worst(at) { | |
| 51 | const now = Math.floor(at / slotMs); | |
| 52 | let w = 0; | |
| 53 | for (let i = 0; i < n; i++) | |
| 54 | if (slot[i]! > now - n && slot[i]! <= now && max[i]! > w) w = max[i]!; | |
| 55 | return w; | |
| 56 | }, | |
| 57 | }; | |
| 58 | } | |
| 59 | ||
| 60 | /** The process's one window, fed by `startLoopWorst`'s timer and read by every response. */ | |
| 61 | export const loopWorst = createLoopWorst(); | |
| 62 | ||
| 63 | let sampling: (() => void) | null = null; | |
| 64 | /** The clock the window is read on; a test injects one to step ten seconds at once. */ | |
| 65 | let windowClock: () => number = () => performance.now(); | |
| 66 | ||
| 67 | export interface LoopWorstOptions { | |
| 68 | /** The window's clock, ms; lateness is always measured on `performance.now`. */ | |
| 69 | readonly clock?: () => number; | |
| 70 | /** How often the timer asks to wake, ms. */ | |
| 71 | readonly everyMs?: number; | |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Start the one sampler feeding `loopWorst` (idempotent: a second server in | |
| 76 | * the same process shares it). Its timer is unref'd, so it never holds a | |
| 77 | * test or a shutdown open. Returns the stop, which also restores the clock. | |
| 78 | */ | |
| 79 | export function startLoopWorst(options: LoopWorstOptions = {}): () => void { | |
| 80 | if (sampling === null) { | |
| 81 | if (options.clock !== undefined) windowClock = options.clock; | |
| 82 | const lag = startLoopLag(options.everyMs ?? LAG_EVERY_MS, () => performance.now(), (late) => | |
| 83 | loopWorst.note(late, windowClock()), | |
| 84 | ); | |
| 85 | sampling = lag.stop; | |
| 86 | } | |
| 87 | return stopLoopWorst; | |
| 88 | } | |
| 89 | ||
| 90 | /** Stop the sampler, if one runs, and put the window back on `performance.now`. */ | |
| 91 | export function stopLoopWorst(): void { | |
| 92 | sampling?.(); | |
| 93 | sampling = null; | |
| 94 | windowClock = () => performance.now(); | |
| 95 | } | |
| 96 | ||
| 97 | /** The worst lag of the window ending now, ms, for a header or `/healthz`. */ | |
| 98 | export function currentLoopLag(): number { | |
| 99 | return loopWorst.worst(windowClock()); | |
| 100 | } |
test/account-lists-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -31,7 +32,7 @@ afterAll(async () => { | ||
| 31 | 32 | |
| 32 | 33 | const basic = `Basic ${btoa("alice:correct-horse-battery")}`; |
| 33 | 34 | const phases = (res: Response) => |
| 34 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n && n !== "app"); | |
| 35 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n && n !== "app"); | |
| 35 | 36 | |
| 36 | 37 | for (const [path, read] of [ |
| 37 | 38 | ["/api/v1/tokens", "tokens"], |
test/admin-lists-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -30,7 +31,7 @@ afterAll(async () => { | ||
| 30 | 31 | }); |
| 31 | 32 | |
| 32 | 33 | const names = (res: Response) => |
| 33 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter(Boolean); | |
| 34 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter(Boolean); | |
| 34 | 35 | |
| 35 | 36 | for (const [path, read] of [["accounts", "accounts"], ["people", "people"], ["invites", "invites"], ["teams", "teams"]] as const) { |
| 36 | 37 | test(`GET /admin/${path} names session, ${read}, render in order`, async () => { |
test/api-agent-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-agents-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-app-open-timing.test.ts +2 −1
| @@ -11,6 +11,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 11 | 11 | import { createUser } from "../src/core/users.ts"; |
| 12 | 12 | import { serve, type Servers } from "../src/http/server.ts"; |
| 13 | 13 | import { quietLogs } from "./helpers/globals.ts"; |
| 14 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 14 | 15 | |
| 15 | 16 | quietLogs(); |
| 16 | 17 | |
| @@ -61,7 +62,7 @@ afterAll(async () => { | ||
| 61 | 62 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 62 | 63 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 63 | 64 | const body = await res.text(); |
| 64 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 65 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 65 | 66 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 66 | 67 | } |
| 67 | 68 |
test/api-apps-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-asked-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-assigned-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -46,7 +47,7 @@ afterAll(async () => { | ||
| 46 | 47 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 47 | 48 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 48 | 49 | const body = await res.text(); |
| 49 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 50 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 50 | 51 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 51 | 52 | } |
| 52 | 53 |
test/api-boards-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -43,7 +44,7 @@ afterAll(async () => { | ||
| 43 | 44 | async function phases(path: string, withCookie = true): Promise<{ status: number; names: string[]; body: string }> { |
| 44 | 45 | const res = await fetch(`${servers.url}${path}`, { headers: withCookie ? { cookie } : {} }); |
| 45 | 46 | const body = await res.text(); |
| 46 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 47 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 47 | 48 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 48 | 49 | } |
| 49 | 50 |
test/api-branches-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-ccheck-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-comments-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -50,7 +51,7 @@ afterAll(async () => { | ||
| 50 | 51 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 51 | 52 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 52 | 53 | const body = await res.text(); |
| 53 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 54 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 54 | 55 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 55 | 56 | } |
| 56 | 57 |
test/api-commit-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -57,7 +58,7 @@ afterAll(async () => { | ||
| 57 | 58 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 58 | 59 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 59 | 60 | const body = await res.text(); |
| 60 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 61 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 61 | 62 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 62 | 63 | } |
| 63 | 64 |
test/api-commits-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -54,7 +55,7 @@ afterAll(async () => { | ||
| 54 | 55 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 55 | 56 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 56 | 57 | const body = await res.text(); |
| 57 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 58 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 58 | 59 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 59 | 60 | } |
| 60 | 61 |
test/api-crew-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-decisions-timing.test.ts +2 −1
| @@ -11,6 +11,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 11 | 11 | import { createUser, getUserById } from "../src/core/users.ts"; |
| 12 | 12 | import { serve, type Servers } from "../src/http/server.ts"; |
| 13 | 13 | import { quietLogs } from "./helpers/globals.ts"; |
| 14 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 14 | 15 | |
| 15 | 16 | quietLogs(); |
| 16 | 17 | |
| @@ -51,7 +52,7 @@ afterAll(async () => { | ||
| 51 | 52 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 52 | 53 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 53 | 54 | const body = await res.text(); |
| 54 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 55 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 55 | 56 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 56 | 57 | } |
| 57 | 58 |
test/api-deps-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -52,7 +53,7 @@ afterAll(async () => { | ||
| 52 | 53 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 53 | 54 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 54 | 55 | const body = await res.text(); |
| 55 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 56 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 56 | 57 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 57 | 58 | } |
| 58 | 59 |
test/api-egress-grants-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -65,7 +66,7 @@ async function phases( | ||
| 65 | 66 | ): Promise<{ status: number; names: string[]; body: string }> { |
| 66 | 67 | const res = await fetch(`${servers.url}${path}`, { headers }); |
| 67 | 68 | const body = await res.text(); |
| 68 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 69 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 69 | 70 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 70 | 71 | } |
| 71 | 72 |
test/api-explore-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -41,7 +42,7 @@ afterAll(async () => { | ||
| 41 | 42 | test("the API explore read names session, explore and render in order", async () => { |
| 42 | 43 | const res = await fetch(`${servers.url}/api/v1/explore`, { headers: { cookie } }); |
| 43 | 44 | const body = JSON.parse(await res.text()); |
| 44 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 45 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 45 | 46 | expect(res.status).toBe(200); |
| 46 | 47 | expect(names.slice(names.indexOf("app") + 1)).toEqual(["session", "explore", "render"]); |
| 47 | 48 | expect(Array.isArray(body.items)).toBe(true); |
test/api-family-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -45,7 +46,7 @@ afterAll(async () => { | ||
| 45 | 46 | async function phases(path: string, withCookie = true) { |
| 46 | 47 | const res = await fetch(`${servers.url}${path}`, { headers: withCookie ? { cookie } : {} }); |
| 47 | 48 | const body = await res.text(); |
| 48 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 49 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 49 | 50 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 50 | 51 | } |
| 51 | 52 |
test/api-flow-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-following-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -56,7 +57,7 @@ afterAll(async () => { | ||
| 56 | 57 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 57 | 58 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 58 | 59 | const body = await res.text(); |
| 59 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 60 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 60 | 61 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 61 | 62 | } |
| 62 | 63 |
test/api-keys-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -46,7 +47,7 @@ afterAll(async () => { | ||
| 46 | 47 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 47 | 48 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 48 | 49 | const body = await res.text(); |
| 49 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 50 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 50 | 51 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 51 | 52 | } |
| 52 | 53 |
test/api-me-timing.test.ts +3 −2
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -46,7 +47,7 @@ afterAll(async () => { | ||
| 46 | 47 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 47 | 48 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 48 | 49 | const body = await res.text(); |
| 49 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 50 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 50 | 51 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 51 | 52 | } |
| 52 | 53 | |
| @@ -60,6 +61,6 @@ test("the profile read names session, user, render in order", async () => { | ||
| 60 | 61 | test("an anonymous read is a 401 naming session", async () => { |
| 61 | 62 | const res = await fetch(`${servers.url}/api/v1/user`); |
| 62 | 63 | expect(res.status).toBe(401); |
| 63 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 64 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 64 | 65 | expect(names.slice(names.indexOf("app") + 1)).toEqual(["session"]); |
| 65 | 66 | }); |
test/api-milestones-timing.test.ts +2 −1
| @@ -11,6 +11,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 11 | 11 | import { createUser } from "../src/core/users.ts"; |
| 12 | 12 | import { serve, type Servers } from "../src/http/server.ts"; |
| 13 | 13 | import { quietLogs } from "./helpers/globals.ts"; |
| 14 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 14 | 15 | |
| 15 | 16 | quietLogs(); |
| 16 | 17 | |
| @@ -63,7 +64,7 @@ afterAll(async () => { | ||
| 63 | 64 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 64 | 65 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 65 | 66 | const body = await res.text(); |
| 66 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 67 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 67 | 68 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 68 | 69 | } |
| 69 | 70 |
test/api-origin-repos-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { newFakeState, serveFakeForge, type FakeForge } from "./fake-origin.ts"; | ||
| 9 | 9 | import { seedAdmin } from "./helpers/admin.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | 11 | import { proveAll } from "./helpers/verified.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -54,7 +55,7 @@ afterAll(async () => { | ||
| 54 | 55 | async function get(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 55 | 56 | const res = await fetch(`${servers.url}${path}`, { headers: { authorization: auth } }); |
| 56 | 57 | const body = await res.text(); |
| 57 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 58 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 58 | 59 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 59 | 60 | } |
| 60 | 61 |
test/api-panel-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createToken } from "../src/core/tokens.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -51,7 +52,7 @@ afterAll(async () => { | ||
| 51 | 52 | async function phases(path: string): Promise<{ status: number; names: string[]; res: Response }> { |
| 52 | 53 | const res = await fetch(`${servers.url}${path}`, { headers: { authorization: `Bearer ${token}` } }); |
| 53 | 54 | await res.text(); |
| 54 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 55 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 55 | 56 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), res }; |
| 56 | 57 | } |
| 57 | 58 |
test/api-pchecks-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -67,7 +68,7 @@ afterAll(async () => { | ||
| 67 | 68 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 68 | 69 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 69 | 70 | const body = await res.text(); |
| 70 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 71 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 71 | 72 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 72 | 73 | } |
| 73 | 74 |
test/api-person-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -46,7 +47,7 @@ afterAll(async () => { | ||
| 46 | 47 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 47 | 48 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 48 | 49 | const body = await res.text(); |
| 49 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 50 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 50 | 51 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 51 | 52 | } |
| 52 | 53 |
test/api-personrepos-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-pins-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-previews-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -67,7 +68,7 @@ afterAll(async () => { | ||
| 67 | 68 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 68 | 69 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 69 | 70 | const body = await res.text(); |
| 70 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 71 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 71 | 72 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 72 | 73 | } |
| 73 | 74 |
test/api-prompts-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-publicrepos-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-pull-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -67,7 +68,7 @@ afterAll(async () => { | ||
| 67 | 68 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 68 | 69 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 69 | 70 | const body = await res.text(); |
| 70 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 71 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 71 | 72 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 72 | 73 | } |
| 73 | 74 |
test/api-pulls-timing.test.ts +2 −1
| @@ -13,6 +13,7 @@ import { createUser, type User } from "../src/core/users.ts"; | ||
| 13 | 13 | import { serve, type Servers } from "../src/http/server.ts"; |
| 14 | 14 | import { expectWork, requestStatements } from "./helpers/work.ts"; |
| 15 | 15 | import { quietLogs } from "./helpers/globals.ts"; |
| 16 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 16 | 17 | |
| 17 | 18 | quietLogs(); |
| 18 | 19 | |
| @@ -76,7 +77,7 @@ afterAll(async () => { | ||
| 76 | 77 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 77 | 78 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 78 | 79 | const body = await res.text(); |
| 79 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 80 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 80 | 81 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 81 | 82 | } |
| 82 | 83 |
test/api-reports-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-returns-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -46,7 +47,7 @@ afterAll(async () => { | ||
| 46 | 47 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 47 | 48 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 48 | 49 | const body = await res.text(); |
| 49 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 50 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 50 | 51 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 51 | 52 | } |
| 52 | 53 |
test/api-rstates-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -67,7 +68,7 @@ afterAll(async () => { | ||
| 67 | 68 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 68 | 69 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 69 | 70 | const body = await res.text(); |
| 70 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 71 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 71 | 72 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 72 | 73 | } |
| 73 | 74 |
test/api-runs-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -52,7 +53,7 @@ afterAll(async () => { | ||
| 52 | 53 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 53 | 54 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 54 | 55 | const body = await res.text(); |
| 55 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 56 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 56 | 57 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 57 | 58 | } |
| 58 | 59 |
test/api-scorecard-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { quietLogs } from "./helpers/globals.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | quietLogs(); |
| 13 | 14 | |
| @@ -46,7 +47,7 @@ afterAll(async () => { | ||
| 46 | 47 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 47 | 48 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 48 | 49 | const body = await res.text(); |
| 49 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 50 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 50 | 51 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 51 | 52 | } |
| 52 | 53 |
test/api-search-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { quietLogs } from "./helpers/globals.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | quietLogs(); |
| 11 | 12 | |
| @@ -32,7 +33,7 @@ afterAll(async () => { | ||
| 32 | 33 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 33 | 34 | const res = await fetch(`${servers.url}${path}`); |
| 34 | 35 | const body = await res.text(); |
| 35 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 36 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 36 | 37 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 37 | 38 | } |
| 38 | 39 |
test/api-tags-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -54,7 +55,7 @@ afterAll(async () => { | ||
| 54 | 55 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 55 | 56 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 56 | 57 | const body = await res.text(); |
| 57 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 58 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 58 | 59 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 59 | 60 | } |
| 60 | 61 |
test/api-threads-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -67,7 +68,7 @@ afterAll(async () => { | ||
| 67 | 68 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 68 | 69 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 69 | 70 | const body = await res.text(); |
| 70 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 71 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 71 | 72 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 72 | 73 | } |
| 73 | 74 |
test/api-upstream-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-usage-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -49,7 +50,7 @@ afterAll(async () => { | ||
| 49 | 50 | async function phases(path: string): Promise<{ status: number; names: string[]; body: string }> { |
| 50 | 51 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 51 | 52 | const body = await res.text(); |
| 52 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 53 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 53 | 54 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 54 | 55 | } |
| 55 | 56 |
test/api-waves.test.ts +2 −1
| @@ -12,6 +12,7 @@ import { serve, type Servers } from "../src/http/server.ts"; | ||
| 12 | 12 | import { writeWave, type Wave } from "../src/story/wave.ts"; |
| 13 | 13 | import { quietLogs } from "./helpers/globals.ts"; |
| 14 | 14 | import { mergedPull, waveNaming } from "./helpers/wave-pulls.ts"; |
| 15 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 15 | 16 | |
| 16 | 17 | quietLogs(); |
| 17 | 18 | |
| @@ -113,7 +114,7 @@ async function get(path: string, withCookie: boolean | string = true) { | ||
| 113 | 114 | headers: withCookie === false ? {} : { cookie: withCookie === true ? cookie : withCookie }, |
| 114 | 115 | }); |
| 115 | 116 | const body = await res.text(); |
| 116 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 117 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 117 | 118 | return { status: res.status, names: names.slice(names.indexOf("app") + 1), body }; |
| 118 | 119 | } |
| 119 | 120 |
test/app-read-timing.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { AppsRuntime } from "../src/apps/runtime.ts"; | ||
| 8 | 8 | import { serve, type Servers } from "../src/http/server.ts"; |
| 9 | 9 | import { seedAdmin } from "./helpers/admin.ts"; |
| 10 | 10 | import { tokenFor } from "./helpers/tokens.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | let dataDir = ""; |
| 13 | 14 | let app: App, servers: Servers, bearer = ""; |
| @@ -21,7 +22,7 @@ const call = (path: string, init: RequestInit = {}, token = bearer) => | ||
| 21 | 22 | async function phases(path: string, token = bearer): Promise<{ status: number; names: string[] }> { |
| 22 | 23 | const res = await call(path, {}, token); |
| 23 | 24 | await res.arrayBuffer(); |
| 24 | const all = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 25 | const all = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 25 | 26 | // The server's own leading `app` (and `db`) entries are not route phases. |
| 26 | 27 | return { status: res.status, names: all.slice(all.indexOf("app") + 1) }; |
| 27 | 28 | } |
test/conflicts-timing.test.ts +2 −1
| @@ -10,6 +10,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 10 | 10 | import { createUser } from "../src/core/users.ts"; |
| 11 | 11 | import { serve, type Servers } from "../src/http/server.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 13 | 14 | |
| 14 | 15 | quietLogs(); |
| 15 | 16 | |
| @@ -71,7 +72,7 @@ test("a conflicted pull's resolve page names its phases after app, in order", as | ||
| 71 | 72 | const res = await fetch(`${servers.url}/alice/r/pulls/${number}/resolve`, { headers: { cookie } }); |
| 72 | 73 | expect(res.status).toBe(200); |
| 73 | 74 | expect(await res.text()).toContain("Block 1 of 1"); |
| 74 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 75 | const parts = requestTiming(res).split(", "); | |
| 75 | 76 | const names = parts.map((p) => p.split(";")[0]!); |
| 76 | 77 | expect(names.slice(names.indexOf("app") + 1)).toEqual([ |
| 77 | 78 | "session", "pull", "checkout", "status", "file", "crew", "render", |
test/fleet-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -32,7 +33,7 @@ afterAll(async () => { | ||
| 32 | 33 | async function phases(path: string): Promise<{ status: number; names: string[] }> { |
| 33 | 34 | const res = await fetch(`${servers.url}${path}`, { headers: { authorization: `Bearer ${bearer}` } }); |
| 34 | 35 | await res.arrayBuffer(); |
| 35 | const names = (res.headers.get("server-timing") ?? "").split(", ").filter(Boolean).filter((p) => !p.startsWith("app;")).map((p) => p.split(";")[0]!); | |
| 36 | const names = requestTiming(res).split(", ").filter(Boolean).filter((p) => !p.startsWith("app;")).map((p) => p.split(";")[0]!); | |
| 36 | 37 | return { status: res.status, names }; |
| 37 | 38 | } |
| 38 | 39 |
test/forks-api-drift.test.ts +2 −1
| @@ -8,6 +8,7 @@ import { createApp, type App } from "../src/app.ts"; | ||
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { promote } from "./helpers/members.ts"; |
| 11 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 11 | 12 | |
| 12 | 13 | let dataDir = ""; |
| 13 | 14 | let app: App, servers: Servers; |
| @@ -48,7 +49,7 @@ test("a fork one commit ahead reads ahead 1, behind 0, with the parent named", a | ||
| 48 | 49 | expect(page.items[0]!.updatedAt).not.toBeNull(); |
| 49 | 50 | expect(page.next_cursor).toBeNull(); |
| 50 | 51 | expect(page.parent).toBeNull(); // the source itself has no parent |
| 51 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 52 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 52 | 53 | expect(names).toEqual(["session", "forks", "drift", "render"]); |
| 53 | 54 | const down = await fetch(`${servers.url}/api/v1/repos/bob/app/forks`); |
| 54 | 55 | expect(((await down.json()) as { parent: string | null }).parent).toBe("alice/app"); |
test/forks-api-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -30,7 +31,7 @@ afterAll(async () => { | ||
| 30 | 31 | }); |
| 31 | 32 | |
| 32 | 33 | const names = (res: Response) => |
| 33 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 34 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 34 | 35 | |
| 35 | 36 | test("the forks list names session, forks, render in order", async () => { |
| 36 | 37 | const made = await fetch(`${servers.url}/api/v1/repos`, { |
test/forks-timing.test.ts +2 −1
| @@ -7,6 +7,7 @@ import { createApp, type App } from "../src/app.ts"; | ||
| 7 | 7 | import { createRepo } from "../src/actions/repos.ts"; |
| 8 | 8 | import { serve, type Servers } from "../src/http/server.ts"; |
| 9 | 9 | import { seedAdmin } from "./helpers/admin.ts"; |
| 10 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 10 | 11 | |
| 11 | 12 | let dataDir = ""; |
| 12 | 13 | let app: App, servers: Servers, bearer = ""; |
| @@ -34,7 +35,7 @@ afterAll(async () => { | ||
| 34 | 35 | }); |
| 35 | 36 | |
| 36 | 37 | const names = (res: Response): string[] => |
| 37 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 38 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 38 | 39 | |
| 39 | 40 | test("the forks page names session, repository, forks, fork-tree, lineage, drift and render, in order", async () => { |
| 40 | 41 | const res = await fetch(`${servers.url}/alice/notes/forks`, { headers: { authorization: `Bearer ${bearer}` } }); |
test/front-api-timing.test.ts +2 −1
| @@ -5,6 +5,7 @@ import { tmpdir } from "node:os"; | ||
| 5 | 5 | import { join } from "node:path"; |
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 8 | 9 | |
| 9 | 10 | let dataDir = ""; |
| 10 | 11 | let app: App, servers: Servers; |
| @@ -25,7 +26,7 @@ test("the front read names session, front, render in order", async () => { | ||
| 25 | 26 | const res = await fetch(`${servers.url}/api/v1/front`); |
| 26 | 27 | await res.arrayBuffer(); |
| 27 | 28 | expect(res.status).toBe(200); |
| 28 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 29 | const parts = requestTiming(res).split(", "); | |
| 29 | 30 | const names = parts.map((p) => p.split(";")[0]!).filter((n) => n !== "app"); |
| 30 | 31 | expect(names).toEqual(["session", "front", "render"]); |
| 31 | 32 | for (const p of parts) expect(Number(/dur=([\d.]+)/.exec(p)?.[1])).not.toBeNaN(); |
test/front-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { quietLogs } from "./helpers/globals.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | quietLogs(); |
| 11 | 12 | |
| @@ -30,7 +31,7 @@ async function read(): Promise<string[]> { | ||
| 30 | 31 | const res = await fetch(`${servers.url}/`, { redirect: "manual" }); |
| 31 | 32 | expect(res.status).toBe(200); |
| 32 | 33 | await res.text(); |
| 33 | return (res.headers.get("server-timing") ?? "").split(", "); | |
| 34 | return requestTiming(res).split(", "); | |
| 34 | 35 | } |
| 35 | 36 | |
| 36 | 37 | test("GET / signed out names session, key, shelf, tiles, render in order and covers the handler's share of app", async () => { |
test/helpers/request-timing.ts added +11
| @@ -0,0 +1,11 @@ | ||
| 1 | /** | |
| 2 | * A response's Server-Timing without the loop's `lag` token (#10288): the | |
| 3 | * phases the request itself named, for a test that lists them in order. | |
| 4 | * `lag` is the worst event-loop lag of the last 10 s, not this request's. | |
| 5 | */ | |
| 6 | export function requestTiming(res: { headers: Headers }): string { | |
| 7 | return (res.headers.get("server-timing") ?? "") | |
| 8 | .split(", ") | |
| 9 | .filter((p) => !p.startsWith("lag;")) | |
| 10 | .join(", "); | |
| 11 | } |
test/here-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -30,7 +31,7 @@ afterAll(async () => { | ||
| 30 | 31 | }); |
| 31 | 32 | |
| 32 | 33 | const phases = (res: Response) => |
| 33 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 34 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app"); | |
| 34 | 35 | |
| 35 | 36 | test("the here read names session, here, render in that order", async () => { |
| 36 | 37 | const res = await fetch(`${servers.url}/api/v1/here?path=/`, { headers: { authorization: `Bearer ${bearer}` } }); |
test/home-200.test.ts +2 −1
| @@ -5,6 +5,7 @@ import { assignIssue, openIssue } from "../src/actions/issues.ts"; | ||
| 5 | 5 | import { expectWork } from "./helpers/work.ts"; |
| 6 | 6 | import { statements } from "./helpers/statements.ts"; |
| 7 | 7 | import { snappyFixture, type SnappyFixture } from "../bench/snappy.ts"; |
| 8 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 8 | 9 | |
| 9 | 10 | let f: SnappyFixture; |
| 10 | 11 | |
| @@ -25,7 +26,7 @@ afterAll(async () => { | ||
| 25 | 26 | async function look(): Promise<{ phases: { name: string; ms: number }[] }> { |
| 26 | 27 | const res = await fetch(`${f.base}/`, { headers: { cookie: f.ownerCookie } }); |
| 27 | 28 | await res.arrayBuffer(); |
| 28 | const phases = (res.headers.get("server-timing") ?? "") | |
| 29 | const phases = requestTiming(res) | |
| 29 | 30 | .split(", ") |
| 30 | 31 | .map((p) => ({ name: p.split(";")[0]!, ms: Number(/dur=([\d.]+)/.exec(p)?.[1] ?? 0) })); |
| 31 | 32 | return { phases }; |
test/issues-list-timing.test.ts +2 −1
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | /** A repository's issues list names its Server-Timing phases in order: session, issues, closing, words, labels, render. */ |
| 2 | 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; |
| 3 | 3 | import { snappyFixture, type SnappyFixture } from "../bench/snappy.ts"; |
| 4 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 4 | 5 | |
| 5 | 6 | let f: SnappyFixture; |
| 6 | 7 | |
| @@ -19,7 +20,7 @@ for (const who of ["owner", "anonymous"] as const) { | ||
| 19 | 20 | }); |
| 20 | 21 | await res.arrayBuffer(); |
| 21 | 22 | expect(res.status).toBe(200); |
| 22 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 23 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 23 | 24 | expect(names.filter((n) => n !== "app")).toEqual(["session", "issues", "closing", "words", "labels", "render"]); |
| 24 | 25 | }); |
| 25 | 26 | } |
test/loop-worst.test.ts added +128
| @@ -0,0 +1,128 @@ | ||
| 1 | /** | |
| 2 | * The worst event-loop lag of the last 10 s (#10288): the window by hand, | |
| 3 | * then a real 150 ms block seen as `lag;dur` on the next response and as | |
| 4 | * `loopLagMs` on `/healthz`, and gone once the injected clock steps 10 s. | |
| 5 | */ | |
| 6 | ||
| 7 | import { afterAll, beforeAll, describe, expect, test } from "bun:test"; | |
| 8 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 9 | import { tmpdir } from "node:os"; | |
| 10 | import { join } from "node:path"; | |
| 11 | import { createApp, type App } from "../src/app.ts"; | |
| 12 | import { withLag } from "../src/core/route_timing.ts"; | |
| 13 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 14 | import { createLoopWorst, startLoopWorst, stopLoopWorst } from "../src/perf/loop_worst.ts"; | |
| 15 | ||
| 16 | function blockFor(ms: number): void { | |
| 17 | const until = performance.now() + ms; | |
| 18 | while (performance.now() < until) { | |
| 19 | // a synchronous job holding the thread, as the lab's stalls do | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | /** `lag;dur=<n>` out of a Server-Timing header, or null. */ | |
| 24 | function lagOf(header: string | null): number | null { | |
| 25 | const m = /(?:^|,\s*)lag;dur=([\d.]+)/.exec(header ?? ""); | |
| 26 | return m === null ? null : Number(m[1]); | |
| 27 | } | |
| 28 | ||
| 29 | describe("the window, by hand", () => { | |
| 30 | test("keeps the worst of the last ten one-second slots", () => { | |
| 31 | const w = createLoopWorst(); | |
| 32 | expect(w.worst(0)).toBe(0); | |
| 33 | w.note(3, 100); | |
| 34 | w.note(150, 2_500); | |
| 35 | w.note(7, 2_900); | |
| 36 | w.note(12, 9_000); | |
| 37 | expect(w.worst(9_999)).toBe(150); | |
| 38 | // Second 2 leaves the window when second 12 begins: slots 3..12 remain. | |
| 39 | expect(w.worst(11_999)).toBe(150); | |
| 40 | expect(w.worst(12_000)).toBe(12); | |
| 41 | expect(w.worst(18_999)).toBe(12); | |
| 42 | expect(w.worst(19_000)).toBe(0); | |
| 43 | }); | |
| 44 | ||
| 45 | test("a slot reused a lap later forgets what it held", () => { | |
| 46 | const w = createLoopWorst(); | |
| 47 | w.note(300, 1_000); | |
| 48 | w.note(2, 11_000); | |
| 49 | expect(w.worst(11_000)).toBe(2); | |
| 50 | }); | |
| 51 | ||
| 52 | test("refuses what is not a lateness", () => { | |
| 53 | const w = createLoopWorst(); | |
| 54 | for (const bad of [-1, Number.NaN, Number.POSITIVE_INFINITY]) w.note(bad, 0); | |
| 55 | w.note(5, Number.NaN); | |
| 56 | expect(w.worst(0)).toBe(0); | |
| 57 | }); | |
| 58 | ||
| 59 | test("a tick (note then worst) costs under 0.1 ms on average over 100k", () => { | |
| 60 | const w = createLoopWorst(); | |
| 61 | const ticks = 100_000; | |
| 62 | let sink = 0; | |
| 63 | const t0 = performance.now(); | |
| 64 | // Each tick: one wake 50 ms apart on the window clock, then one header read. | |
| 65 | for (let k = 0; k < ticks; k++) { | |
| 66 | w.note(k % 97, k * 50); | |
| 67 | sink += w.worst(k * 50); | |
| 68 | } | |
| 69 | const meanMs = (performance.now() - t0) / ticks; | |
| 70 | expect(sink).toBeGreaterThan(0); | |
| 71 | expect(meanMs).toBeLessThan(0.1); | |
| 72 | }); | |
| 73 | ||
| 74 | test("withLag appends one token and ignores a non-number", () => { | |
| 75 | expect(withLag("app;dur=3.0", 148.26)).toBe("app;dur=3.0, lag;dur=148.3"); | |
| 76 | expect(withLag("app;dur=3.0", Number.NaN)).toBe("app;dur=3.0"); | |
| 77 | }); | |
| 78 | }); | |
| 79 | ||
| 80 | describe("on every response", () => { | |
| 81 | let dataDir: string; | |
| 82 | let app: App; | |
| 83 | let servers: Servers; | |
| 84 | let skew = 0; | |
| 85 | ||
| 86 | beforeAll(async () => { | |
| 87 | dataDir = await mkdtemp(join(tmpdir(), "forge-loop-worst-")); | |
| 88 | app = createApp({ dataDir, port: 0 }); | |
| 89 | // Wake every 5 ms, so a 150 ms block reads as at least 145; the window | |
| 90 | // reads a clock the test can step. | |
| 91 | stopLoopWorst(); | |
| 92 | startLoopWorst({ clock: () => performance.now() + skew, everyMs: 5 }); | |
| 93 | servers = await serve(app); | |
| 94 | }); | |
| 95 | ||
| 96 | afterAll(async () => { | |
| 97 | stopLoopWorst(); | |
| 98 | servers.stop(); | |
| 99 | app.close(); | |
| 100 | await rm(dataDir, { recursive: true, force: true }); | |
| 101 | }); | |
| 102 | ||
| 103 | test("a 150 ms block shows as lag;dur ≥ 140 on the next response, then falls out", async () => { | |
| 104 | blockFor(150); | |
| 105 | // The overdue wake runs before this one: timers fire in due order. | |
| 106 | await new Promise((r) => setTimeout(r, 0)); | |
| 107 | const res = await fetch(`${servers.url}/nope`); | |
| 108 | await res.text(); | |
| 109 | const header = res.headers.get("server-timing"); | |
| 110 | expect(header).toContain("app;dur="); | |
| 111 | expect(lagOf(header)).toBeGreaterThanOrEqual(140); | |
| 112 | ||
| 113 | const health = await fetch(`${servers.url}/healthz`, { headers: { accept: "application/json" } }); | |
| 114 | const body = (await health.json()) as { ok: boolean; loopLagMs: number }; | |
| 115 | expect(body.ok).toBe(true); | |
| 116 | expect(body.loopLagMs).toBeGreaterThanOrEqual(140); | |
| 117 | // A probe asking for nothing still hears the plain `ok` (#2917). | |
| 118 | expect(await (await fetch(`${servers.url}/healthz`)).text()).toBe("ok"); | |
| 119 | ||
| 120 | // Ten seconds on the window's clock with no new wake: nothing is left in it. | |
| 121 | stopLoopWorst(); | |
| 122 | startLoopWorst({ clock: () => performance.now() + skew, everyMs: 60_000 }); | |
| 123 | skew += 10_000; | |
| 124 | const later = await fetch(`${servers.url}/nope`); | |
| 125 | await later.text(); | |
| 126 | expect(lagOf(later.headers.get("server-timing"))).toBe(0); | |
| 127 | }); | |
| 128 | }); |
test/map-api-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { addDependency } from "../src/core/dependencies.ts"; | ||
| 9 | 9 | import { createIssue } from "../src/core/issues.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { seedAdmin } from "./helpers/admin.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | let dataDir = ""; |
| 14 | 15 | let app: App, servers: Servers, bearer = ""; |
| @@ -50,7 +51,7 @@ test("the repository map names its phases in order, with durations", async () => | ||
| 50 | 51 | expect(res.status).toBe(200); |
| 51 | 52 | const body = (await res.json()) as { ready: unknown[] }; |
| 52 | 53 | expect(body.ready.length).toBeGreaterThan(0); |
| 53 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 54 | const parts = requestTiming(res).split(", "); | |
| 54 | 55 | const names = parts.map((p) => p.split(";")[0]!); |
| 55 | 56 | expect(names.filter((n) => n !== "app")).toEqual(["session", "graph", "marks", "pillars", "asktolive", "held", "render"]); |
| 56 | 57 | for (const p of parts) expect(Number(/dur=([\d.]+)/.exec(p)?.[1])).not.toBeNaN(); |
test/map-cache.test.ts +2 −1
| @@ -19,6 +19,7 @@ import type { Graph } from "../src/core/dependencies.ts"; | ||
| 19 | 19 | import { seedFixture, type Fixture, type Shape } from "../bench/map-home.ts"; |
| 20 | 20 | import { countStatements } from "../bench/run.ts"; |
| 21 | 21 | import { quietLogs } from "./helpers/globals.ts"; |
| 22 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 22 | 23 | |
| 23 | 24 | // `seedFixture` raises the log floor and never lowers it: borrowed for |
| 24 | 25 | // this file, and given back to whichever file runs next (#7419). |
| @@ -275,7 +276,7 @@ describe("/map's Server-Timing names its phases", () => { | ||
| 275 | 276 | const cookie = who === "owner" ? f.ownerCookie : f.strangerCookie; |
| 276 | 277 | for (const pass of ["cold", "warm"]) { |
| 277 | 278 | const res = await fetch(`${servers.url}/map`, { headers: { cookie } }); |
| 278 | const header = res.headers.get("server-timing") ?? ""; | |
| 279 | const header = requestTiming(res); | |
| 279 | 280 | const dur = new Map( |
| 280 | 281 | [...header.matchAll(/([a-z-]+);dur=(\d+\.\d)/g)].map((m) => [m[1]!, Number(m[2])]), |
| 281 | 282 | ); |
test/map-timing.test.ts +2 −1
| @@ -5,6 +5,7 @@ import { tmpdir } from "node:os"; | ||
| 5 | 5 | import { join } from "node:path"; |
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 8 | 9 | |
| 9 | 10 | let dataDir = ""; |
| 10 | 11 | let app: App, servers: Servers; |
| @@ -25,6 +26,6 @@ test("the forge-wide map names session, graph, render in order", async () => { | ||
| 25 | 26 | const res = await fetch(`${servers.url}/api/v1/map`); |
| 26 | 27 | await res.arrayBuffer(); |
| 27 | 28 | expect(res.status).toBe(200); |
| 28 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]); | |
| 29 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]); | |
| 29 | 30 | expect(names.filter((n) => n !== "app")).toEqual(["session", "graph", "render"]); |
| 30 | 31 | }); |
test/new-pull-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -58,6 +59,6 @@ test("the new pull page names session, compare and render after app", async () = | ||
| 58 | 59 | const res = await fetch(`${servers.url}/alice/r/pulls/new`, { headers: { cookie } }); |
| 59 | 60 | expect(res.status).toBe(200); |
| 60 | 61 | await res.arrayBuffer(); |
| 61 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 62 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 62 | 63 | expect(names.slice(names.indexOf("app") + 1)).toEqual(["session", "compare", "render"]); |
| 63 | 64 | }); |
test/og-timing.test.ts +2 −1
| @@ -7,6 +7,7 @@ import { createApp, type App } from "../src/app.ts"; | ||
| 7 | 7 | import { createRepo } from "../src/actions/repos.ts"; |
| 8 | 8 | import { createUser } from "../src/core/users.ts"; |
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 10 | 11 | |
| 11 | 12 | setDefaultTimeout(30_000); |
| 12 | 13 | |
| @@ -39,7 +40,7 @@ afterAll(async () => { | ||
| 39 | 40 | async function get(path: string, headers: Record<string, string> = {}) { |
| 40 | 41 | const res = await fetch(servers.url + path, { headers }); |
| 41 | 42 | await res.arrayBuffer(); |
| 42 | const names = (res.headers.get("server-timing") ?? "") | |
| 43 | const names = requestTiming(res) | |
| 43 | 44 | .split(", ") |
| 44 | 45 | .map((p) => /^([a-z-]+);/.exec(p)?.[1]) |
| 45 | 46 | .filter((n): n is string => n !== undefined && n !== "app"); |
test/org-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -39,7 +40,7 @@ async function phases(path: string, status = 200): Promise<string[]> { | ||
| 39 | 40 | const res = await fetch(`${servers.url}${path}`, { headers: auth() }); |
| 40 | 41 | await res.arrayBuffer(); |
| 41 | 42 | expect(res.status).toBe(status); |
| 42 | return (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app" && n !== "db"); | |
| 43 | return requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app" && n !== "db"); | |
| 43 | 44 | } |
| 44 | 45 | |
| 45 | 46 | test("an org names session, org, render", async () => { |
test/passport-health-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { issue } from "../src/kernel/passport.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers; |
| @@ -23,7 +24,7 @@ afterAll(async () => { | ||
| 23 | 24 | }); |
| 24 | 25 | |
| 25 | 26 | const phases = (res: Response) => |
| 26 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n && n !== "app"); | |
| 27 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n && n !== "app"); | |
| 27 | 28 | |
| 28 | 29 | const ask = (token?: string) => |
| 29 | 30 | fetch(`${servers.url}/api/v1/passport/health`, { |
test/pull-page-phases.test.ts +2 −1
| @@ -23,6 +23,7 @@ import { serve, type Servers } from "../src/http/server.ts"; | ||
| 23 | 23 | import { quietLogs } from "./helpers/globals.ts"; |
| 24 | 24 | import { PULL_PAGE_PHASES } from "./helpers/pull-phases.ts"; |
| 25 | 25 | import { PNG } from "./helpers/studio.ts"; |
| 26 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 26 | 27 | |
| 27 | 28 | quietLogs(); |
| 28 | 29 | |
| @@ -101,7 +102,7 @@ async function timed(): Promise<{ names: string[]; ms: (n: string) => number; bo | ||
| 101 | 102 | const res = await fetch(`${servers.url}/alice/r/pulls/${number}`, { headers: { cookie } }); |
| 102 | 103 | expect(res.status).toBe(200); |
| 103 | 104 | const body = await res.text(); |
| 104 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 105 | const parts = requestTiming(res).split(", "); | |
| 105 | 106 | const all = parts.map((p) => p.split(";")[0]!); |
| 106 | 107 | const ms = (n: string) => Number(parts[all.indexOf(n)]!.match(/dur=([\d.]+)/)![1]); |
| 107 | 108 | return { names: all.slice(all.indexOf("app") + 1), ms, body }; |
test/pull-timing.test.ts +3 −2
| @@ -14,6 +14,7 @@ import { createUser } from "../src/core/users.ts"; | ||
| 14 | 14 | import { serve, type Servers } from "../src/http/server.ts"; |
| 15 | 15 | import { quietLogs } from "./helpers/globals.ts"; |
| 16 | 16 | import { PULL_PAGE_PHASES } from "./helpers/pull-phases.ts"; |
| 17 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 17 | 18 | |
| 18 | 19 | quietLogs(); |
| 19 | 20 | |
| @@ -105,7 +106,7 @@ test("a pull names its read phases and render after app, summing to no more than | ||
| 105 | 106 | const res = await fetch(`${servers.url}/alice/r/pulls/${number}`, { headers: { cookie } }); |
| 106 | 107 | expect(res.status).toBe(200); |
| 107 | 108 | await res.arrayBuffer(); |
| 108 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 109 | const parts = requestTiming(res).split(", "); | |
| 109 | 110 | const names = parts.map((p) => p.split(";")[0]!); |
| 110 | 111 | const after = names.slice(names.indexOf("app") + 1); |
| 111 | 112 | expect(after).toEqual([...PULL_PAGE_PHASES]); |
| @@ -124,7 +125,7 @@ test("a pull's Files page names session, diff, checks, reviews and render, in th | ||
| 124 | 125 | const html = await res.text(); |
| 125 | 126 | expect(html).toContain("m39.ts"); |
| 126 | 127 | expect(html).not.toContain("<b>1</b>"); |
| 127 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 128 | const parts = requestTiming(res).split(", "); | |
| 128 | 129 | const names = parts.map((p) => p.split(";")[0]!); |
| 129 | 130 | expect(names.slice(names.indexOf("app") + 1)).toEqual(["session", "diff", "diff-git", "diff-parse", "diff-highlight", "git", "checks", "reviews", "render"]); |
| 130 | 131 | }); |
test/pulls-list-timing.test.ts +2 −1
| @@ -7,6 +7,7 @@ import { createApp, type App } from "../src/app.ts"; | ||
| 7 | 7 | import { createRepo } from "../src/actions/repos.ts"; |
| 8 | 8 | import { serve, type Servers } from "../src/http/server.ts"; |
| 9 | 9 | import { seedAdmin } from "./helpers/admin.ts"; |
| 10 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 10 | 11 | |
| 11 | 12 | let dataDir = ""; |
| 12 | 13 | let app: App, servers: Servers, bearer = ""; |
| @@ -34,7 +35,7 @@ afterAll(async () => { | ||
| 34 | 35 | }); |
| 35 | 36 | |
| 36 | 37 | function names(res: Response): string[] { |
| 37 | return (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 38 | return requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 38 | 39 | } |
| 39 | 40 | |
| 40 | 41 | test("a repository's pulls list names session, repo, pulls, words, tells, labels, steps and render in order", async () => { |
test/repo-pages-timing.test.ts +2 −1
| @@ -11,6 +11,7 @@ import { serve, type Servers } from "../src/http/server.ts"; | ||
| 11 | 11 | import { proveAll } from "./helpers/verified.ts"; |
| 12 | 12 | import { quietLogs } from "./helpers/globals.ts"; |
| 13 | 13 | import { promote } from "./helpers/members.ts"; |
| 14 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 14 | 15 | |
| 15 | 16 | quietLogs(); |
| 16 | 17 | |
| @@ -79,7 +80,7 @@ for (const [path, who, names] of PAGES) | ||
| 79 | 80 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 80 | 81 | await res.text(); |
| 81 | 82 | expect(res.status).toBe(200); |
| 82 | return (res.headers.get("server-timing") ?? "").split(", "); | |
| 83 | return requestTiming(res).split(", "); | |
| 83 | 84 | }; |
| 84 | 85 | const dur = (part: string | undefined): number => { |
| 85 | 86 | expect(part).toMatch(/^[a-z-]+;dur=\d+\.\d$/); |
test/repo-reads-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -19,7 +20,7 @@ const call = (path: string, init: RequestInit = {}) => | ||
| 19 | 20 | const phases = async (path: string): Promise<{ status: number; names: string[] }> => { |
| 20 | 21 | const res = await call(path); |
| 21 | 22 | await res.arrayBuffer(); |
| 22 | const names = (res.headers.get("server-timing") ?? "").split(", ").filter(Boolean).map((p) => p.split(";")[0]!).filter((n) => n !== "db" && n !== "app"); | |
| 23 | const names = requestTiming(res).split(", ").filter(Boolean).map((p) => p.split(";")[0]!).filter((n) => n !== "db" && n !== "app"); | |
| 23 | 24 | return { status: res.status, names }; |
| 24 | 25 | }; |
| 25 | 26 |
test/repo-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createSession } from "../src/core/sessions.ts"; | ||
| 9 | 9 | import { createUser } from "../src/core/users.ts"; |
| 10 | 10 | import { serve, type Servers } from "../src/http/server.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -55,6 +56,6 @@ test("a repository home names session, appof, refs, tree, readme, following, wav | ||
| 55 | 56 | const res = await fetch(`${servers.url}/alice/r`, { headers: { cookie } }); |
| 56 | 57 | expect(res.status).toBe(200); |
| 57 | 58 | await res.arrayBuffer(); |
| 58 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 59 | const names = requestTiming(res).split(", ").map((p) => p.split(";")[0]!); | |
| 59 | 60 | expect(names).toEqual(["app", "session", "appof", "refs", "tree", "readme", "following", "wave", "behind", "render"]); |
| 60 | 61 | }); |
test/route-timing.test.ts +2 −2
| @@ -46,8 +46,8 @@ test("a response carries Server-Timing, and /alice/x and /bob/y share one route | ||
| 46 | 46 | const a = await fetch(`${servers.url}/alice/x`); |
| 47 | 47 | await fetch(`${servers.url}/bob/y`); |
| 48 | 48 | const health = await fetch(`${servers.url}/healthz`); |
| 49 | expect(a.headers.get("server-timing")).toMatch(/^app;dur=\d+\.\d$/); | |
| 50 | expect(health.headers.get("server-timing")).toMatch(/^db;dur=\d+\.\d, app;dur=\d+\.\d$/); | |
| 49 | expect(a.headers.get("server-timing")).toMatch(/^app;dur=\d+\.\d, lag;dur=\d+\.\d$/); | |
| 50 | expect(health.headers.get("server-timing")).toMatch(/^db;dur=\d+\.\d, app;dur=\d+\.\d, lag;dur=\d+\.\d$/); | |
| 51 | 51 | servers.stop(); |
| 52 | 52 | |
| 53 | 53 | const rows = readTiming(app.db, ""); |
test/settings-timing.test.ts +2 −1
| @@ -9,6 +9,7 @@ import { createUser } from "../src/core/users.ts"; | ||
| 9 | 9 | import { serve, type Servers } from "../src/http/server.ts"; |
| 10 | 10 | import { proveAll } from "./helpers/verified.ts"; |
| 11 | 11 | import { quietLogs } from "./helpers/globals.ts"; |
| 12 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 12 | 13 | |
| 13 | 14 | quietLogs(); |
| 14 | 15 | |
| @@ -65,7 +66,7 @@ for (const [path, names] of PAGES) | ||
| 65 | 66 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); |
| 66 | 67 | await res.text(); |
| 67 | 68 | expect(res.status).toBe(200); |
| 68 | const parts = (res.headers.get("server-timing") ?? "").split(", "); | |
| 69 | const parts = requestTiming(res).split(", "); | |
| 69 | 70 | expect(parts.map((p) => p.split(";")[0]).filter((n) => n !== "app")).toEqual(names); |
| 70 | 71 | for (const name of names) expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/); |
| 71 | 72 | } |
test/telemetry-reads-timing.test.ts +2 −1
| @@ -6,6 +6,7 @@ import { join } from "node:path"; | ||
| 6 | 6 | import { createApp, type App } from "../src/app.ts"; |
| 7 | 7 | import { serve, type Servers } from "../src/http/server.ts"; |
| 8 | 8 | import { seedAdmin } from "./helpers/admin.ts"; |
| 9 | import { requestTiming } from "./helpers/request-timing.ts"; | |
| 9 | 10 | |
| 10 | 11 | let dataDir = ""; |
| 11 | 12 | let app: App, servers: Servers, bearer = ""; |
| @@ -30,7 +31,7 @@ afterAll(async () => { | ||
| 30 | 31 | }); |
| 31 | 32 | |
| 32 | 33 | const phases = (res: Response) => |
| 33 | (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n && n !== "app"); | |
| 34 | requestTiming(res).split(", ").map((p) => p.split(";")[0]!).filter((n) => n && n !== "app"); | |
| 34 | 35 | |
| 35 | 36 | for (const what of ["system", "history", "flow", "sources"]) { |
| 36 | 37 | test(`telemetry/${what} names session, read, render in order`, async () => { |
The sub-0.1 ms tick cost is only asserted in prose here. Add a test that bounds it (see the review body).