887 lines · 29.2 KB · JavaScript
RawDownload
import { spawn, execFileSync } from "node:child_process";import fs from "node:fs";import os from "node:os";import path from "node:path"; const repoRoot = path.resolve(import.meta.dirname, "..");const marker = "<!-- desktop-perf-trace-summary -->";const defaultSeedRelativePath = "apps/desktop/scripts/perf-seed.json";const defaultPerfProjects = [  {    id: "perf-project-1",    cwd: "/tmp/perf/codething-mvp",    name: "codething-mvp",  },  {    id: "perf-project-2",    cwd: "/tmp/perf/contracts-bench",    name: "contracts-bench",  },]; function sleep(ms) {  return new Promise((resolve) => setTimeout(resolve, ms));} function parseArgs(argv) {  let postPr = false;  let seedPath = null;   for (let index = 2; index < argv.length; index += 1) {    const arg = argv[index];    if (arg === "--post-pr") {      postPr = true;      continue;    }    if (arg === "--seed") {      const value = argv[index + 1];      if (!value || value.startsWith("--")) {        throw new Error("--seed requires a file path");      }      seedPath = value;      index += 1;      continue;    }    if (arg.startsWith("--seed=")) {      const value = arg.slice("--seed=".length).trim();      if (value.length === 0) {        throw new Error("--seed= requires a non-empty path");      }      seedPath = value;      continue;    }    throw new Error(`Unknown argument: ${arg}`);  }   return {    postPr,    seedPath,  };} function toMs(us) {  return us / 1000;} function round(n, digits = 3) {  return Number(n.toFixed(digits));} function mb(bytes) {  return bytes / (1024 * 1024);} function parseNumericEnv({ name, value, predicate, expectation, fallback }) {  if (typeof value !== "string") {    return null;  }  const raw = value.trim();  if (raw.length === 0) {    console.warn(      `[desktop-perf] invalid ${name}="${value}" (expected ${expectation}); using ${fallback}`,    );    return null;  }  const parsed = Number(raw);  if (Number.isFinite(parsed) && predicate(parsed)) {    return {      parsed,      raw,    };  }  console.warn(    `[desktop-perf] invalid ${name}="${value}" (expected ${expectation}); using ${fallback}`,  );  return null;} function resolveNonNegativeNumberEnv({ name, value, fallback }) {  const resolved = parseNumericEnv({    name,    value,    predicate: (parsed) => parsed >= 0,    expectation: "non-negative number",    fallback,  });  return resolved?.parsed ?? fallback;} function resolveNonNegativeIntegerEnv({ name, value, fallback }) {  const resolved = parseNumericEnv({    name,    value,    predicate: (parsed) => parsed >= 0,    expectation: "non-negative number",    fallback,  });  if (!resolved) {    return fallback;  }  const normalized = Math.trunc(resolved.parsed);  if (normalized !== resolved.parsed) {    console.warn(      `[desktop-perf] non-integer ${name}="${resolved.raw}" truncated to ${normalized} for consistency`,    );  }  return normalized;} function resolvePositiveIntegerEnv({ name, value, fallback }) {  const resolved = parseNumericEnv({    name,    value,    predicate: (parsed) => parsed > 0,    expectation: "positive number",    fallback,  });  if (!resolved) {    return fallback;  }  const normalized = Math.trunc(resolved.parsed);  if (normalized !== resolved.parsed) {    console.warn(      `[desktop-perf] non-integer ${name}="${resolved.raw}" truncated to ${normalized} for consistency`,    );  }  return normalized;} function summarizeTrace(tracePath) {  const payload = JSON.parse(fs.readFileSync(tracePath, "utf8"));  const events = payload.traceEvents;   const durationByName = new Map();  const dispatchRows = [];  const functionRows = [];  const userTiming = new Map();  const updateCounters = [];   for (const event of events) {    if (event.name === "UpdateCounters" && event.args?.data) {      const heap = event.args.data.jsHeapSizeUsed;      if (typeof heap === "number") {        updateCounters.push(heap);      }    }     if (event.cat === "blink.user_timing" && typeof event.name === "string") {      userTiming.set(event.name, (userTiming.get(event.name) ?? 0) + 1);    }     if (event.ph !== "X" || typeof event.dur !== "number" || typeof event.name !== "string") {      continue;    }     const byName = durationByName.get(event.name) ?? { count: 0, dur: 0, max: 0 };    byName.count += 1;    byName.dur += event.dur;    byName.max = Math.max(byName.max, event.dur);    durationByName.set(event.name, byName);     if (event.name === "EventDispatch") {      const type = event.args?.data?.type ?? "";      dispatchRows.push({ type, dur: event.dur });    }     if (event.name === "FunctionCall") {      const functionName = event.args?.data?.functionName ?? "(unknown)";      functionRows.push({ functionName, dur: event.dur });    }  }   const aggregateDispatchType = (type) => {    const rows = dispatchRows.filter((row) => row.type === type);    if (rows.length === 0) {      return {        count: 0,        totalMs: 0,        avgMs: 0,        maxMs: 0,      };    }    const totalDur = rows.reduce((sum, row) => sum + row.dur, 0);    const maxDur = rows.reduce((max, row) => Math.max(max, row.dur), 0);    return {      count: rows.length,      totalMs: round(toMs(totalDur)),      avgMs: round(toMs(totalDur / rows.length)),      maxMs: round(toMs(maxDur)),    };  };   const aggregateFunction = (name) => {    const rows = functionRows.filter((row) => row.functionName === name);    if (rows.length === 0) {      return {        count: 0,        totalMs: 0,        avgMs: 0,        maxMs: 0,      };    }    const totalDur = rows.reduce((sum, row) => sum + row.dur, 0);    const maxDur = rows.reduce((max, row) => Math.max(max, row.dur), 0);    return {      count: rows.length,      totalMs: round(toMs(totalDur)),      avgMs: round(toMs(totalDur / rows.length)),      maxMs: round(toMs(maxDur)),    };  };   const topUserTiming = [...userTiming.entries()]    .toSorted((a, b) => b[1] - a[1])    .slice(0, 10)    .map(([name, count]) => ({ name, count }));   const topDurationEvents = [...durationByName.entries()]    .map(([name, value]) => ({      name,      count: value.count,      totalMs: round(toMs(value.dur), 2),      avgMs: round(toMs(value.dur / value.count)),      maxMs: round(toMs(value.max)),    }))    .toSorted((a, b) => b.totalMs - a.totalMs)    .slice(0, 10);   const heap = (() => {    if (updateCounters.length === 0) return null;    const first = updateCounters[0];    const last = updateCounters[updateCounters.length - 1];    const min = Math.min(...updateCounters);    const max = Math.max(...updateCounters);    return {      firstMb: round(mb(first), 1),      lastMb: round(mb(last), 1),      minMb: round(mb(min), 1),      maxMb: round(mb(max), 1),      deltaMb: round(mb(last - first), 1),    };  })();   const longDispatchCount = dispatchRows.filter((row) => row.dur >= 50_000).length;   return {    keypress: aggregateDispatchType("keypress"),    textInput: aggregateDispatchType("textInput"),    input: aggregateDispatchType("input"),    keydown: aggregateDispatchType("keydown"),    dispatchDiscreteEvent: aggregateFunction("dispatchDiscreteEvent"),    performWorkUntilDeadline: aggregateFunction("performWorkUntilDeadline"),    longDispatchCount,    heap,    topUserTiming,    topDurationEvents,  };} const pad = (value, width) => String(value).padEnd(width, " "); function evaluateThresholds(summary, thresholds) {  const checks = [    {      label: "keypress avg",      actual: summary.keypress.avgMs,      threshold: thresholds.maxKeypressAvgMs,      ok: summary.keypress.avgMs <= thresholds.maxKeypressAvgMs,    },    {      label: "keypress max",      actual: summary.keypress.maxMs,      threshold: thresholds.maxKeypressMaxMs,      ok: summary.keypress.maxMs <= thresholds.maxKeypressMaxMs,    },    {      label: "long EventDispatch spikes",      actual: summary.longDispatchCount,      threshold: thresholds.maxLongDispatchCount,      ok: summary.longDispatchCount <= thresholds.maxLongDispatchCount,    },  ];   const failures = checks    .filter((check) => !check.ok)    .map((check) => `${check.label} ${check.actual} exceeds ${check.threshold}`);   return {    checks,    failures,    passed: failures.length === 0,  };} function createMarkdownSummary({ tracePath, donePayload, summary, thresholds, thresholdCheck }) {  const THREAD_COL_WIDTH = 8;  const TITLE_COL_WIDTH = 15;  const largeThreadRenderStats = Array.isArray(donePayload.interactions?.largeThreadRenderStats)    ? donePayload.interactions.largeThreadRenderStats    : [];  const formatThreadRenderRow = (stat) => {    const threadShort =      typeof stat.threadId === "string" ? stat.threadId.slice(0, THREAD_COL_WIDTH) : "n/a";    const titleShort =      typeof stat.threadTitleShort === "string"        ? stat.threadTitleShort        : typeof stat.threadId === "string"          ? stat.threadId.slice(0, 15)          : "n/a";    const firstRenderMs =      typeof stat.firstRenderMs === "number"        ? stat.firstRenderMs        : typeof stat.renderMs === "number"          ? stat.renderMs          : "n/a";    const followUpRenderMs =      typeof stat.followUpRenderMs === "number" ? stat.followUpRenderMs : "n/a";    const followUpMinMs = typeof stat.followUpMinMs === "number" ? stat.followUpMinMs : "n/a";    const followUpMaxMs = typeof stat.followUpMaxMs === "number" ? stat.followUpMaxMs : "n/a";    const followUpSampleCount =      typeof stat.followUpSampleCount === "number" ? stat.followUpSampleCount : "n/a";    const deltaMs = typeof stat.deltaMs === "number" ? stat.deltaMs : "n/a";    const deltaPct = typeof stat.deltaPct === "number" ? stat.deltaPct : "n/a";    const followUpRange =      typeof followUpMinMs === "number" && typeof followUpMaxMs === "number"        ? `${followUpMinMs}-${followUpMaxMs} (${followUpSampleCount}x)`        : "n/a";    return `| ${pad(threadShort, THREAD_COL_WIDTH)} | ${pad(titleShort, TITLE_COL_WIDTH)} | ${stat.messageCount} | ${firstRenderMs} | ${followUpRenderMs} | ${followUpRange} | ${deltaMs} | ${deltaPct}% |`;  };  const benchmarkThreadIds = Array.isArray(donePayload.interactions?.benchmarkThreadIds)    ? donePayload.interactions.benchmarkThreadIds    : [];  const seedSource =    donePayload.seed?.source === "file"      ? `file (\`${donePayload.seed?.path ?? "unknown"}\`)`      : (donePayload.seed?.source ?? "generated");  const topDurationHead = summary.topDurationEvents.slice(0, 3);  const topDurationSummary =    topDurationHead.length === 0      ? "none"      : topDurationHead.map((entry) => `${entry.name} (${entry.totalMs}ms total)`).join(", ");  const actionLines = thresholdCheck.passed    ? [        "- Action: no threshold regressions detected in this run.",        "- Follow-up: keep watching top duration events for drift in future traces.",      ]    : [        "- Action: threshold regression detected; profile and optimize the failing path(s) before merge.",        ...thresholdCheck.failures.map((failure) => `- Failing metric: ${failure}`),      ];  const lines = [    marker,    "## Desktop Dev Perf Trace",    "",    `- Status: ${thresholdCheck.passed ? "PASS" : "FAIL"}`,    `- Thresholds: keypress avg ${summary.keypress.avgMs}/${thresholds.maxKeypressAvgMs}ms, keypress max ${summary.keypress.maxMs}/${thresholds.maxKeypressMaxMs}ms, long dispatch spikes ${summary.longDispatchCount}/${thresholds.maxLongDispatchCount}`,    `- Coverage: seed=${seedSource}; projects=${donePayload.seed?.projectCount ?? "n/a"}; threads=${donePayload.seed?.threadCount ?? "n/a"}; benchmark threads exercised=${benchmarkThreadIds.length}`,    `- Interaction volume: thread clicks=${donePayload.interactions?.threadClicks ?? "n/a"}, typed chars=${donePayload.interactions?.typedChars ?? "n/a"}`,    `- Top hotspots: ${topDurationSummary}`,    "",    ...actionLines,    "",    "<details>",    "<summary>Detailed trace metrics and diagnostics</summary>",    "",    "### Run Metadata",    "",    `- Command: \`bun dev:desktop\``,    `- Trace: \`${tracePath}\``,    `- Started: ${donePayload.startedAt ?? "n/a"}`,    `- Completed: ${donePayload.completedAt ?? "n/a"}`,    `- Duration: ${donePayload.durationMs ?? "n/a"} ms`,    "",    "### Interaction Run",    "",    `- Thread clicks: ${donePayload.interactions?.threadClicks ?? "n/a"}`,    `- Typed chars: ${donePayload.interactions?.typedChars ?? "n/a"}`,    `- Benchmark thread ids exercised: ${benchmarkThreadIds.length > 0 ? benchmarkThreadIds.join(", ") : "none"}`,    `- Model selected: ${donePayload.interactions?.selectedModel ?? "n/a"}`,    `- Terminal opened by shortcut: ${donePayload.interactions?.terminal?.openedByShortcut ?? "n/a"}`,    `- Terminal shortcut modifier: ${donePayload.interactions?.terminal?.modifierUsed ?? "n/a"}`,    `- Terminal splits: ${donePayload.interactions?.terminal?.splitCount ?? "n/a"}`,    `- Terminal command executed: ${donePayload.interactions?.terminal?.commandFileTouched ?? "n/a"}`,    `- Terminal output observed: ${donePayload.interactions?.terminal?.commandEchoObserved ?? "n/a"}`,    `- Terminal marker: ${donePayload.interactions?.terminal?.commandMarker ?? "n/a"}`,    `- Terminal interactions enabled: ${donePayload.config?.runTerminalInteractions ?? "n/a"}`,    `- Optional renderer interactions enabled: ${donePayload.config?.runOptionalRendererInteractions ?? "n/a"}`,    `- Benchmark sweep enabled: ${donePayload.config?.runBenchmarkThreadSweep ?? "n/a"}`,    `- Benchmark follow-up pass count: ${donePayload.config?.benchmarkFollowUpPassCount ?? "n/a"}`,    "",    "### Threshold Config",    "",    `- keypress avg max: ${thresholds.maxKeypressAvgMs}ms`,    `- keypress max max: ${thresholds.maxKeypressMaxMs}ms`,    `- long EventDispatch spike cap: ${thresholds.maxLongDispatchCount}`,    "",    "### Benchmark Thread Render",    "",    "| Thread | Title (15) | Messages | First Nav (ms) | Follow-up Median (ms) | Follow-up Range | Delta (ms) | Delta (%) |",    "| --- | --- | ---: | ---: | ---: | --- | ---: | ---: |",    ...(largeThreadRenderStats.length > 0      ? largeThreadRenderStats.map((stat) => formatThreadRenderRow(stat))      : ["| n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a |"]),    "",    "### Input Event Metrics",    "",    "| Event | Count | Avg (ms) | Max (ms) | Total (ms) |",    "| --- | ---: | ---: | ---: | ---: |",    `| keypress | ${summary.keypress.count} | ${summary.keypress.avgMs} | ${summary.keypress.maxMs} | ${summary.keypress.totalMs} |`,    `| textInput | ${summary.textInput.count} | ${summary.textInput.avgMs} | ${summary.textInput.maxMs} | ${summary.textInput.totalMs} |`,    `| input | ${summary.input.count} | ${summary.input.avgMs} | ${summary.input.maxMs} | ${summary.input.totalMs} |`,    `| keydown | ${summary.keydown.count} | ${summary.keydown.avgMs} | ${summary.keydown.maxMs} | ${summary.keydown.totalMs} |`,    "",    "### Scheduler/Event Hotspots",    "",    `- dispatchDiscreteEvent: ${summary.dispatchDiscreteEvent.totalMs}ms total (${summary.dispatchDiscreteEvent.count} calls)`,    `- performWorkUntilDeadline: ${summary.performWorkUntilDeadline.totalMs}ms total (${summary.performWorkUntilDeadline.count} calls)`,    `- EventDispatch spikes >= 50ms: ${summary.longDispatchCount}`,    "",    "### Threshold Check",    "",    ...thresholdCheck.checks.map(      (check) =>        `- ${check.label} <= ${check.threshold}: ${check.ok ? "pass" : `fail (actual ${check.actual})`}`,    ),    "",  ];   if (summary.heap) {    lines.push("### Heap Counters", "");    lines.push(      `- first=${summary.heap.firstMb}MB, last=${summary.heap.lastMb}MB, min=${summary.heap.minMb}MB, max=${summary.heap.maxMb}MB, delta=${summary.heap.deltaMb}MB`,      "",    );  }   lines.push("### Top User Timing Marks", "");  for (const entry of summary.topUserTiming) {    lines.push(`- ${entry.count}x ${entry.name}`);  }  lines.push("", "### Top Duration Events", "");  for (const entry of summary.topDurationEvents) {    lines.push(      `- ${entry.name}: total=${entry.totalMs}ms, avg=${entry.avgMs}ms, max=${entry.maxMs}ms, count=${entry.count}`,    );  }  lines.push("", "</details>", "");   return lines.join("\n");} async function waitForDoneFile(donePath, child, timeoutMs) {  const startedAt = Date.now();  while (Date.now() - startedAt < timeoutMs) {    if (fs.existsSync(donePath)) {      return JSON.parse(fs.readFileSync(donePath, "utf8"));    }    if (child.exitCode !== null) {      throw new Error(`desktop dev process exited early (code ${child.exitCode})`);    }    // oxlint-disable-next-line no-await-in-loop    await sleep(500);  }  throw new Error(`Timed out waiting for done marker at ${donePath}`);} function tailLogText(logChunks, lineCount = 120) {  const lines = logChunks.join("").split(/\r?\n/);  return lines.slice(-lineCount).join("\n").trim();} function inferDesktopPerfTimeoutHint(logChunks) {  const joined = logChunks.join("");  const lowered = joined.toLowerCase();   if (lowered.includes("waiting for bundles") && lowered.includes("outdated:")) {    return "Desktop bundles were present but older than desktop source files; dev bundler did not refresh outputs in time.";  }  if (lowered.includes("waiting for bundles") && lowered.includes("stale:")) {    return "Desktop bundles were detected as stale by startup freshness checks.";  }

Lines 1–500 of 887.