benchforge 0.1.9 → 0.2.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/LICENSE +20 -0
- package/README.md +99 -260
- package/bin/benchforge +1 -2
- package/dist/AnalyzeArchive-8NCJhmhS.mjs +145 -0
- package/dist/AnalyzeArchive-8NCJhmhS.mjs.map +1 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs +1050 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs.map +1 -0
- package/dist/BenchRunner-DglX1NOn.d.mts +302 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs +27 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs.map +1 -0
- package/dist/Formatters-BWj3d4sv.mjs +95 -0
- package/dist/Formatters-BWj3d4sv.mjs.map +1 -0
- package/dist/{HeapSampler-B8dtKHn1.mjs → HeapSampler-Dq-hpXem.mjs} +4 -4
- package/dist/HeapSampler-Dq-hpXem.mjs.map +1 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs +3075 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs.map +1 -0
- package/dist/StatisticalUtils-BD92crgM.mjs +255 -0
- package/dist/StatisticalUtils-BD92crgM.mjs.map +1 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs +29 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs.map +1 -0
- package/dist/ViewerServer-BJhdnxlN.mjs +639 -0
- package/dist/ViewerServer-BJhdnxlN.mjs.map +1 -0
- package/dist/ViewerServer-CuMNdNBz.mjs +2 -0
- package/dist/bin/benchforge.mjs +4 -5
- package/dist/bin/benchforge.mjs.map +1 -1
- package/dist/index.d.mts +731 -522
- package/dist/index.mjs +98 -3
- package/dist/index.mjs.map +1 -0
- package/dist/runners/WorkerScript.d.mts +12 -4
- package/dist/runners/WorkerScript.mjs +92 -120
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/viewer/assets/CIPlot-BkOvMoMa.js +1 -0
- package/dist/viewer/assets/HistogramKde-CmSyUFY0.js +1 -0
- package/dist/viewer/assets/LegendUtils-BJpbn_jr.js +55 -0
- package/dist/viewer/assets/SampleTimeSeries-C4VBhXr3.js +1 -0
- package/dist/viewer/assets/index-Br9bp_cX.js +153 -0
- package/dist/viewer/assets/index-NzXXe_CC.css +1 -0
- package/dist/viewer/index.html +19 -0
- package/dist/viewer/speedscope/LICENSE +21 -0
- package/dist/viewer/speedscope/SourceCodePro-Regular.ttf-ILST5JV6.woff2 +0 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js +2 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js.map +7 -0
- package/dist/viewer/speedscope/favicon-16x16-VSI62OPJ.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-3EB2YCUY.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js +2 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js.map +7 -0
- package/dist/viewer/speedscope/favicon-FOKUP5Y5.ico +0 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js +2 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js.map +7 -0
- package/dist/viewer/speedscope/file-format-schema.json +274 -0
- package/dist/viewer/speedscope/index.html +19 -0
- package/dist/viewer/speedscope/jfrview_bg-BLJXNNQB.wasm +0 -0
- package/dist/viewer/speedscope/perf-vertx-stacks-01-collapsed-all-ZNUIGAJL.txt +199 -0
- package/dist/viewer/speedscope/release.txt +3 -0
- package/dist/viewer/speedscope/source-code-pro.LICENSE.md +93 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css +2 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css.map +7 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js +212 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js.map +7 -0
- package/package.json +52 -26
- package/src/bin/benchforge.ts +2 -2
- package/src/cli/AnalyzeArchive.ts +232 -0
- package/src/cli/BrowserBench.ts +322 -0
- package/src/cli/CliArgs.ts +164 -48
- package/src/cli/CliExport.ts +179 -0
- package/src/cli/CliOptions.ts +147 -0
- package/src/cli/CliReport.ts +197 -0
- package/src/cli/FilterBenchmarks.ts +18 -30
- package/src/cli/RunBenchCLI.ts +138 -844
- package/src/cli/SuiteRunner.ts +160 -0
- package/src/cli/ViewerServer.ts +282 -0
- package/src/export/AllocExport.ts +121 -0
- package/src/export/ArchiveExport.ts +146 -0
- package/src/export/ArchiveFormat.ts +50 -0
- package/src/export/CoverageExport.ts +148 -0
- package/src/export/EditorUri.ts +10 -0
- package/src/export/PerfettoExport.ts +91 -126
- package/src/export/SpeedscopeTypes.ts +98 -0
- package/src/export/TimeExport.ts +115 -0
- package/src/index.ts +87 -62
- package/src/matrix/BenchMatrix.ts +230 -0
- package/src/matrix/CaseLoader.ts +8 -6
- package/src/matrix/MatrixDirRunner.ts +153 -0
- package/src/matrix/MatrixFilter.ts +55 -53
- package/src/matrix/MatrixInlineRunner.ts +50 -0
- package/src/matrix/MatrixReport.ts +94 -254
- package/src/matrix/VariantLoader.ts +9 -9
- package/src/profiling/browser/BenchLoop.ts +51 -0
- package/src/profiling/browser/BrowserCDP.ts +133 -0
- package/src/profiling/browser/BrowserGcStats.ts +33 -0
- package/src/profiling/browser/BrowserProfiler.ts +160 -0
- package/src/profiling/browser/CdpClient.ts +82 -0
- package/src/profiling/browser/CdpPage.ts +138 -0
- package/src/profiling/browser/ChromeLauncher.ts +158 -0
- package/src/profiling/browser/ChromeTraceEvent.ts +28 -0
- package/src/profiling/browser/PageLoadMode.ts +61 -0
- package/src/profiling/node/CoverageSampler.ts +27 -0
- package/src/profiling/node/CoverageTypes.ts +23 -0
- package/src/profiling/node/HeapSampleReport.ts +261 -0
- package/src/{heap-sample → profiling/node}/HeapSampler.ts +55 -13
- package/src/profiling/node/ResolvedProfile.ts +98 -0
- package/src/profiling/node/TimeSampler.ts +57 -0
- package/src/report/BenchmarkReport.ts +146 -0
- package/src/report/Colors.ts +9 -0
- package/src/report/Formatters.ts +110 -0
- package/src/report/GcSections.ts +151 -0
- package/src/{GitUtils.ts → report/GitUtils.ts} +18 -19
- package/src/report/HtmlReport.ts +223 -0
- package/src/report/ParseStats.ts +73 -0
- package/src/report/StandardSections.ts +147 -0
- package/src/report/ViewerSections.ts +286 -0
- package/src/report/text/TableReport.ts +253 -0
- package/src/report/text/TextReport.ts +123 -0
- package/src/runners/AdaptiveWrapper.ts +167 -287
- package/src/runners/BenchRunner.ts +27 -22
- package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
- package/src/runners/CreateRunner.ts +5 -7
- package/src/runners/GcStats.ts +58 -61
- package/src/{MeasuredResults.ts → runners/MeasuredResults.ts} +43 -37
- package/src/runners/MergeBatches.ts +123 -0
- package/src/{NodeGC.ts → runners/NodeGC.ts} +2 -3
- package/src/runners/RunnerOrchestrator.ts +180 -296
- package/src/runners/RunnerUtils.ts +75 -1
- package/src/runners/SampleStats.ts +100 -0
- package/src/runners/TimingRunner.ts +244 -0
- package/src/runners/TimingUtils.ts +3 -2
- package/src/runners/WorkerScript.ts +162 -178
- package/src/stats/BootstrapDifference.ts +282 -0
- package/src/{PermutationTest.ts → stats/PermutationTest.ts} +31 -40
- package/src/stats/StatisticalUtils.ts +445 -0
- package/src/{tests → test}/AdaptiveConvergence.test.ts +10 -10
- package/src/test/AdaptiveRunner.test.ts +39 -41
- package/src/{tests → test}/AdaptiveSampling.test.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +9 -41
- package/src/{tests → test}/BenchMatrix.test.ts +31 -28
- package/src/test/BenchmarkReport.test.ts +63 -13
- package/src/test/BrowserBench.e2e.test.ts +186 -17
- package/src/test/BrowserBench.test.ts +10 -5
- package/src/test/BuildTimeSection.test.ts +130 -0
- package/src/test/CapSamples.test.ts +82 -0
- package/src/test/CoverageExport.test.ts +115 -0
- package/src/test/CoverageSampler.test.ts +33 -0
- package/src/test/HeapAttribution.test.ts +51 -0
- package/src/{tests → test}/MatrixFilter.test.ts +16 -16
- package/src/{tests → test}/MatrixReport.test.ts +1 -1
- package/src/test/PermutationTest.test.ts +1 -1
- package/src/{tests → test}/RealDataValidation.test.ts +6 -6
- package/src/test/RunBenchCLI.test.ts +57 -56
- package/src/test/RunnerOrchestrator.test.ts +12 -12
- package/src/test/StatisticalUtils.test.ts +48 -12
- package/src/{table-util/test → test}/TableReport.test.ts +2 -2
- package/src/test/TestUtils.ts +35 -30
- package/src/test/TimeExport.test.ts +139 -0
- package/src/test/TimeSampler.test.ts +37 -0
- package/src/test/ViewerLive.e2e.test.ts +159 -0
- package/src/test/ViewerStatic.static.e2e.test.ts +137 -0
- package/src/{tests → test}/fixtures/baseline/impl.ts +1 -1
- package/src/{tests → test}/fixtures/bevy30-samples.ts +3 -1
- package/src/test/fixtures/cases/asyncCases.ts +9 -0
- package/src/{tests → test}/fixtures/cases/cases.ts +5 -2
- package/src/test/fixtures/cases/variants/product.ts +2 -0
- package/src/test/fixtures/cases/variants/sum.ts +2 -0
- package/src/test/fixtures/discover/fast.ts +1 -0
- package/src/{tests → test}/fixtures/discover/slow.ts +1 -1
- package/src/test/fixtures/invalid/bad.ts +1 -0
- package/src/test/fixtures/loader/fast.ts +1 -0
- package/src/{tests → test}/fixtures/loader/slow.ts +1 -1
- package/src/test/fixtures/loader/stateful.ts +2 -0
- package/src/test/fixtures/stateful/stateful.ts +2 -0
- package/src/test/fixtures/variants/extra.ts +1 -0
- package/src/test/fixtures/variants/impl.ts +1 -0
- package/src/test/fixtures/worker/fast.ts +1 -0
- package/src/{tests → test}/fixtures/worker/slow.ts +1 -1
- package/src/viewer/DateFormat.ts +30 -0
- package/src/viewer/Helpers.ts +23 -0
- package/src/viewer/LineData.ts +120 -0
- package/src/viewer/Providers.ts +191 -0
- package/src/viewer/ReportData.ts +123 -0
- package/src/viewer/State.ts +49 -0
- package/src/viewer/Theme.ts +15 -0
- package/src/viewer/components/App.tsx +73 -0
- package/src/viewer/components/DropZone.tsx +71 -0
- package/src/viewer/components/LazyPlot.ts +33 -0
- package/src/viewer/components/SamplesPanel.tsx +214 -0
- package/src/viewer/components/Shell.tsx +26 -0
- package/src/viewer/components/SourcePanel.tsx +216 -0
- package/src/viewer/components/SummaryPanel.tsx +332 -0
- package/src/viewer/components/TabBar.tsx +131 -0
- package/src/viewer/components/TabContent.tsx +46 -0
- package/src/viewer/components/ThemeToggle.tsx +50 -0
- package/src/viewer/index.html +20 -0
- package/src/viewer/main.tsx +4 -0
- package/src/viewer/plots/CIPlot.ts +313 -0
- package/src/{html/browser → viewer/plots}/HistogramKde.ts +42 -47
- package/src/viewer/plots/LegendUtils.ts +134 -0
- package/src/viewer/plots/PlotTypes.ts +85 -0
- package/src/viewer/plots/RenderPlots.ts +230 -0
- package/src/viewer/plots/SampleTimeSeries.ts +306 -0
- package/src/viewer/plots/SvgHelpers.ts +136 -0
- package/src/viewer/plots/TimeSeriesMarks.ts +319 -0
- package/src/viewer/report.css +427 -0
- package/src/viewer/shell.css +357 -0
- package/src/viewer/tsconfig.json +11 -0
- package/dist/BenchRunner-CSKN9zPy.d.mts +0 -225
- package/dist/BrowserHeapSampler-DCeL42RE.mjs +0 -202
- package/dist/BrowserHeapSampler-DCeL42RE.mjs.map +0 -1
- package/dist/GcStats-ByEovUi1.mjs +0 -77
- package/dist/GcStats-ByEovUi1.mjs.map +0 -1
- package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
- package/dist/TimingUtils-ClclVQ7E.mjs +0 -597
- package/dist/TimingUtils-ClclVQ7E.mjs.map +0 -1
- package/dist/browser/index.js +0 -914
- package/dist/src-Cf_LXwlp.mjs +0 -2873
- package/dist/src-Cf_LXwlp.mjs.map +0 -1
- package/src/BenchMatrix.ts +0 -380
- package/src/BenchmarkReport.ts +0 -156
- package/src/HtmlDataPrep.ts +0 -148
- package/src/StandardSections.ts +0 -261
- package/src/StatisticalUtils.ts +0 -176
- package/src/TypeUtil.ts +0 -8
- package/src/browser/BrowserGcStats.ts +0 -44
- package/src/browser/BrowserHeapSampler.ts +0 -271
- package/src/export/JsonExport.ts +0 -103
- package/src/export/JsonFormat.ts +0 -91
- package/src/heap-sample/HeapSampleReport.ts +0 -196
- package/src/html/HtmlReport.ts +0 -131
- package/src/html/HtmlTemplate.ts +0 -284
- package/src/html/Types.ts +0 -88
- package/src/html/browser/CIPlot.ts +0 -287
- package/src/html/browser/LegendUtils.ts +0 -163
- package/src/html/browser/RenderPlots.ts +0 -263
- package/src/html/browser/SampleTimeSeries.ts +0 -389
- package/src/html/browser/Types.ts +0 -96
- package/src/html/browser/index.ts +0 -1
- package/src/html/index.ts +0 -17
- package/src/runners/BasicRunner.ts +0 -364
- package/src/table-util/ConvergenceFormatters.ts +0 -19
- package/src/table-util/Formatters.ts +0 -152
- package/src/table-util/README.md +0 -70
- package/src/table-util/TableReport.ts +0 -293
- package/src/tests/fixtures/cases/asyncCases.ts +0 -7
- package/src/tests/fixtures/cases/variants/product.ts +0 -2
- package/src/tests/fixtures/cases/variants/sum.ts +0 -2
- package/src/tests/fixtures/discover/fast.ts +0 -1
- package/src/tests/fixtures/invalid/bad.ts +0 -1
- package/src/tests/fixtures/loader/fast.ts +0 -1
- package/src/tests/fixtures/loader/stateful.ts +0 -2
- package/src/tests/fixtures/stateful/stateful.ts +0 -2
- package/src/tests/fixtures/variants/extra.ts +0 -1
- package/src/tests/fixtures/variants/impl.ts +0 -1
- package/src/tests/fixtures/worker/fast.ts +0 -1
- package/src/{table-util/test → test}/TableValueExtractor.test.ts +0 -0
- package/src/{table-util/test → test}/TableValueExtractor.ts +9 -9
|
@@ -1,46 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
2
|
+
import type { Session } from "node:inspector/promises";
|
|
3
|
+
import type { CoverageData } from "../profiling/node/CoverageTypes.ts";
|
|
4
|
+
import type { HeapProfile } from "../profiling/node/HeapSampler.ts";
|
|
5
|
+
import type { TimeProfile } from "../profiling/node/TimeSampler.ts";
|
|
6
|
+
import type { BenchmarkFunction, BenchmarkSpec } from "./BenchmarkSpec.ts";
|
|
7
|
+
import type { BenchRunner, RunnerOptions } from "./BenchRunner.ts";
|
|
8
|
+
import type { KnownRunner } from "./CreateRunner.ts";
|
|
9
|
+
import type { MeasuredResults } from "./MeasuredResults.ts";
|
|
6
10
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { createRunner, type KnownRunner } from "./CreateRunner.ts";
|
|
11
|
+
createBenchRunner,
|
|
12
|
+
importBenchFn,
|
|
13
|
+
resolveVariantFn,
|
|
14
|
+
} from "./RunnerUtils.ts";
|
|
12
15
|
import { debugWorkerTiming, getElapsed, getPerfNow } from "./TimingUtils.ts";
|
|
13
16
|
|
|
14
|
-
const workerStartTime = getPerfNow();
|
|
15
|
-
const maxLifetime = 5 * 60 * 1000; // 5 minutes
|
|
16
|
-
|
|
17
17
|
/** Message sent to worker process to start a benchmark run. */
|
|
18
18
|
export interface RunMessage {
|
|
19
19
|
type: "run";
|
|
20
20
|
spec: BenchmarkSpec;
|
|
21
21
|
runnerName: KnownRunner;
|
|
22
22
|
options: RunnerOptions;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
/** Serialized function body (mutually exclusive with modulePath) */
|
|
24
|
+
fnCode?: string;
|
|
25
|
+
modulePath?: string;
|
|
26
|
+
/** Defaults to default export */
|
|
27
|
+
exportName?: string;
|
|
28
|
+
/** Called once before benchmarking; result passed as params to fn */
|
|
29
|
+
setupExportName?: string;
|
|
27
30
|
params?: unknown;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
|
|
32
|
+
/** Directory URL containing variant .ts files (BenchMatrix mode) */
|
|
33
|
+
variantDir?: string;
|
|
34
|
+
/** Variant filename without .ts extension */
|
|
35
|
+
variantId?: string;
|
|
36
|
+
caseData?: unknown;
|
|
37
|
+
caseId?: string;
|
|
38
|
+
/** Module URL exporting cases[] and loadCase() */
|
|
39
|
+
casesModule?: string;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
/**
|
|
42
|
+
/** Benchmark results returned from worker process. */
|
|
37
43
|
export interface ResultMessage {
|
|
38
44
|
type: "result";
|
|
39
45
|
results: MeasuredResults[];
|
|
40
46
|
heapProfile?: HeapProfile;
|
|
47
|
+
timeProfile?: TimeProfile;
|
|
48
|
+
coverage?: CoverageData;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
/**
|
|
51
|
+
/** Error returned from worker process when benchmark fails. */
|
|
44
52
|
export interface ErrorMessage {
|
|
45
53
|
type: "error";
|
|
46
54
|
error: string;
|
|
@@ -49,83 +57,30 @@ export interface ErrorMessage {
|
|
|
49
57
|
|
|
50
58
|
export type WorkerMessage = RunMessage | ResultMessage | ErrorMessage;
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
process.on("message", async (message: RunMessage) => {
|
|
57
|
-
if (message.type !== "run") return;
|
|
58
|
-
|
|
59
|
-
logTiming(`Processing ${message.spec.name} with ${message.runnerName}`);
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const start = getPerfNow();
|
|
63
|
-
const baseRunner = await createRunner(message.runnerName);
|
|
64
|
-
|
|
65
|
-
const runner = (message.options as any).adaptive
|
|
66
|
-
? createAdaptiveWrapper(baseRunner, message.options as AdaptiveOptions)
|
|
67
|
-
: baseRunner;
|
|
68
|
-
|
|
69
|
-
logTiming("Runner created in", getElapsed(start));
|
|
70
|
-
|
|
71
|
-
const benchStart = getPerfNow();
|
|
72
|
-
|
|
73
|
-
// Run with heap sampling if enabled (covers module import + execution)
|
|
74
|
-
if (message.options.heapSample) {
|
|
75
|
-
const { withHeapSampling } = await import(
|
|
76
|
-
"../heap-sample/HeapSampler.ts"
|
|
77
|
-
);
|
|
78
|
-
const heapOpts = {
|
|
79
|
-
samplingInterval: message.options.heapInterval,
|
|
80
|
-
stackDepth: message.options.heapDepth,
|
|
81
|
-
};
|
|
82
|
-
const { result: results, profile: heapProfile } = await withHeapSampling(
|
|
83
|
-
heapOpts,
|
|
84
|
-
async () => {
|
|
85
|
-
const { fn, params } = await resolveBenchmarkFn(message);
|
|
86
|
-
return runner.runBench(
|
|
87
|
-
{ ...message.spec, fn },
|
|
88
|
-
message.options,
|
|
89
|
-
params,
|
|
90
|
-
);
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
94
|
-
sendAndExit({ type: "result", results, heapProfile }, 0);
|
|
95
|
-
} else {
|
|
96
|
-
const { fn, params } = await resolveBenchmarkFn(message);
|
|
97
|
-
const results = await runner.runBench(
|
|
98
|
-
{ ...message.spec, fn },
|
|
99
|
-
message.options,
|
|
100
|
-
params,
|
|
101
|
-
);
|
|
102
|
-
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
103
|
-
sendAndExit({ type: "result", results }, 0);
|
|
104
|
-
}
|
|
105
|
-
} catch (error) {
|
|
106
|
-
sendAndExit(createErrorMessage(error), 1);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
60
|
+
interface BenchmarkImportResult {
|
|
61
|
+
fn: BenchmarkFunction;
|
|
62
|
+
params: unknown;
|
|
63
|
+
}
|
|
109
64
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
65
|
+
/** Profiling state accumulated during worker benchmark execution */
|
|
66
|
+
interface ProfilingState {
|
|
67
|
+
heapProfile?: HeapProfile;
|
|
68
|
+
timeProfile?: TimeProfile;
|
|
69
|
+
coverage?: CoverageData;
|
|
70
|
+
/** Shared session so TimeSampler doesn't reset coverage counters */
|
|
71
|
+
profilerSession?: Session;
|
|
72
|
+
}
|
|
115
73
|
|
|
116
|
-
|
|
74
|
+
const workerStartTime = getPerfNow();
|
|
75
|
+
const maxLifetime = 5 * 60 * 1000;
|
|
117
76
|
|
|
118
|
-
/** Log timing with consistent format */
|
|
119
77
|
const logTiming = debugWorkerTiming ? _logTiming : () => {};
|
|
120
78
|
function _logTiming(operation: string, duration?: number) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
} else {
|
|
124
|
-
console.log(`[Worker] ${operation} ${duration.toFixed(1)}ms`);
|
|
125
|
-
}
|
|
79
|
+
const suffix = duration !== undefined ? ` ${duration.toFixed(1)}ms` : "";
|
|
80
|
+
console.log(`[Worker] ${operation}${suffix}`);
|
|
126
81
|
}
|
|
127
82
|
|
|
128
|
-
/** Send message
|
|
83
|
+
/** Send IPC message to parent then exit the worker process */
|
|
129
84
|
function sendAndExit(msg: ResultMessage | ErrorMessage, exitCode: number) {
|
|
130
85
|
process.send!(msg, undefined, undefined, (err: Error | null): void => {
|
|
131
86
|
if (err) {
|
|
@@ -138,11 +93,6 @@ function sendAndExit(msg: ResultMessage | ErrorMessage, exitCode: number) {
|
|
|
138
93
|
});
|
|
139
94
|
}
|
|
140
95
|
|
|
141
|
-
interface BenchmarkImportResult {
|
|
142
|
-
fn: BenchmarkFunction;
|
|
143
|
-
params: unknown;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
96
|
/** Resolve benchmark function from message (variant dir, module path, or fnCode) */
|
|
147
97
|
async function resolveBenchmarkFn(
|
|
148
98
|
message: RunMessage,
|
|
@@ -151,7 +101,12 @@ async function resolveBenchmarkFn(
|
|
|
151
101
|
return importVariantModule(message);
|
|
152
102
|
}
|
|
153
103
|
if (message.modulePath) {
|
|
154
|
-
|
|
104
|
+
const { modulePath, exportName, setupExportName, params } = message;
|
|
105
|
+
logTiming(
|
|
106
|
+
`Importing from ${modulePath}${exportName ? ` (${exportName})` : ""}`,
|
|
107
|
+
);
|
|
108
|
+
if (setupExportName) logTiming(`Calling setup: ${setupExportName}`);
|
|
109
|
+
return importBenchFn(modulePath, exportName, setupExportName, params);
|
|
155
110
|
}
|
|
156
111
|
return { fn: reconstructFunction(message.fnCode!), params: message.params };
|
|
157
112
|
}
|
|
@@ -160,97 +115,126 @@ async function resolveBenchmarkFn(
|
|
|
160
115
|
async function importVariantModule(
|
|
161
116
|
message: RunMessage,
|
|
162
117
|
): Promise<BenchmarkImportResult> {
|
|
163
|
-
const { variantDir, variantId
|
|
164
|
-
let { caseData } = message;
|
|
165
|
-
const moduleUrl = variantModuleUrl(variantDir!, variantId!);
|
|
118
|
+
const { variantDir, variantId } = message;
|
|
166
119
|
logTiming(`Importing variant ${variantId} from ${variantDir}`);
|
|
120
|
+
return resolveVariantFn({
|
|
121
|
+
...message,
|
|
122
|
+
variantDir: variantDir!,
|
|
123
|
+
variantId: variantId!,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
167
126
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (typeof run !== "function") {
|
|
176
|
-
throw new Error(`Variant '${variantId}' must export 'run' function`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Stateful variant: setup returns state, run receives state
|
|
180
|
-
if (typeof setup === "function") {
|
|
181
|
-
logTiming(`Calling setup for ${variantId}`);
|
|
182
|
-
const state = await setup(caseData);
|
|
183
|
-
return { fn: () => run(state), params: undefined };
|
|
127
|
+
/** Eval serialized function body back into a callable */
|
|
128
|
+
function reconstructFunction(fnCode: string): BenchmarkFunction {
|
|
129
|
+
// biome-ignore lint/security/noGlobalEval: Necessary for worker process isolation, code is from trusted source
|
|
130
|
+
const fn = eval(`(${fnCode})`); // eslint-disable-line no-eval
|
|
131
|
+
if (typeof fn !== "function") {
|
|
132
|
+
throw new Error("Reconstructed code is not a function");
|
|
184
133
|
}
|
|
185
|
-
|
|
186
|
-
// Stateless variant: run receives caseData directly
|
|
187
|
-
return { fn: () => run(caseData), params: undefined };
|
|
134
|
+
return fn;
|
|
188
135
|
}
|
|
189
136
|
|
|
190
|
-
/**
|
|
191
|
-
async function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
): Promise<
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
137
|
+
/** Run benchmark with optional heap, time, and coverage profiling */
|
|
138
|
+
async function runWithProfiling(
|
|
139
|
+
message: RunMessage,
|
|
140
|
+
runner: BenchRunner,
|
|
141
|
+
): Promise<ResultMessage> {
|
|
142
|
+
const state: ProfilingState = {};
|
|
143
|
+
const runBench = buildProfilingChain(message, runner, state);
|
|
144
|
+
|
|
145
|
+
if (!message.options.callCounts) {
|
|
146
|
+
const results = await runBench();
|
|
147
|
+
return { type: "result", results, ...state };
|
|
199
148
|
}
|
|
200
|
-
|
|
149
|
+
|
|
150
|
+
const { withCoverageProfiling } = await import(
|
|
151
|
+
"../profiling/node/CoverageSampler.ts"
|
|
152
|
+
);
|
|
153
|
+
const r = await withCoverageProfiling(async session => {
|
|
154
|
+
state.profilerSession = session;
|
|
155
|
+
return runBench();
|
|
156
|
+
});
|
|
157
|
+
state.coverage = r.coverage;
|
|
158
|
+
return { type: "result", results: r.result, ...state };
|
|
201
159
|
}
|
|
202
160
|
|
|
203
|
-
/**
|
|
204
|
-
|
|
161
|
+
/** Build nested profiling wrappers: outer heap, inner time */
|
|
162
|
+
function buildProfilingChain(
|
|
205
163
|
message: RunMessage,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
164
|
+
runner: BenchRunner,
|
|
165
|
+
state: ProfilingState,
|
|
166
|
+
): () => Promise<MeasuredResults[]> {
|
|
167
|
+
const { alloc, profile, profileInterval, allocInterval, allocDepth } =
|
|
168
|
+
message.options;
|
|
169
|
+
|
|
170
|
+
const run = async () => {
|
|
171
|
+
const { fn, params } = await resolveBenchmarkFn(message);
|
|
172
|
+
return runner.runBench({ ...message.spec, fn }, message.options, params);
|
|
173
|
+
};
|
|
212
174
|
|
|
213
|
-
const
|
|
175
|
+
const runMaybeWithTime = profile
|
|
176
|
+
? async () => {
|
|
177
|
+
const { withTimeProfiling } = await import(
|
|
178
|
+
"../profiling/node/TimeSampler.ts"
|
|
179
|
+
);
|
|
180
|
+
const opts = {
|
|
181
|
+
interval: profileInterval,
|
|
182
|
+
session: state.profilerSession,
|
|
183
|
+
};
|
|
184
|
+
const r = await withTimeProfiling(opts, run);
|
|
185
|
+
state.timeProfile = r.profile;
|
|
186
|
+
return r.result;
|
|
187
|
+
}
|
|
188
|
+
: run;
|
|
189
|
+
|
|
190
|
+
return alloc
|
|
191
|
+
? async () => {
|
|
192
|
+
const { withHeapSampling } = await import(
|
|
193
|
+
"../profiling/node/HeapSampler.ts"
|
|
194
|
+
);
|
|
195
|
+
const heapOpts = {
|
|
196
|
+
samplingInterval: allocInterval,
|
|
197
|
+
stackDepth: allocDepth,
|
|
198
|
+
};
|
|
199
|
+
const r = await withHeapSampling(heapOpts, runMaybeWithTime);
|
|
200
|
+
state.heapProfile = r.profile;
|
|
201
|
+
return r.result;
|
|
202
|
+
}
|
|
203
|
+
: runMaybeWithTime;
|
|
204
|
+
}
|
|
214
205
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const setupFn = getModuleExport(module, setupExportName, modulePath!);
|
|
218
|
-
const setupResult = await setupFn(params);
|
|
219
|
-
return { fn, params: setupResult };
|
|
220
|
-
}
|
|
206
|
+
process.on("message", async (message: RunMessage) => {
|
|
207
|
+
if (message.type !== "run") return;
|
|
221
208
|
|
|
222
|
-
|
|
223
|
-
}
|
|
209
|
+
logTiming(`Processing ${message.spec.name} with ${message.runnerName}`);
|
|
224
210
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
modulePath: string,
|
|
230
|
-
): BenchmarkFunction {
|
|
231
|
-
const fn = exportName ? module[exportName] : module.default || module;
|
|
232
|
-
if (typeof fn !== "function") {
|
|
233
|
-
const name = exportName || "default";
|
|
234
|
-
throw new Error(`Export '${name}' from ${modulePath} is not a function`);
|
|
235
|
-
}
|
|
236
|
-
return fn;
|
|
237
|
-
}
|
|
211
|
+
try {
|
|
212
|
+
const start = getPerfNow();
|
|
213
|
+
const runner = await createBenchRunner(message.runnerName, message.options);
|
|
214
|
+
logTiming("Runner created in", getElapsed(start));
|
|
238
215
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
216
|
+
const benchStart = getPerfNow();
|
|
217
|
+
const result = await runWithProfiling(message, runner);
|
|
218
|
+
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
219
|
+
sendAndExit(result, 0);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const err = error instanceof Error ? error : undefined;
|
|
222
|
+
sendAndExit(
|
|
223
|
+
{
|
|
224
|
+
type: "error",
|
|
225
|
+
error: err?.message ?? String(error),
|
|
226
|
+
stack: err?.stack,
|
|
227
|
+
},
|
|
228
|
+
1,
|
|
229
|
+
);
|
|
245
230
|
}
|
|
246
|
-
|
|
247
|
-
}
|
|
231
|
+
});
|
|
248
232
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
233
|
+
// Prevent zombie processes
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
console.error("WorkerScript: Maximum lifetime exceeded, exiting");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}, maxLifetime);
|
|
238
|
+
|
|
239
|
+
// Prevent stdin from keeping the worker process alive
|
|
240
|
+
process.stdin.pause();
|