benchforge 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { a as createAdaptiveWrapper, c as BasicRunner, i as createRunner, l as computeStats, n as getElapsed, o as average, r as getPerfNow, s as bootstrapDifferenceCI, t as debugWorkerTiming, u as discoverVariants } from "./TimingUtils-ClclVQ7E.mjs";
2
+ import { n as parseGcLine, t as aggregateGcStats } from "./GcStats-ByEovUi1.mjs";
2
3
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { execSync, fork, spawn } from "node:child_process";
@@ -8,7 +9,6 @@ import pico from "picocolors";
8
9
  import { table } from "table";
9
10
  import yargs from "yargs";
10
11
  import { hideBin } from "yargs/helpers";
11
- import { chromium } from "playwright";
12
12
  import { createServer } from "node:http";
13
13
  import open from "open";
14
14
 
@@ -30,81 +30,6 @@ async function loadCaseData(casesModule, caseId) {
30
30
  return { data: caseId };
31
31
  }
32
32
 
33
- //#endregion
34
- //#region src/runners/GcStats.ts
35
- /** Parse a single --trace-gc-nvp stderr line */
36
- function parseGcLine(line) {
37
- if (!line.includes("pause=")) return void 0;
38
- const fields = parseNvpFields(line);
39
- if (!fields.gc) return void 0;
40
- const int = (k) => Number.parseInt(fields[k] || "0", 10);
41
- const type = parseGcType(fields.gc);
42
- const pauseMs = Number.parseFloat(fields.pause || "0");
43
- const allocated = int("allocated");
44
- const promoted = int("promoted");
45
- const survived = int("new_space_survived") || int("survived");
46
- const startSize = int("start_object_size");
47
- const endSize = int("end_object_size");
48
- const collected = startSize > endSize ? startSize - endSize : 0;
49
- if (Number.isNaN(pauseMs)) return void 0;
50
- return {
51
- type,
52
- pauseMs,
53
- allocated,
54
- collected,
55
- promoted,
56
- survived
57
- };
58
- }
59
- /** Parse name=value pairs from trace-gc-nvp line */
60
- function parseNvpFields(line) {
61
- const fields = {};
62
- const matches = line.matchAll(/(\w+)=([^\s,]+)/g);
63
- for (const [, key, value] of matches) fields[key] = value;
64
- return fields;
65
- }
66
- /** Map V8 gc type codes to our types */
67
- function parseGcType(gcField) {
68
- if (gcField === "s" || gcField === "scavenge") return "scavenge";
69
- if (gcField === "mc" || gcField === "ms" || gcField === "mark-compact") return "mark-compact";
70
- if (gcField === "mmc" || gcField === "minor-mc" || gcField === "minor-ms") return "minor-ms";
71
- return "unknown";
72
- }
73
- /** Aggregate GC events into summary stats */
74
- function aggregateGcStats(events) {
75
- let scavenges = 0;
76
- let markCompacts = 0;
77
- let gcPauseTime = 0;
78
- let totalCollected = 0;
79
- let hasNodeFields = false;
80
- let totalAllocated = 0;
81
- let totalPromoted = 0;
82
- let totalSurvived = 0;
83
- for (const e of events) {
84
- if (e.type === "scavenge" || e.type === "minor-ms") scavenges++;
85
- else if (e.type === "mark-compact") markCompacts++;
86
- gcPauseTime += e.pauseMs;
87
- totalCollected += e.collected;
88
- if (e.allocated != null) {
89
- hasNodeFields = true;
90
- totalAllocated += e.allocated;
91
- totalPromoted += e.promoted ?? 0;
92
- totalSurvived += e.survived ?? 0;
93
- }
94
- }
95
- return {
96
- scavenges,
97
- markCompacts,
98
- totalCollected,
99
- gcPauseTime,
100
- ...hasNodeFields && {
101
- totalAllocated,
102
- totalPromoted,
103
- totalSurvived
104
- }
105
- };
106
- }
107
-
108
33
  //#endregion
109
34
  //#region src/runners/RunnerOrchestrator.ts
110
35
  const logTiming = debugWorkerTiming ? (message) => console.log(`[RunnerOrchestrator] ${message}`) : () => {};
@@ -1025,188 +950,6 @@ function parseCliArgs(args, configure = defaultCliArgs) {
1025
950
  return configure(yargs(args)).parseSync();
1026
951
  }
1027
952
 
