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

/system lists each first-opened page's warm median beside its budget, one plain line per page #11567

Parked opened this
Closed without merging

scout/forge:scout/11544-system-lists-each-first-opened-page-s-wa → main 7 commits fork

  • Closed without merging. Nothing was applied to main.

Review

Sign in to review this change.
7 files changed +94 −2
docs/features/page-budget.md added +3
@@ -0,0 +1,3 @@
1 # Page budget on /system
2
3 /system lists each first-opened page from `PAGE_BUDGETS` with its warm median of the last 15 minutes (from the in-memory perf record, no SQL) beside its budget, one plain line per page. A page over its budget reads "map answers in 82 ms, budget 50 ms"; a page nobody opened reads "not opened in the last 15 minutes". `src/perf/page_budget_read.ts` makes the rows.
scripts/unreached-allow.json −1
@@ -1,5 +1,4 @@
11 [
2 "src/perf/page_budget.ts",
32 "src/spawn/pod.ts",
43 "src/tui/ui/qr.ts",
54 "src/web/studio/held_posts.ts"
src/perf/page_budget_read.ts added +29
@@ -0,0 +1,29 @@
1 /**
2 * The page budget read against what the forge measured (#11544): one row per
3 * page in PAGE_BUDGETS order, with the warm median of the last 15 minutes from
4 * the in-memory perf record. A page nobody opened reads `medianMs: null`, never 0.
5 * Pure: the caller hands over the samples it already holds, no SQL.
6 */
7 import { PAGE_BUDGETS } from "./page_budget.ts";
8 import { WINDOW_MS, summarize, type Sample } from "./record.ts";
9
10 export interface PageBudgetRow {
11 readonly page: string;
12 readonly pattern: string;
13 readonly budgetMs: number;
14 readonly statements: number;
15 readonly medianMs: number | null;
16 readonly n: number;
17 /** The median is past the budget. */
18 readonly over: boolean;
19 }
20
21 /** Samples keyed by router pattern; only those within the window count. */
22 export function pageBudgetRows(samples: ReadonlyMap<string, readonly Sample[]>, now: number): PageBudgetRow[] {
23 const since = now - WINDOW_MS;
24 return PAGE_BUDGETS.map((b) => {
25 const stat = summarize((samples.get(b.pattern) ?? []).filter((s) => s.at >= since));
26 const medianMs = stat === null ? null : stat.p50;
27 return { page: b.page, pattern: b.pattern, budgetMs: b.medianMs, statements: b.statements, medianMs, n: stat?.n ?? 0, over: medianMs !== null && medianMs > b.medianMs };
28 });
29 }
src/web/pages/system-budget.ts added +20
@@ -0,0 +1,20 @@
1 /** /system's page-budget section (#11544): each first-opened page's warm median beside its budget, one plain line per page. */
2 import type { PageBudgetRow } from "../../perf/page_budget_read.ts";
3 import { html, type Html } from "../html.ts";
4
5 /** One line in words; an over-budget page says so plainly, no banner. */
6 export function budgetLine(r: PageBudgetRow): string {
7 if (r.medianMs === null) return `${r.page}: not opened in the last 15 minutes (budget ${String(r.budgetMs)} ms)`;
8 const ms = String(Math.round(r.medianMs));
9 return r.over
10 ? `${r.page} answers in ${ms} ms, budget ${String(r.budgetMs)} ms`
11 : `${r.page} answers in ${ms} ms, within its ${String(r.budgetMs)} ms budget`;
12 }
13
14 export function budgetSection(rows: readonly PageBudgetRow[]): Html {
15 const over = rows.filter((r) => r.over).length;
16 const head = over === 0 ? "Page budgets: all met" : `Page budgets: ${String(over)} over`;
17 return html`<section class="settings-block" id="page-budget"><h2>First-opened pages against their budget</h2>
18 <details data-over="${String(over)}"><summary>${head}</summary>
19 <ul class="list" aria-label="Warm median of each first-opened page beside its budget">${rows.map((r) => html`<li data-page="${r.page}" data-over="${r.over ? "1" : "0"}">${budgetLine(r)}</li>`)}</ul></details></section>`;
20 }
src/web/pages/system.ts +4 −1
@@ -33,7 +33,9 @@ import { readerPaces, type ReaderPace } from "../../system/telemetry_reads.ts";
3333 import { slowPageLine, slowPages, type SlowPage } from "../../perf/watch.ts";
3434 import { stallHour } from "../../metrics/stalls.ts";
3535 import { HOUR_MS, type RouteStall } from "../../perf/loop.ts";
36 import { lagMinutes, loopWatch, stallBlame } from "../../perf/live.ts";
36 import { lagMinutes, loopWatch, perfRecord, stallBlame } from "../../perf/live.ts";
37 import { pageBudgetRows } from "../../perf/page_budget_read.ts";
38 import { budgetSection } from "./system-budget.ts";
3739 import { LAG_BAR_MS, LAG_READINGS, overBar, type LagReading } from "../../perf/lag_minutes.ts";
3840 import { WORST_STALLS, type BlamedStall } from "../../perf/stall_blame.ts";
3941 import { notePhase, takeSessionTime, timePhase } from "../../core/route_timing.ts";
@@ -404,6 +406,7 @@ export async function systemPage(ctx: Ctx): Promise<Response> {
404406 ${featuresSection(timePhase(ctx.request, "features", () => featureHealth(ctx.app, reader)))}
405407 ${lagSection(lagMinutes.readings())}
406408 <div id="sys-stalls">${stallSection(stalls, worstStallsTable(worst, nowMono, Date.now()))}</div>
409 ${budgetSection(pageBudgetRows(new Map(perfRecord.patterns().map((p) => [p, perfRecord.traffic(p, 0)])), Date.now()))}
407410 <div id="sys-slow-pages">${slowPagesSection(timePhase(ctx.request, "slowpages", () => slowPagesCached(ctx.app.db)))}</div>
408411 <div id="sys-readers">${readersSection(readerPaces())}</div>
409412 <div id="sys-caches">${cachesSection(pageCaches())}</div>
test/page-budget-read.test.ts added +37
@@ -0,0 +1,37 @@
1 import { expect, test } from "bun:test";
2 import { PAGE_BUDGETS } from "../src/perf/page_budget.ts";
3 import { pageBudgetRows } from "../src/perf/page_budget_read.ts";
4 import { WINDOW_MS, type Sample } from "../src/perf/record.ts";
5 import { budgetLine, budgetSection } from "../src/web/pages/system-budget.ts";
6
7 const NOW = 10_000_000;
8 const s = (ms: number, at = NOW - 1000): Sample => ({ at, ms, signedIn: true, phases: [] });
9
10 test("rows follow PAGE_BUDGETS; unsampled reads null, never 0", () => {
11 const rows = pageBudgetRows(new Map(), NOW);
12 expect(rows.map((r) => r.pattern)).toEqual(PAGE_BUDGETS.map((b) => b.pattern));
13 for (const r of rows) {
14 expect(r.medianMs).toBeNull();
15 expect(r.n).toBe(0);
16 expect(r.over).toBe(false);
17 }
18 });
19
20 test("over is true only past the budget; old samples do not count", () => {
21 const budget = PAGE_BUDGETS[0]!;
22 const at = (ms: number) => pageBudgetRows(new Map([[budget.pattern, [s(ms), s(ms), s(ms)]]]), NOW)[0]!;
23 expect(at(budget.medianMs).over).toBe(false);
24 expect(at(budget.medianMs + 1).over).toBe(true);
25 expect(at(82).medianMs).toBe(82);
26 const old = pageBudgetRows(new Map([[budget.pattern, [s(500, NOW - WINDOW_MS - 1)]]]), NOW)[0]!;
27 expect(old.medianMs).toBeNull();
28 });
29
30 test("the section shows each page name and says it in words", () => {
31 const rows = pageBudgetRows(new Map([["/map", [s(82)]]]), NOW);
32 const out = String(budgetSection(rows));
33 for (const b of PAGE_BUDGETS) expect(out).toContain(b.page);
34 expect(out).toContain("map answers in 82 ms, budget 50 ms");
35 expect(out).toContain("not opened in the last 15 minutes");
36 expect(budgetLine(rows[0]!)).toContain("not opened");
37 });
test/system-parts.test.ts +1
@@ -54,6 +54,7 @@ test("one <h2> per part, each in a settings-block, and no card", async () => {
5454 "Processes",
5555 "Loop lag, last 20 minutes",
5656 "Main-thread stalls, last hour",
57 "First-opened pages against their budget",
5758 "Telemetry readers",
5859 "Code page caches, since start",
5960 "Forge memory",

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs