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.
- package/dist/{BenchRunner-BLfGX2wQ.d.mts → BenchRunner-CSKN9zPy.d.mts} +1 -1
- package/dist/BrowserHeapSampler-DQwmmuDu.mjs +187 -0
- package/dist/BrowserHeapSampler-DQwmmuDu.mjs.map +1 -0
- package/dist/GcStats-ByEovUi1.mjs +77 -0
- package/dist/GcStats-ByEovUi1.mjs.map +1 -0
- package/dist/{HeapSampler-BX3de22o.mjs → HeapSampler-B8dtKHn1.mjs} +1 -1
- package/dist/{HeapSampler-BX3de22o.mjs.map → HeapSampler-B8dtKHn1.mjs.map} +1 -1
- package/dist/bin/benchforge.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/runners/WorkerScript.d.mts +1 -1
- package/dist/runners/WorkerScript.mjs +1 -1
- package/dist/{src-JGOI6_Sc.mjs → src-B06_i1RD.mjs} +8 -259
- package/dist/src-B06_i1RD.mjs.map +1 -0
- package/package.json +10 -2
- package/src/cli/RunBenchCLI.ts +15 -4
- package/dist/src-JGOI6_Sc.mjs.map +0 -1
|
@@ -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-
|
|
2837
|
+
//# sourceMappingURL=src-B06_i1RD.mjs.map
|