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,16 +1,19 @@
|
|
|
1
1
|
import { type ChildProcess, fork } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type {
|
|
5
|
-
import type { HeapProfile } from "../
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
type AdaptiveOptions,
|
|
9
|
-
createAdaptiveWrapper,
|
|
10
|
-
} from "./AdaptiveWrapper.ts";
|
|
4
|
+
import type { CoverageData } from "../profiling/node/CoverageTypes.ts";
|
|
5
|
+
import type { HeapProfile } from "../profiling/node/HeapSampler.ts";
|
|
6
|
+
import type { TimeProfile } from "../profiling/node/TimeSampler.ts";
|
|
7
|
+
import type { BenchmarkFunction, BenchmarkSpec } from "./BenchmarkSpec.ts";
|
|
11
8
|
import type { RunnerOptions } from "./BenchRunner.ts";
|
|
12
|
-
import {
|
|
9
|
+
import type { KnownRunner } from "./CreateRunner.ts";
|
|
13
10
|
import { aggregateGcStats, type GcEvent, parseGcLine } from "./GcStats.ts";
|
|
11
|
+
import type { MeasuredResults } from "./MeasuredResults.ts";
|
|
12
|
+
import {
|
|
13
|
+
createBenchRunner,
|
|
14
|
+
importBenchFn,
|
|
15
|
+
resolveVariantFn,
|
|
16
|
+
} from "./RunnerUtils.ts";
|
|
14
17
|
import { debugWorkerTiming, getElapsed, getPerfNow } from "./TimingUtils.ts";
|
|
15
18
|
import type {
|
|
16
19
|
ErrorMessage,
|
|
@@ -18,21 +21,17 @@ import type {
|
|
|
18
21
|
RunMessage,
|
|
19
22
|
} from "./WorkerScript.ts";
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
/** Parameters for running a matrix variant */
|
|
25
|
+
export interface RunMatrixVariantParams {
|
|
26
|
+
variantDir: string;
|
|
27
|
+
variantId: string;
|
|
28
|
+
caseId: string;
|
|
29
|
+
caseData?: unknown;
|
|
30
|
+
casesModule?: string;
|
|
27
31
|
runner: KnownRunner;
|
|
28
32
|
options: RunnerOptions;
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type WorkerHandlers = {
|
|
33
|
-
resolve: (results: MeasuredResults[], heapProfile?: HeapProfile) => void;
|
|
34
|
-
reject: (error: Error) => void;
|
|
35
|
-
};
|
|
33
|
+
useWorker?: boolean;
|
|
34
|
+
}
|
|
36
35
|
|
|
37
36
|
interface RunBenchmarkParams<T = unknown> {
|
|
38
37
|
spec: BenchmarkSpec<T>;
|
|
@@ -42,7 +41,11 @@ interface RunBenchmarkParams<T = unknown> {
|
|
|
42
41
|
params?: T;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
const logTiming = debugWorkerTiming
|
|
45
|
+
? (message: string) => console.log(`[RunnerOrchestrator] ${message}`)
|
|
46
|
+
: () => {};
|
|
47
|
+
|
|
48
|
+
/** Run a benchmark spec, optionally in an isolated worker process for profiling support. */
|
|
46
49
|
export async function runBenchmark<T = unknown>({
|
|
47
50
|
spec,
|
|
48
51
|
runner,
|
|
@@ -51,22 +54,39 @@ export async function runBenchmark<T = unknown>({
|
|
|
51
54
|
params,
|
|
52
55
|
}: RunBenchmarkParams<T>): Promise<MeasuredResults[]> {
|
|
53
56
|
if (!useWorker) {
|
|
54
|
-
const
|
|
57
|
+
const resolved = spec.modulePath
|
|
55
58
|
? await resolveModuleSpec(spec, params)
|
|
56
59
|
: { spec, params };
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const benchRunner = (options as any).adaptive
|
|
60
|
-
? createAdaptiveWrapper(base, options as AdaptiveOptions)
|
|
61
|
-
: base;
|
|
62
|
-
return benchRunner.runBench(
|
|
63
|
-
resolvedSpec.spec,
|
|
64
|
-
options,
|
|
65
|
-
resolvedSpec.params,
|
|
66
|
-
);
|
|
60
|
+
const benchRunner = await createBenchRunner(runner, options);
|
|
61
|
+
return benchRunner.runBench(resolved.spec, options, resolved.params);
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
|
|
64
|
+
const msg = createRunMessage(spec, runner, options, params);
|
|
65
|
+
return runWorkerWithMessage(spec.name, options, msg);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Run a matrix variant benchmark, directly or in a worker. */
|
|
69
|
+
export async function runMatrixVariant(
|
|
70
|
+
params: RunMatrixVariantParams,
|
|
71
|
+
): Promise<MeasuredResults[]> {
|
|
72
|
+
const { variantId, caseId, runner, options, useWorker = true } = params;
|
|
73
|
+
const name = `${variantId}/${caseId}`;
|
|
74
|
+
|
|
75
|
+
if (!useWorker) return runMatrixVariantDirect(params, name);
|
|
76
|
+
|
|
77
|
+
const { variantDir, caseData, casesModule } = params;
|
|
78
|
+
const message: RunMessage = {
|
|
79
|
+
type: "run",
|
|
80
|
+
spec: { name } as BenchmarkSpec,
|
|
81
|
+
runnerName: runner,
|
|
82
|
+
options,
|
|
83
|
+
variantDir,
|
|
84
|
+
variantId,
|
|
85
|
+
caseId,
|
|
86
|
+
caseData,
|
|
87
|
+
casesModule,
|
|
88
|
+
};
|
|
89
|
+
return runWorkerWithMessage(name, options, message);
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
/** Resolve modulePath/exportName to a real function for non-worker mode */
|
|
@@ -74,74 +94,43 @@ async function resolveModuleSpec<T>(
|
|
|
74
94
|
spec: BenchmarkSpec<T>,
|
|
75
95
|
params: T | undefined,
|
|
76
96
|
): Promise<{ spec: BenchmarkSpec<T>; params: T | undefined }> {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (typeof fn !== "function") {
|
|
84
|
-
const name = spec.exportName || "default";
|
|
85
|
-
throw new Error(
|
|
86
|
-
`Export '${name}' from ${spec.modulePath} is not a function`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let resolvedParams = params;
|
|
91
|
-
if (spec.setupExportName) {
|
|
92
|
-
const setupFn = module[spec.setupExportName];
|
|
93
|
-
if (typeof setupFn !== "function") {
|
|
94
|
-
const msg = `Setup export '${spec.setupExportName}' from ${spec.modulePath} is not a function`;
|
|
95
|
-
throw new Error(msg);
|
|
96
|
-
}
|
|
97
|
-
resolvedParams = await setupFn(params);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return { spec: { ...spec, fn }, params: resolvedParams };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Run benchmark in isolated worker process */
|
|
104
|
-
async function runInWorker<T>(
|
|
105
|
-
workerParams: WorkerParams<T>,
|
|
106
|
-
): Promise<MeasuredResults[]> {
|
|
107
|
-
const { spec, runner, options, params } = workerParams;
|
|
108
|
-
const msg = createRunMessage(spec, runner, options, params);
|
|
109
|
-
return runWorkerWithMessage(spec.name, options, msg);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Create worker process with timing logs */
|
|
113
|
-
function createWorkerWithTiming(gcStats: boolean) {
|
|
114
|
-
const workerStart = getPerfNow();
|
|
115
|
-
const gcEvents: GcEvent[] = [];
|
|
116
|
-
const worker = createWorkerProcess(gcStats);
|
|
117
|
-
const createTime = getPerfNow();
|
|
118
|
-
if (gcStats && worker.stdout) setupGcCapture(worker, gcEvents);
|
|
119
|
-
logTiming(
|
|
120
|
-
`Worker process created in ${getElapsed(workerStart, createTime).toFixed(1)}ms`,
|
|
97
|
+
const { modulePath, exportName, setupExportName } = spec;
|
|
98
|
+
const imported = await importBenchFn(
|
|
99
|
+
modulePath!,
|
|
100
|
+
exportName,
|
|
101
|
+
setupExportName,
|
|
102
|
+
params,
|
|
121
103
|
);
|
|
122
|
-
|
|
104
|
+
const fn = imported.fn as BenchmarkFunction<T>;
|
|
105
|
+
return { spec: { ...spec, fn }, params: imported.params as T | undefined };
|
|
123
106
|
}
|
|
124
107
|
|
|
125
|
-
/**
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
108
|
+
/** Serialize a BenchmarkSpec into a worker-safe message (modulePath or fnCode) */
|
|
109
|
+
function createRunMessage<T>(
|
|
110
|
+
spec: BenchmarkSpec<T>,
|
|
111
|
+
runnerName: KnownRunner,
|
|
112
|
+
options: RunnerOptions,
|
|
113
|
+
params?: T,
|
|
114
|
+
): RunMessage {
|
|
115
|
+
const { fn, ...rest } = spec;
|
|
116
|
+
const message: RunMessage = {
|
|
117
|
+
type: "run",
|
|
118
|
+
spec: rest as BenchmarkSpec,
|
|
119
|
+
runnerName,
|
|
120
|
+
options,
|
|
121
|
+
params,
|
|
122
|
+
};
|
|
123
|
+
if (spec.modulePath) {
|
|
124
|
+
message.modulePath = spec.modulePath;
|
|
125
|
+
message.exportName = spec.exportName;
|
|
126
|
+
if (spec.setupExportName) message.setupExportName = spec.setupExportName;
|
|
127
|
+
} else {
|
|
128
|
+
message.fnCode = fn.toString();
|
|
129
|
+
}
|
|
130
|
+
return message;
|
|
142
131
|
}
|
|
143
132
|
|
|
144
|
-
/**
|
|
133
|
+
/** Run a benchmark in an isolated worker process with timeout and GC capture. */
|
|
145
134
|
function runWorkerWithMessage(
|
|
146
135
|
name: string,
|
|
147
136
|
options: RunnerOptions,
|
|
@@ -152,227 +141,122 @@ function runWorkerWithMessage(
|
|
|
152
141
|
logTiming(`Starting worker for ${name}`);
|
|
153
142
|
|
|
154
143
|
return new Promise((resolve, reject) => {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
name,
|
|
159
|
-
startTime,
|
|
160
|
-
gcEvents,
|
|
161
|
-
resolve,
|
|
162
|
-
reject,
|
|
163
|
-
);
|
|
164
|
-
setupWorkerHandlers(worker, name, handlers);
|
|
165
|
-
sendWorkerMessage(worker, message, createTime);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Send message to worker with timing log */
|
|
170
|
-
function sendWorkerMessage(
|
|
171
|
-
worker: ReturnType<typeof createWorkerProcess>,
|
|
172
|
-
message: RunMessage,
|
|
173
|
-
createTime: number,
|
|
174
|
-
): void {
|
|
175
|
-
const messageTime = getPerfNow();
|
|
176
|
-
worker.send(message);
|
|
177
|
-
logTiming(
|
|
178
|
-
`Message sent to worker in ${getElapsed(createTime, messageTime).toFixed(1)}ms`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
144
|
+
const gcEvents: GcEvent[] = [];
|
|
145
|
+
const worker = spawnWorkerProcess(collectGcStats);
|
|
146
|
+
if (collectGcStats && worker.stdout) setupGcCapture(worker, gcEvents);
|
|
181
147
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
handlers: WorkerHandlers,
|
|
187
|
-
) {
|
|
188
|
-
const { resolve, reject } = handlers;
|
|
189
|
-
const cleanup = createCleanup(worker, specName, reject);
|
|
190
|
-
worker.on(
|
|
191
|
-
"message",
|
|
192
|
-
createMessageHandler(specName, cleanup, resolve, reject),
|
|
193
|
-
);
|
|
194
|
-
worker.on("error", createErrorHandler(specName, cleanup, reject));
|
|
195
|
-
worker.on("exit", createExitHandler(specName, cleanup, reject));
|
|
196
|
-
}
|
|
148
|
+
const timeoutId = setTimeout(() => {
|
|
149
|
+
killWorker();
|
|
150
|
+
reject(new Error(`Benchmark "${name}" timed out after 60 seconds`));
|
|
151
|
+
}, 60000);
|
|
197
152
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
cleanup: () => void,
|
|
202
|
-
resolve: (results: MeasuredResults[], heapProfile?: HeapProfile) => void,
|
|
203
|
-
reject: (error: Error) => void,
|
|
204
|
-
) {
|
|
205
|
-
return (msg: ResultMessage | ErrorMessage) => {
|
|
206
|
-
cleanup();
|
|
207
|
-
if (msg.type === "result") {
|
|
208
|
-
resolve(msg.results, msg.heapProfile);
|
|
209
|
-
} else if (msg.type === "error") {
|
|
210
|
-
const error = new Error(`Benchmark "${specName}" failed: ${msg.error}`);
|
|
211
|
-
if (msg.stack) error.stack = msg.stack;
|
|
212
|
-
reject(error);
|
|
153
|
+
function killWorker() {
|
|
154
|
+
clearTimeout(timeoutId);
|
|
155
|
+
if (!worker.killed) worker.kill("SIGTERM");
|
|
213
156
|
}
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
157
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return (code: number | null, _signal: NodeJS.Signals | null) => {
|
|
240
|
-
if (code !== 0 && code !== null) {
|
|
241
|
-
cleanup();
|
|
242
|
-
const msg = `Worker exited with code ${code} for benchmark "${specName}"`;
|
|
158
|
+
worker.on("message", (msg: ResultMessage | ErrorMessage) => {
|
|
159
|
+
killWorker();
|
|
160
|
+
if (msg.type === "error") {
|
|
161
|
+
const error = new Error(`Benchmark "${name}" failed: ${msg.error}`);
|
|
162
|
+
if (msg.stack) error.stack = msg.stack;
|
|
163
|
+
return reject(error);
|
|
164
|
+
}
|
|
165
|
+
const elapsed = getElapsed(startTime).toFixed(1);
|
|
166
|
+
logTiming(`Total worker time for ${name}: ${elapsed}ms`);
|
|
167
|
+
const { results, heapProfile, timeProfile, coverage } = msg;
|
|
168
|
+
attachProfilingData(
|
|
169
|
+
results,
|
|
170
|
+
gcEvents,
|
|
171
|
+
heapProfile,
|
|
172
|
+
timeProfile,
|
|
173
|
+
coverage,
|
|
174
|
+
);
|
|
175
|
+
resolve(results);
|
|
176
|
+
});
|
|
177
|
+
worker.on("error", (error: Error) => {
|
|
178
|
+
killWorker();
|
|
179
|
+
const msg = `Worker process failed for "${name}": ${error.message}`;
|
|
243
180
|
reject(new Error(msg));
|
|
244
|
-
}
|
|
245
|
-
|
|
181
|
+
});
|
|
182
|
+
worker.on("exit", (code: number | null) => {
|
|
183
|
+
if (code !== 0 && code !== null) {
|
|
184
|
+
killWorker();
|
|
185
|
+
reject(new Error(`Worker exited with code ${code} for "${name}"`));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
worker.send(message);
|
|
190
|
+
});
|
|
246
191
|
}
|
|
247
192
|
|
|
248
|
-
/**
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}, 60000);
|
|
258
|
-
const cleanup = () => {
|
|
259
|
-
clearTimeout(timeoutId);
|
|
260
|
-
if (!worker.killed) worker.kill("SIGTERM");
|
|
261
|
-
};
|
|
262
|
-
return cleanup;
|
|
193
|
+
/** Run matrix variant in-process (no worker isolation) */
|
|
194
|
+
async function runMatrixVariantDirect(
|
|
195
|
+
params: RunMatrixVariantParams,
|
|
196
|
+
name: string,
|
|
197
|
+
): Promise<MeasuredResults[]> {
|
|
198
|
+
const { runner, options } = params;
|
|
199
|
+
const { fn } = await resolveVariantFn(params);
|
|
200
|
+
const benchRunner = await createBenchRunner(runner, options);
|
|
201
|
+
return benchRunner.runBench({ name, fn }, options);
|
|
263
202
|
}
|
|
264
203
|
|
|
265
|
-
/**
|
|
266
|
-
function
|
|
204
|
+
/** Spawn worker process with V8 flags */
|
|
205
|
+
function spawnWorkerProcess(gcStats: boolean) {
|
|
267
206
|
const workerPath = resolveWorkerPath();
|
|
268
207
|
const execArgv = ["--expose-gc", "--allow-natives-syntax"];
|
|
269
208
|
if (gcStats) execArgv.push("--trace-gc-nvp");
|
|
270
209
|
|
|
210
|
+
const env = { ...process.env, NODE_OPTIONS: "" };
|
|
211
|
+
// silent mode captures stdout so we can parse --trace-gc-nvp output
|
|
271
212
|
return fork(workerPath, [], {
|
|
272
213
|
execArgv,
|
|
273
|
-
silent: gcStats,
|
|
274
|
-
env
|
|
275
|
-
|
|
276
|
-
NODE_OPTIONS: "",
|
|
277
|
-
},
|
|
214
|
+
silent: gcStats,
|
|
215
|
+
env,
|
|
216
|
+
serialization: "advanced",
|
|
278
217
|
});
|
|
279
218
|
}
|
|
280
219
|
|
|
281
|
-
/**
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
220
|
+
/** Capture and parse GC lines from worker stdout (--trace-gc-nvp). */
|
|
221
|
+
function setupGcCapture(worker: ChildProcess, gcEvents: GcEvent[]): void {
|
|
222
|
+
let buffer = "";
|
|
223
|
+
worker.stdout!.on("data", (data: Buffer) => {
|
|
224
|
+
buffer += data.toString();
|
|
225
|
+
const lines = buffer.split("\n");
|
|
226
|
+
buffer = lines.pop() || "";
|
|
227
|
+
for (const line of lines) {
|
|
228
|
+
const event = parseGcLine(line);
|
|
229
|
+
if (event) gcEvents.push(event);
|
|
230
|
+
else if (line.trim()) process.stdout.write(line + "\n");
|
|
231
|
+
}
|
|
232
|
+
});
|
|
287
233
|
}
|
|
288
234
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
/** @return handlers that attach GC stats and heap profile to results */
|
|
293
|
-
function createWorkerHandlers(
|
|
294
|
-
specName: string,
|
|
295
|
-
startTime: number,
|
|
235
|
+
/** Attach profiling data collected by the worker to each result. */
|
|
236
|
+
function attachProfilingData(
|
|
237
|
+
results: MeasuredResults[],
|
|
296
238
|
gcEvents: GcEvent[] | undefined,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
for (const r of results) r.gcStats = gcStats;
|
|
308
|
-
}
|
|
309
|
-
if (heapProfile) for (const r of results) r.heapProfile = heapProfile;
|
|
310
|
-
resolve(results);
|
|
311
|
-
},
|
|
312
|
-
reject,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** Create message for worker execution */
|
|
317
|
-
function createRunMessage<T>(
|
|
318
|
-
spec: BenchmarkSpec<T>,
|
|
319
|
-
runnerName: KnownRunner,
|
|
320
|
-
options: RunnerOptions,
|
|
321
|
-
params?: T,
|
|
322
|
-
): RunMessage {
|
|
323
|
-
const { fn, ...rest } = spec;
|
|
324
|
-
const message: RunMessage = {
|
|
325
|
-
type: "run",
|
|
326
|
-
spec: rest as BenchmarkSpec,
|
|
327
|
-
runnerName,
|
|
328
|
-
options,
|
|
329
|
-
params,
|
|
239
|
+
heapProfile?: HeapProfile,
|
|
240
|
+
timeProfile?: TimeProfile,
|
|
241
|
+
coverage?: CoverageData,
|
|
242
|
+
): void {
|
|
243
|
+
const gcStats = gcEvents?.length ? aggregateGcStats(gcEvents) : undefined;
|
|
244
|
+
const attach = <K extends keyof MeasuredResults>(
|
|
245
|
+
key: K,
|
|
246
|
+
value: MeasuredResults[K] | undefined,
|
|
247
|
+
) => {
|
|
248
|
+
if (value) for (const r of results) r[key] = value;
|
|
330
249
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
} else {
|
|
336
|
-
message.fnCode = fn.toString();
|
|
337
|
-
}
|
|
338
|
-
return message;
|
|
250
|
+
attach("gcStats", gcStats);
|
|
251
|
+
attach("heapProfile", heapProfile);
|
|
252
|
+
attach("timeProfile", timeProfile);
|
|
253
|
+
attach("coverage", coverage);
|
|
339
254
|
}
|
|
340
255
|
|
|
341
|
-
/**
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
casesModule?: string;
|
|
348
|
-
runner: KnownRunner;
|
|
349
|
-
options: RunnerOptions;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/** Run a matrix variant benchmark in isolated worker process */
|
|
353
|
-
export async function runMatrixVariant(
|
|
354
|
-
params: RunMatrixVariantParams,
|
|
355
|
-
): Promise<MeasuredResults[]> {
|
|
356
|
-
const {
|
|
357
|
-
variantDir,
|
|
358
|
-
variantId,
|
|
359
|
-
caseId,
|
|
360
|
-
caseData,
|
|
361
|
-
casesModule,
|
|
362
|
-
runner,
|
|
363
|
-
options,
|
|
364
|
-
} = params;
|
|
365
|
-
const name = `${variantId}/${caseId}`;
|
|
366
|
-
const message: RunMessage = {
|
|
367
|
-
type: "run",
|
|
368
|
-
spec: { name, fn: () => {} },
|
|
369
|
-
runnerName: runner,
|
|
370
|
-
options,
|
|
371
|
-
variantDir,
|
|
372
|
-
variantId,
|
|
373
|
-
caseId,
|
|
374
|
-
caseData,
|
|
375
|
-
casesModule,
|
|
376
|
-
};
|
|
377
|
-
return runWorkerWithMessage(name, options, message);
|
|
256
|
+
/** Resolve WorkerScript path for dev (.ts) or dist (.mjs) */
|
|
257
|
+
function resolveWorkerPath(): string {
|
|
258
|
+
const dir = import.meta.dirname!;
|
|
259
|
+
const tsPath = path.join(dir, "WorkerScript.ts");
|
|
260
|
+
if (existsSync(tsPath)) return tsPath;
|
|
261
|
+
return path.join(dir, "runners", "WorkerScript.mjs");
|
|
378
262
|
}
|
|
@@ -1,2 +1,76 @@
|
|
|
1
|
+
import { prepareBenchFn } from "../matrix/BenchMatrix.ts";
|
|
2
|
+
import { loadCaseData, loadCasesModule } from "../matrix/CaseLoader.ts";
|
|
3
|
+
import { loadVariant } from "../matrix/VariantLoader.ts";
|
|
4
|
+
import {
|
|
5
|
+
type AdaptiveOptions,
|
|
6
|
+
createAdaptiveWrapper,
|
|
7
|
+
} from "./AdaptiveWrapper.ts";
|
|
8
|
+
import type { BenchmarkFunction } from "./BenchmarkSpec.ts";
|
|
9
|
+
import type { BenchRunner, RunnerOptions } from "./BenchRunner.ts";
|
|
10
|
+
import { createRunner, type KnownRunner } from "./CreateRunner.ts";
|
|
11
|
+
|
|
1
12
|
export const msToNs = 1e6;
|
|
2
|
-
|
|
13
|
+
|
|
14
|
+
/** Get named or default export from module, throw if not a function */
|
|
15
|
+
// biome-ignore lint/complexity/noBannedTypes: generic function constraint
|
|
16
|
+
export function getModuleExport<T extends Function = Function>(
|
|
17
|
+
module: any,
|
|
18
|
+
exportName: string | undefined,
|
|
19
|
+
modulePath: string,
|
|
20
|
+
): T {
|
|
21
|
+
const fn = exportName ? module[exportName] : module.default || module;
|
|
22
|
+
if (typeof fn !== "function") {
|
|
23
|
+
const name = exportName || "default";
|
|
24
|
+
throw new Error(`Export '${name}' from ${modulePath} is not a function`);
|
|
25
|
+
}
|
|
26
|
+
return fn as T;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Import a benchmark function from a module, optionally running a setup export */
|
|
30
|
+
export async function importBenchFn(
|
|
31
|
+
modulePath: string,
|
|
32
|
+
exportName: string | undefined,
|
|
33
|
+
setupExportName: string | undefined,
|
|
34
|
+
params: unknown,
|
|
35
|
+
): Promise<{ fn: BenchmarkFunction; params: unknown }> {
|
|
36
|
+
const module = await import(modulePath);
|
|
37
|
+
const fn = getModuleExport<BenchmarkFunction>(module, exportName, modulePath);
|
|
38
|
+
if (!setupExportName) return { fn, params };
|
|
39
|
+
|
|
40
|
+
const setup = getModuleExport<BenchmarkFunction>(
|
|
41
|
+
module,
|
|
42
|
+
setupExportName,
|
|
43
|
+
modulePath,
|
|
44
|
+
);
|
|
45
|
+
return { fn, params: await setup(params) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Resolve a matrix variant to a benchmark function (shared by orchestrator and worker). */
|
|
49
|
+
export async function resolveVariantFn(params: {
|
|
50
|
+
variantDir: string;
|
|
51
|
+
variantId: string;
|
|
52
|
+
caseId?: string;
|
|
53
|
+
caseData?: unknown;
|
|
54
|
+
casesModule?: string;
|
|
55
|
+
}): Promise<{ fn: BenchmarkFunction; params: undefined }> {
|
|
56
|
+
let { caseData } = params;
|
|
57
|
+
if (params.casesModule && params.caseId) {
|
|
58
|
+
const cases = await loadCasesModule(params.casesModule);
|
|
59
|
+
caseData = (await loadCaseData(cases, params.caseId)).data;
|
|
60
|
+
}
|
|
61
|
+
const variant = await loadVariant(params.variantDir, params.variantId);
|
|
62
|
+
const fn = await prepareBenchFn(variant, caseData);
|
|
63
|
+
return { fn, params: undefined };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Create runner, wrapping with adaptive sampling if options.adaptive is set */
|
|
67
|
+
export async function createBenchRunner(
|
|
68
|
+
runnerName: KnownRunner,
|
|
69
|
+
options: RunnerOptions,
|
|
70
|
+
): Promise<BenchRunner> {
|
|
71
|
+
const base = await createRunner(runnerName);
|
|
72
|
+
if ("adaptive" in options && options.adaptive) {
|
|
73
|
+
return createAdaptiveWrapper(base, options as AdaptiveOptions);
|
|
74
|
+
}
|
|
75
|
+
return base;
|
|
76
|
+
}
|