1028
- //#endregion
1029
- //#region src/browser/BrowserGcStats.ts
1030
- /** Parse CDP trace events (MinorGC/MajorGC) into GcEvent[] */
1031
- function parseGcTraceEvents(traceEvents) {
1032
- return traceEvents.flatMap((e) => {
1033
- if (e.ph !== "X") return [];
1034
- const type = gcType(e.name);
1035
- if (!type) return [];
1036
- const durUs = e.dur ?? 0;
1037
- const heapBefore = e.args?.usedHeapSizeBefore ?? 0;
1038
- const heapAfter = e.args?.usedHeapSizeAfter ?? 0;
1039
- return [{
1040
- type,
1041
- pauseMs: durUs / 1e3,
1042
- collected: Math.max(0, heapBefore - heapAfter)
1043
- }];
1044
- });
1045
- }
1046
- function gcType(name) {
1047
- if (name === "MinorGC") return "scavenge";
1048
- if (name === "MajorGC") return "mark-compact";
1049
- }
1050
- /** Parse CDP trace events and aggregate into GcStats */
1051
- function browserGcStats(traceEvents) {
1052
- return aggregateGcStats(parseGcTraceEvents(traceEvents));
1053
- }
1054
-
1055
- //#endregion
1056
- //#region src/browser/BrowserHeapSampler.ts
1057
- /** Run browser benchmark, auto-detecting page API mode.
1058
- * Bench function (window.__bench): CLI controls iteration and timing.
1059
- * Lap mode (__start/__lap/__done): page controls the measured region. */
1060
- async function profileBrowser(params) {
1061
- const { url, headless = true, chromeArgs, timeout = 60 } = params;
1062
- const { gcStats: collectGc } = params;
1063
- const { samplingInterval = 32768 } = params.heapOptions ?? {};
1064
- const browser = await chromium.launch({
1065
- headless,
1066
- args: chromeArgs
1067
- });
1068
- try {
1069
- const page = await browser.newPage();
1070
- page.setDefaultTimeout(timeout * 1e3);
1071
- const cdp = await page.context().newCDPSession(page);
1072
- const pageErrors = [];
1073
- page.on("pageerror", (err) => pageErrors.push(err.message));
1074
- const traceEvents = collectGc ? await startGcTracing(cdp) : [];
1075
- const lapMode = await setupLapMode(page, cdp, params, samplingInterval, timeout, pageErrors);
1076
- await page.goto(url, { waitUntil: "load" });
1077
- const hasBench = await page.evaluate(() => typeof globalThis.__bench === "function");
1078
- let result;
1079
- if (hasBench) {
1080
- lapMode.cancel();
1081
- lapMode.promise.catch(() => {});
1082
- result = await runBenchLoop(page, cdp, params, samplingInterval);
1083
- } else {
1084
- result = await lapMode.promise;
1085
- lapMode.cancel();
1086
- }
1087
- if (collectGc) result = {
1088
- ...result,
1089
- gcStats: await collectTracing(cdp, traceEvents)
1090
- };
1091
- return result;
1092
- } finally {
1093
- await browser.close();
1094
- }
1095
- }
1096
- /** Inject __start/__lap as in-page functions, expose __done for results collection.
1097
- * __start/__lap are pure in-page (zero CDP overhead). First __start() triggers
1098
- * instrument start. __done() stops instruments and collects timing data. */
1099
- async function setupLapMode(page, cdp, params, samplingInterval, timeout, pageErrors) {
1100
- const { heapSample } = params;
1101
- const { promise, resolve, reject } = Promise.withResolvers();
1102
- let instrumentsStarted = false;
1103
- await page.exposeFunction("__benchInstrumentStart", async () => {
1104
- if (instrumentsStarted) return;
1105
- instrumentsStarted = true;
1106
- if (heapSample) await cdp.send("HeapProfiler.startSampling", heapSamplingParams(samplingInterval));
1107
- });
1108
- await page.exposeFunction("__benchCollect", async (samples, wallTimeMs) => {
1109
- let heapProfile;
1110
- if (heapSample && instrumentsStarted) heapProfile = (await cdp.send("HeapProfiler.stopSampling")).profile;
1111
- resolve({
1112
- samples,
1113
- heapProfile,
1114
- wallTimeMs
1115
- });
1116
- });
1117
- await page.addInitScript(injectLapFunctions);
1118
- const timer = setTimeout(() => {
1119
- const lines = [`Timed out after ${timeout}s`];
1120
- if (pageErrors.length) lines.push("Page JS errors:", ...pageErrors.map((e) => ` ${e}`));
1121
- else lines.push("Page did not call __done() or define window.__bench");
1122
- reject(new Error(lines.join("\n")));
1123
- }, timeout * 1e3);
1124
- return {
1125
- promise,
1126
- cancel: () => clearTimeout(timer)
1127
- };
1128
- }
1129
- /** In-page timing functions injected via addInitScript (zero CDP overhead).
1130
- * __start/__lap collect timestamps, __done delegates to exposed __benchCollect. */
1131
- function injectLapFunctions() {
1132
- const g = globalThis;
1133
- g.__benchSamples = [];
1134
- g.__benchLastTime = 0;
1135
- g.__benchFirstStart = 0;
1136
- g.__start = () => {
1137
- const now = performance.now();
1138
- g.__benchLastTime = now;
1139
- if (!g.__benchFirstStart) {
1140
- g.__benchFirstStart = now;
1141
- return g.__benchInstrumentStart();
1142
- }
1143
- };
1144
- g.__lap = () => {
1145
- const now = performance.now();
1146
- g.__benchSamples.push(now - g.__benchLastTime);
1147
- g.__benchLastTime = now;
1148
- };
1149
- g.__done = () => {
1150
- const wall = g.__benchFirstStart ? performance.now() - g.__benchFirstStart : 0;
1151
- return g.__benchCollect(g.__benchSamples.slice(), wall);
1152
- };
1153
- }
1154
- function heapSamplingParams(samplingInterval) {
1155
- return {
1156
- samplingInterval,
1157
- includeObjectsCollectedByMajorGC: true,
1158
- includeObjectsCollectedByMinorGC: true
1159
- };
1160
- }
1161
- /** Start CDP GC tracing, returns the event collector array. */
1162
- async function startGcTracing(cdp) {
1163
- const events = [];
1164
- cdp.on("Tracing.dataCollected", ({ value }) => {
1165
- for (const e of value) events.push(e);
1166
- });
1167
- await cdp.send("Tracing.start", { traceConfig: { includedCategories: ["v8", "v8.gc"] } });
1168
- return events;
1169
- }
1170
- /** Bench function mode: run window.__bench in a timed iteration loop. */
1171
- async function runBenchLoop(page, cdp, params, samplingInterval) {
1172
- const { heapSample } = params;
1173
- const maxTime = params.maxTime ?? 642;
1174
- const maxIter = params.maxIterations ?? Number.MAX_SAFE_INTEGER;
1175
- if (heapSample) await cdp.send("HeapProfiler.startSampling", heapSamplingParams(samplingInterval));
1176
- const { samples, totalMs } = await page.evaluate(async ({ maxTime, maxIter }) => {
1177
- const bench = globalThis.__bench;
1178
- const samples = [];
1179
- const startAll = performance.now();
1180
- const deadline = startAll + maxTime;
1181
- for (let i = 0; i < maxIter && performance.now() < deadline; i++) {
1182
- const t0 = performance.now();
1183
- await bench();
1184
- samples.push(performance.now() - t0);
1185
- }
1186
- return {
1187
- samples,
1188
- totalMs: performance.now() - startAll
1189
- };
1190
- }, {
1191
- maxTime,
1192
- maxIter
1193
- });
1194
- let heapProfile;
1195
- if (heapSample) heapProfile = (await cdp.send("HeapProfiler.stopSampling")).profile;
1196
- return {
1197
- samples,
1198
- heapProfile,
1199
- wallTimeMs: totalMs
1200
- };
1201
- }
1202
- /** Stop CDP tracing and parse GC events into GcStats. */
1203
- async function collectTracing(cdp, traceEvents) {
1204
- const complete = new Promise((resolve) => cdp.once("Tracing.tracingComplete", () => resolve()));
1205
- await cdp.send("Tracing.end");
1206
- await complete;
1207
- return browserGcStats(traceEvents);
1208
- }
1209
-
1210
953
  //#endregion
