// Boot benchmark: time from navigation to a guest's exit, and the peak resident memory // of the whole browser (every process, so the page, its workers and the SW count), // sampled from `ps` every 10ms. Also prints the wire size of each dist/runtime/ asset. // Usage: node scripts/bench-boot.mjs [page=/demo/index.html] [runs=7] // Needs the dev server (npm run serve) on :8931. import { chromium } from "@playwright/test"; import { execFileSync } from "node:child_process"; import { readdirSync, readFileSync, statSync } from "node:fs"; import { brotliCompressSync, gzipSync } from "node:zlib"; const PAGE = process.argv[2] ?? "/demo/index.html"; const RUNS = Number(process.argv[3] ?? 7); const BASE = process.env.BASE ?? "http://localhost:8931"; function treeRssKB(root) { const rows = execFileSync("ps", ["-A", "-o", "pid=,ppid=,rss="], { encoding: "utf8" }) .trim().split("\n").map((l) => l.trim().split(/\s+/).map(Number)); const kids = new Map(); for (const [pid, ppid] of rows) (kids.get(ppid) ?? kids.set(ppid, []).get(ppid)).push(pid); const rss = new Map(rows.map(([pid, , r]) => [pid, r])); let sum = 0; const stack = [root]; while (stack.length) { const p = stack.pop(); sum += rss.get(p) ?? 0; stack.push(...(kids.get(p) ?? [])); } return sum; } const server = await chromium.launchServer(); const browser = await chromium.connect(server.wsEndpoint()); const pid = server.process().pid; const rows = []; for (let i = 0; i < RUNS; i++) { const ctx = await browser.newContext(); const page = await ctx.newPage(); await page.goto(BASE + "/demo/demo.css"); // settle a renderer first so its baseline is not counted as boot const base = treeRssKB(pid); let peak = base; let sampling = true; const sampler = (async () => { while (sampling) { peak = Math.max(peak, treeRssKB(pid)); await new Promise((r) => setTimeout(r, 10)); } })(); const t0 = performance.now(); await page.goto(BASE + PAGE); await page.waitForFunction(() => { const t = document.getElementById("exit")?.textContent; return t && t !== "…"; }, null, { timeout: 60_000, polling: 5 }); const ms = Math.round(performance.now() - t0); await new Promise((r) => setTimeout(r, 200)); sampling = false; await sampler; rows.push({ ms, peakMB: Math.round((peak - base) / 1024), exit: await page.locator("#exit").textContent() }); await ctx.close(); } await browser.close(); await server.close(); const med = (k) => rows.map((r) => r[k]).sort((a, b) => a - b)[Math.floor(rows.length / 2)]; console.log(rows.map((r) => `${r.ms}ms/${r.peakMB}MB/exit ${r.exit}`).join(" ")); console.log(`median boot→exit ${med("ms")}ms · peak RSS over baseline ${med("peakMB")}MB`); for (const f of readdirSync("dist/runtime")) { if (!statSync(`dist/runtime/${f}`).isFile()) continue; const b = readFileSync(`dist/runtime/${f}`); console.log(` ${f.padEnd(14)} ${String(Math.round(b.length / 1024)).padStart(6)}KB raw ${String(Math.round(gzipSync(b, { level: 9 }).length / 1024)).padStart(6)}KB gz ${String(Math.round(brotliCompressSync(b).length / 1024)).padStart(6)}KB br`); }