1211
954
  //#region src/export/JsonExport.ts
1212
955
  /** Export benchmark results to JSON file */
@@ -2705,6 +2448,12 @@ async function benchExports(suite, args) {
2705
2448
  /** Run browser profiling via Playwright + CDP, report with standard pipeline */
2706
2449
  async function browserBenchExports(args) {
2707
2450
  warnBrowserFlags(args);
2451
+ let profileBrowser;
2452
+ try {
2453
+ ({profileBrowser} = await import("./BrowserHeapSampler-DQwmmuDu.mjs"));
2454
+ } catch {
2455
+ throw new Error("playwright is required for browser benchmarking (--url).\n\nQuick start: npx benchforge-browser --url <your-url>\n\nOr install manually:\n npm install playwright\n npx playwright install chromium");
2456
+ }
2708
2457
  const url = args.url;
2709
2458
  const { iterations, time } = args;
2710
2459
  const result = await profileBrowser({
@@ -3085,4 +2834,4 @@ function getMostRecentModifiedDate(dir) {
3085
2834
 
3086
2835
  //#endregion
3087
2836
  export { timeSection as A, parseCliArgs as B, adaptiveSection as C, gcStatsSection as D, gcSection as E, generateHtmlReport as F, truncate as G, formatBytes as H, formatDateWithTimezone as I, loadCaseData as J, isStatefulVariant as K, prepareHtmlData as L, formatConvergence as M, filterMatrix as N, optSection as O, parseMatrixFilter as P, exportPerfettoTrace as R, reportMatrixResults as S, cpuSection as T, integer as U, reportResults as V, timeMs as W, loadCasesModule as Y, runDefaultMatrixBench as _, cliToMatrixOptions as a, gcStatsColumns as b, exportReports as c, matrixToReportGroups as d, parseBenchArgs as f, runDefaultBench as g, runBenchmarks as h, benchExports as i, totalTimeSection as j, runsSection as k, hasField as l, reportOptStatus as m, getBaselineVersion as n, defaultMatrixReport as o, printHeapReports as p, runMatrix as q, getCurrentGitVersion as r, defaultReport as s, formatGitVersion as t, matrixBenchExports as u, runMatrixSuite as v, buildGenericSections as w, heapTotalColumn as x, gcPauseColumn as y, defaultCliArgs as z };
3088
- //# sourceMappingURL=src-JGOI6_Sc.mjs.map
2837
+ //# sourceMappingURL=src-B06_i1RD.mjs.map