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
package/src/cli/RunBenchCLI.ts
CHANGED
|
@@ -1,777 +1,139 @@
|
|
|
1
1
|
import { basename, resolve } from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
|
-
import pico from "picocolors";
|
|
4
3
|
import { hideBin } from "yargs/helpers";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
MatrixSuite,
|
|
8
|
-
RunMatrixOptions,
|
|
9
|
-
} from "../BenchMatrix.ts";
|
|
10
|
-
import { runMatrix } from "../BenchMatrix.ts";
|
|
11
|
-
import type { BenchGroup, BenchmarkSpec, BenchSuite } from "../Benchmark.ts";
|
|
12
|
-
import type {
|
|
13
|
-
BenchmarkReport,
|
|
14
|
-
ReportGroup,
|
|
15
|
-
ResultsMapper,
|
|
16
|
-
} from "../BenchmarkReport.ts";
|
|
17
|
-
import { reportResults } from "../BenchmarkReport.ts";
|
|
18
|
-
import type { BrowserProfileResult } from "../browser/BrowserHeapSampler.ts";
|
|
19
|
-
import { exportBenchmarkJson } from "../export/JsonExport.ts";
|
|
20
|
-
import { exportPerfettoTrace } from "../export/PerfettoExport.ts";
|
|
21
|
-
import type { GitVersion } from "../GitUtils.ts";
|
|
22
|
-
import { prepareHtmlData } from "../HtmlDataPrep.ts";
|
|
23
|
-
import {
|
|
24
|
-
aggregateSites,
|
|
25
|
-
filterSites,
|
|
26
|
-
flattenProfile,
|
|
27
|
-
formatHeapReport,
|
|
28
|
-
type HeapReportOptions,
|
|
29
|
-
isBrowserUserCode,
|
|
30
|
-
totalProfileBytes,
|
|
31
|
-
} from "../heap-sample/HeapSampleReport.ts";
|
|
32
|
-
import { generateHtmlReport } from "../html/index.ts";
|
|
33
|
-
import type { MeasuredResults } from "../MeasuredResults.ts";
|
|
4
|
+
import type { MatrixResults, MatrixSuite } from "../matrix/BenchMatrix.ts";
|
|
5
|
+
import { runMatrix } from "../matrix/BenchMatrix.ts";
|
|
34
6
|
import { loadCasesModule } from "../matrix/CaseLoader.ts";
|
|
35
7
|
import {
|
|
36
8
|
type FilteredMatrix,
|
|
37
9
|
filterMatrix,
|
|
10
|
+
type MatrixFilter,
|
|
38
11
|
parseMatrixFilter,
|
|
12
|
+
resolveCaseIds,
|
|
13
|
+
resolveVariantIds,
|
|
39
14
|
} from "../matrix/MatrixFilter.ts";
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} from "
|
|
44
|
-
import { computeStats } from "../runners/BasicRunner.ts";
|
|
45
|
-
import type { RunnerOptions } from "../runners/BenchRunner.ts";
|
|
46
|
-
import type { KnownRunner } from "../runners/CreateRunner.ts";
|
|
47
|
-
import { runBenchmark } from "../runners/RunnerOrchestrator.ts";
|
|
48
|
-
import {
|
|
49
|
-
adaptiveSection,
|
|
50
|
-
browserGcStatsSection,
|
|
51
|
-
cpuSection,
|
|
52
|
-
gcStatsSection,
|
|
53
|
-
optSection,
|
|
54
|
-
runsSection,
|
|
55
|
-
timeSection,
|
|
56
|
-
totalTimeSection,
|
|
57
|
-
} from "../StandardSections.ts";
|
|
15
|
+
import type { MatrixReportOptions } from "../matrix/MatrixReport.ts";
|
|
16
|
+
import type { ReportSection } from "../report/BenchmarkReport.ts";
|
|
17
|
+
import type { BenchSuite } from "../runners/BenchmarkSpec.ts";
|
|
18
|
+
import { browserBenchExports } from "./BrowserBench.ts";
|
|
58
19
|
import {
|
|
59
20
|
type Configure,
|
|
60
21
|
type DefaultCliArgs,
|
|
61
|
-
defaultAdaptiveMaxTime,
|
|
62
22
|
parseCliArgs,
|
|
63
23
|
} from "./CliArgs.ts";
|
|
64
|
-
import {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
24
|
+
import { finishReports, type MatrixExportOptions } from "./CliExport.ts";
|
|
25
|
+
import { cliToMatrixOptions, validateArgs } from "./CliOptions.ts";
|
|
26
|
+
import {
|
|
27
|
+
defaultMatrixReport,
|
|
28
|
+
defaultReport,
|
|
29
|
+
matrixToReportGroups,
|
|
30
|
+
withStatus,
|
|
31
|
+
} from "./CliReport.ts";
|
|
32
|
+
import { runBenchmarks } from "./SuiteRunner.ts";
|
|
74
33
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!args.worker) ignored.push("--no-worker");
|
|
79
|
-
if (args.cpu) ignored.push("--cpu");
|
|
80
|
-
if (args["trace-opt"]) ignored.push("--trace-opt");
|
|
81
|
-
if (args.collect) ignored.push("--collect");
|
|
82
|
-
if (args.adaptive) ignored.push("--adaptive");
|
|
83
|
-
if (args.batches > 1) ignored.push("--batches");
|
|
84
|
-
if (ignored.length) {
|
|
85
|
-
console.warn(yellow(`Ignored in browser mode: ${ignored.join(", ")}`));
|
|
86
|
-
}
|
|
34
|
+
/** Options for running a BenchSuite: custom sections replace the CLI-derived defaults. */
|
|
35
|
+
export interface BenchExportsOptions {
|
|
36
|
+
sections?: ReportSection[];
|
|
87
37
|
}
|
|
88
38
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
options: RunnerOptions;
|
|
92
|
-
useWorker: boolean;
|
|
93
|
-
params: unknown;
|
|
94
|
-
metadata?: Record<string, any>;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
type SuiteParams = {
|
|
98
|
-
runner: KnownRunner;
|
|
99
|
-
options: RunnerOptions;
|
|
100
|
-
useWorker: boolean;
|
|
101
|
-
suite: BenchSuite;
|
|
102
|
-
batches: number;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/** Parse CLI with custom configuration */
|
|
106
|
-
export function parseBenchArgs<T = DefaultCliArgs>(
|
|
107
|
-
configureArgs?: Configure<T>,
|
|
108
|
-
): T & DefaultCliArgs {
|
|
39
|
+
/** Top-level CLI dispatch: route to view, analyze, or default bench runner. */
|
|
40
|
+
export async function dispatchCli(): Promise<void> {
|
|
109
41
|
const argv = hideBin(process.argv);
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Run suite with CLI arguments */
|
|
114
|
-
export async function runBenchmarks(
|
|
115
|
-
suite: BenchSuite,
|
|
116
|
-
args: DefaultCliArgs,
|
|
117
|
-
): Promise<ReportGroup[]> {
|
|
118
|
-
validateArgs(args);
|
|
119
|
-
const { filter, worker: useWorker, batches = 1 } = args;
|
|
120
|
-
const options = cliToRunnerOptions(args);
|
|
121
|
-
const filtered = filterBenchmarks(suite, filter);
|
|
122
|
-
|
|
123
|
-
return runSuite({
|
|
124
|
-
suite: filtered,
|
|
125
|
-
runner: "basic",
|
|
126
|
-
options,
|
|
127
|
-
useWorker,
|
|
128
|
-
batches,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
42
|
+
const [command] = argv;
|
|
131
43
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const results: ReportGroup[] = [];
|
|
136
|
-
for (const group of suite.groups) {
|
|
137
|
-
results.push(await runGroup(group, runner, options, useWorker, batches));
|
|
44
|
+
if (command === "view") {
|
|
45
|
+
const { viewArchive } = await import("./ViewerServer.ts");
|
|
46
|
+
return viewArchive(requireFile(argv[1], "view"));
|
|
138
47
|
}
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Execute group with shared setup, optionally batching to reduce ordering bias */
|
|
143
|
-
async function runGroup(
|
|
144
|
-
group: BenchGroup,
|
|
145
|
-
runner: KnownRunner,
|
|
146
|
-
options: RunnerOptions,
|
|
147
|
-
useWorker: boolean,
|
|
148
|
-
batches = 1,
|
|
149
|
-
): Promise<ReportGroup> {
|
|
150
|
-
const { name, benchmarks, baseline, setup, metadata } = group;
|
|
151
|
-
const setupParams = await setup?.();
|
|
152
|
-
validateBenchmarkParameters(group);
|
|
153
|
-
|
|
154
|
-
const runParams = {
|
|
155
|
-
runner,
|
|
156
|
-
options,
|
|
157
|
-
useWorker,
|
|
158
|
-
params: setupParams,
|
|
159
|
-
metadata,
|
|
160
|
-
};
|
|
161
|
-
if (batches === 1) {
|
|
162
|
-
return runSingleBatch(name, benchmarks, baseline, runParams);
|
|
48
|
+
if (command === "analyze") {
|
|
49
|
+
const { analyzeArchive } = await import("./AnalyzeArchive.ts");
|
|
50
|
+
return analyzeArchive(requireFile(argv[1], "analyze"));
|
|
163
51
|
}
|
|
164
|
-
|
|
52
|
+
await runDefaultBench(undefined, undefined, argv);
|
|
165
53
|
}
|
|
166
54
|
|
|
167
|
-
/** Run benchmarks
|
|
168
|
-
async function
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
): Promise<ReportGroup> {
|
|
174
|
-
const baselineReport = baseline
|
|
175
|
-
? await runSingleBenchmark(baseline, runParams)
|
|
176
|
-
: undefined;
|
|
177
|
-
const reports = await serialMap(benchmarks, b =>
|
|
178
|
-
runSingleBenchmark(b, runParams),
|
|
179
|
-
);
|
|
180
|
-
return { name, reports, baseline: baselineReport };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Run benchmarks in multiple batches, alternating order to reduce bias */
|
|
184
|
-
async function runMultipleBatches(
|
|
185
|
-
name: string,
|
|
186
|
-
benchmarks: BenchmarkSpec[],
|
|
187
|
-
baseline: BenchmarkSpec | undefined,
|
|
188
|
-
runParams: RunParams,
|
|
189
|
-
batches: number,
|
|
190
|
-
): Promise<ReportGroup> {
|
|
191
|
-
const timePerBatch = (runParams.options.maxTime || 5000) / batches;
|
|
192
|
-
const batchParams = {
|
|
193
|
-
...runParams,
|
|
194
|
-
options: { ...runParams.options, maxTime: timePerBatch },
|
|
195
|
-
};
|
|
196
|
-
const baselineBatches: MeasuredResults[] = [];
|
|
197
|
-
const benchmarkBatches = new Map<string, MeasuredResults[]>();
|
|
198
|
-
|
|
199
|
-
for (let i = 0; i < batches; i++) {
|
|
200
|
-
const reverseOrder = i % 2 === 1;
|
|
201
|
-
await runBatchIteration(
|
|
202
|
-
benchmarks,
|
|
203
|
-
baseline,
|
|
204
|
-
batchParams,
|
|
205
|
-
reverseOrder,
|
|
206
|
-
baselineBatches,
|
|
207
|
-
benchmarkBatches,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const meta = runParams.metadata;
|
|
212
|
-
return mergeBatchResults(
|
|
213
|
-
name,
|
|
214
|
-
benchmarks,
|
|
215
|
-
baseline,
|
|
216
|
-
baselineBatches,
|
|
217
|
-
benchmarkBatches,
|
|
218
|
-
meta,
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** Run one batch iteration in either order */
|
|
223
|
-
async function runBatchIteration(
|
|
224
|
-
benchmarks: BenchmarkSpec[],
|
|
225
|
-
baseline: BenchmarkSpec | undefined,
|
|
226
|
-
runParams: RunParams,
|
|
227
|
-
reverseOrder: boolean,
|
|
228
|
-
baselineBatches: MeasuredResults[],
|
|
229
|
-
benchmarkBatches: Map<string, MeasuredResults[]>,
|
|
55
|
+
/** Run benchmarks and display results. Suite is optional with --url (browser mode). */
|
|
56
|
+
export async function runDefaultBench(
|
|
57
|
+
suite?: BenchSuite,
|
|
58
|
+
configureArgs?: Configure<any>,
|
|
59
|
+
argv?: string[],
|
|
60
|
+
opts?: BenchExportsOptions,
|
|
230
61
|
): Promise<void> {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
for (const b of benchmarks) {
|
|
239
|
-
const r = await runSingleBenchmark(b, runParams);
|
|
240
|
-
appendToMap(benchmarkBatches, b.name, r.measuredResults);
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
if (reverseOrder) {
|
|
245
|
-
await runBenches();
|
|
246
|
-
await runBaseline();
|
|
247
|
-
} else {
|
|
248
|
-
await runBaseline();
|
|
249
|
-
await runBenches();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/** Merge batch results into final ReportGroup */
|
|
254
|
-
function mergeBatchResults(
|
|
255
|
-
name: string,
|
|
256
|
-
benchmarks: BenchmarkSpec[],
|
|
257
|
-
baseline: BenchmarkSpec | undefined,
|
|
258
|
-
baselineBatches: MeasuredResults[],
|
|
259
|
-
benchmarkBatches: Map<string, MeasuredResults[]>,
|
|
260
|
-
metadata?: Record<string, unknown>,
|
|
261
|
-
): ReportGroup {
|
|
262
|
-
const mergedBaseline = baseline
|
|
263
|
-
? {
|
|
264
|
-
name: baseline.name,
|
|
265
|
-
measuredResults: mergeResults(baselineBatches),
|
|
266
|
-
metadata,
|
|
267
|
-
}
|
|
268
|
-
: undefined;
|
|
269
|
-
const reports = benchmarks.map(b => ({
|
|
270
|
-
name: b.name,
|
|
271
|
-
measuredResults: mergeResults(benchmarkBatches.get(b.name) || []),
|
|
272
|
-
metadata,
|
|
273
|
-
}));
|
|
274
|
-
return { name, reports, baseline: mergedBaseline };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** Run single benchmark and create report */
|
|
278
|
-
async function runSingleBenchmark(
|
|
279
|
-
spec: BenchmarkSpec,
|
|
280
|
-
runParams: RunParams,
|
|
281
|
-
): Promise<BenchmarkReport> {
|
|
282
|
-
const { runner, options, useWorker, params, metadata } = runParams;
|
|
283
|
-
const benchmarkParams = { spec, runner, options, useWorker, params };
|
|
284
|
-
const [result] = await runBenchmark(benchmarkParams);
|
|
285
|
-
return { name: spec.name, measuredResults: result, metadata };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** Warn if parameterized benchmarks lack setup */
|
|
289
|
-
function validateBenchmarkParameters(group: BenchGroup): void {
|
|
290
|
-
const { name, setup, benchmarks, baseline } = group;
|
|
291
|
-
if (setup) return;
|
|
292
|
-
|
|
293
|
-
const allBenchmarks = baseline ? [...benchmarks, baseline] : benchmarks;
|
|
294
|
-
for (const benchmark of allBenchmarks) {
|
|
295
|
-
if (benchmark.fn.length > 0) {
|
|
296
|
-
console.warn(
|
|
297
|
-
`Benchmark "${benchmark.name}" in group "${name}" expects parameters but no setup() provided.`,
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/** Merge multiple batch results into a single MeasuredResults */
|
|
304
|
-
function mergeResults(results: MeasuredResults[]): MeasuredResults {
|
|
305
|
-
if (results.length === 0) {
|
|
306
|
-
throw new Error("Cannot merge empty results array");
|
|
307
|
-
}
|
|
308
|
-
if (results.length === 1) return results[0];
|
|
309
|
-
|
|
310
|
-
const allSamples = results.flatMap(r => r.samples);
|
|
311
|
-
const allWarmup = results.flatMap(r => r.warmupSamples || []);
|
|
312
|
-
const time = computeStats(allSamples);
|
|
313
|
-
|
|
314
|
-
let offset = 0;
|
|
315
|
-
const allPausePoints = results.flatMap(r => {
|
|
316
|
-
const pts = (r.pausePoints ?? []).map(p => ({
|
|
317
|
-
sampleIndex: p.sampleIndex + offset,
|
|
318
|
-
durationMs: p.durationMs,
|
|
319
|
-
}));
|
|
320
|
-
offset += r.samples.length;
|
|
321
|
-
return pts;
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
name: results[0].name,
|
|
326
|
-
samples: allSamples,
|
|
327
|
-
warmupSamples: allWarmup.length ? allWarmup : undefined,
|
|
328
|
-
time,
|
|
329
|
-
totalTime: results.reduce((sum, r) => sum + (r.totalTime || 0), 0),
|
|
330
|
-
pausePoints: allPausePoints.length ? allPausePoints : undefined,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function appendToMap(
|
|
335
|
-
map: Map<string, MeasuredResults[]>,
|
|
336
|
-
key: string,
|
|
337
|
-
value: MeasuredResults,
|
|
338
|
-
) {
|
|
339
|
-
if (!map.has(key)) map.set(key, []);
|
|
340
|
-
map.get(key)!.push(value);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Generate table with standard sections */
|
|
344
|
-
export function defaultReport(
|
|
345
|
-
groups: ReportGroup[],
|
|
346
|
-
args: DefaultCliArgs,
|
|
347
|
-
): string {
|
|
348
|
-
const { adaptive, "gc-stats": gcStats, "trace-opt": traceOpt } = args;
|
|
349
|
-
const hasCpu = hasField(groups, "cpu");
|
|
350
|
-
const hasOpt = hasField(groups, "optStatus");
|
|
351
|
-
const sections = buildReportSections(
|
|
352
|
-
adaptive,
|
|
353
|
-
gcStats,
|
|
354
|
-
hasCpu,
|
|
355
|
-
traceOpt && hasOpt,
|
|
62
|
+
const args = parseBenchArgs(configureArgs, argv);
|
|
63
|
+
if (args.url) return browserBenchExports(args);
|
|
64
|
+
if (args.list && suite) return listSuite(suite);
|
|
65
|
+
if (suite) return benchExports(suite, args, opts);
|
|
66
|
+
if (args.file) return fileBenchExports(args.file, args);
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Provide a benchmark file, --url for browser mode, or pass a BenchSuite directly.",
|
|
356
69
|
);
|
|
357
|
-
return reportResults(groups, sections);
|
|
358
70
|
}
|
|
359
71
|
|
|
360
|
-
/**
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
)
|
|
367
|
-
const sections = adaptive
|
|
368
|
-
? [adaptiveSection, totalTimeSection]
|
|
369
|
-
: [timeSection];
|
|
370
|
-
|
|
371
|
-
if (gcStats) sections.push(gcStatsSection);
|
|
372
|
-
if (hasCpuData) sections.push(cpuSection);
|
|
373
|
-
if (hasOptData) sections.push(optSection);
|
|
374
|
-
sections.push(runsSection);
|
|
375
|
-
|
|
376
|
-
return sections;
|
|
72
|
+
/** Parse CLI args with optional custom yargs configuration. */
|
|
73
|
+
export function parseBenchArgs<T = DefaultCliArgs>(
|
|
74
|
+
configureArgs?: Configure<T>,
|
|
75
|
+
argv?: string[],
|
|
76
|
+
): T & DefaultCliArgs {
|
|
77
|
+
const args = argv ?? hideBin(process.argv);
|
|
78
|
+
return parseCliArgs(args, configureArgs) as T & DefaultCliArgs;
|
|
377
79
|
}
|
|
378
80
|
|
|
379
|
-
/** Run
|
|
81
|
+
/** Run a BenchSuite and print results with standard reporting. */
|
|
380
82
|
export async function benchExports(
|
|
381
83
|
suite: BenchSuite,
|
|
382
84
|
args: DefaultCliArgs,
|
|
85
|
+
opts?: BenchExportsOptions,
|
|
383
86
|
): Promise<void> {
|
|
384
87
|
const results = await runBenchmarks(suite, args);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
/** Run browser profiling via Playwright + CDP, report with standard pipeline */
|
|
391
|
-
export async function browserBenchExports(args: DefaultCliArgs): Promise<void> {
|
|
392
|
-
warnBrowserFlags(args);
|
|
393
|
-
|
|
394
|
-
let profileBrowser: typeof import("../browser/BrowserHeapSampler.ts").profileBrowser;
|
|
395
|
-
try {
|
|
396
|
-
({ profileBrowser } = await import("../browser/BrowserHeapSampler.ts"));
|
|
397
|
-
} catch {
|
|
398
|
-
throw new Error(
|
|
399
|
-
"playwright is required for browser benchmarking (--url).\n\n" +
|
|
400
|
-
"Quick start: npx benchforge-browser --url <your-url>\n\n" +
|
|
401
|
-
"Or install manually:\n" +
|
|
402
|
-
" npm install playwright\n" +
|
|
403
|
-
" npx playwright install chromium",
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const url = args.url!;
|
|
408
|
-
const { iterations, time } = args;
|
|
409
|
-
const result = await profileBrowser({
|
|
410
|
-
url,
|
|
411
|
-
heapSample: args["heap-sample"],
|
|
412
|
-
heapOptions: {
|
|
413
|
-
samplingInterval: args["heap-interval"],
|
|
414
|
-
stackDepth: args["heap-depth"],
|
|
415
|
-
},
|
|
416
|
-
headless: args.headless,
|
|
417
|
-
chromeArgs: args["chrome-args"]
|
|
418
|
-
?.flatMap(a => a.split(/\s+/))
|
|
419
|
-
.map(stripQuotes)
|
|
420
|
-
.filter(Boolean),
|
|
421
|
-
timeout: args.timeout,
|
|
422
|
-
gcStats: args["gc-stats"],
|
|
423
|
-
maxTime: iterations ? Number.MAX_SAFE_INTEGER : time * 1000,
|
|
424
|
-
maxIterations: iterations,
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
const name = new URL(url).pathname.split("/").pop() || "browser";
|
|
428
|
-
const results = browserResultGroups(name, result);
|
|
429
|
-
printBrowserReport(result, results, args);
|
|
430
|
-
await exportReports({ results, args });
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/** Print browser benchmark tables and heap reports */
|
|
434
|
-
function printBrowserReport(
|
|
435
|
-
result: BrowserProfileResult,
|
|
436
|
-
results: ReportGroup[],
|
|
437
|
-
args: DefaultCliArgs,
|
|
438
|
-
): void {
|
|
439
|
-
const hasSamples = result.samples && result.samples.length > 0;
|
|
440
|
-
const sections: ResultsMapper<any>[] = [];
|
|
441
|
-
if (hasSamples || result.wallTimeMs != null) {
|
|
442
|
-
sections.push(timeSection);
|
|
443
|
-
}
|
|
444
|
-
if (result.gcStats) {
|
|
445
|
-
sections.push(browserGcStatsSection);
|
|
446
|
-
}
|
|
447
|
-
if (hasSamples || result.wallTimeMs != null) {
|
|
448
|
-
sections.push(runsSection);
|
|
449
|
-
}
|
|
450
|
-
if (sections.length > 0) {
|
|
451
|
-
console.log(reportResults(results, sections));
|
|
452
|
-
}
|
|
453
|
-
if (result.heapProfile) {
|
|
454
|
-
printHeapReports(results, {
|
|
455
|
-
...cliHeapReportOptions(args),
|
|
456
|
-
isUserCode: isBrowserUserCode,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/** Wrap browser profile result as ReportGroup[] for the standard pipeline */
|
|
462
|
-
function browserResultGroups(
|
|
463
|
-
name: string,
|
|
464
|
-
result: BrowserProfileResult,
|
|
465
|
-
): ReportGroup[] {
|
|
466
|
-
const { gcStats, heapProfile } = result;
|
|
467
|
-
let measured: MeasuredResults;
|
|
468
|
-
|
|
469
|
-
// Bench function mode: multiple timing samples with real statistics
|
|
470
|
-
if (result.samples && result.samples.length > 0) {
|
|
471
|
-
const { samples } = result;
|
|
472
|
-
const totalTime = result.wallTimeMs ? result.wallTimeMs / 1000 : undefined;
|
|
473
|
-
measured = {
|
|
474
|
-
name,
|
|
475
|
-
samples,
|
|
476
|
-
time: computeStats(samples),
|
|
477
|
-
totalTime,
|
|
478
|
-
gcStats,
|
|
479
|
-
heapProfile,
|
|
480
|
-
};
|
|
481
|
-
} else {
|
|
482
|
-
// Lap mode: 0 laps = single wall-clock, N laps handled above
|
|
483
|
-
const wallMs = result.wallTimeMs ?? 0;
|
|
484
|
-
const time = {
|
|
485
|
-
min: wallMs,
|
|
486
|
-
max: wallMs,
|
|
487
|
-
avg: wallMs,
|
|
488
|
-
p50: wallMs,
|
|
489
|
-
p75: wallMs,
|
|
490
|
-
p99: wallMs,
|
|
491
|
-
p999: wallMs,
|
|
492
|
-
};
|
|
493
|
-
measured = { name, samples: [wallMs], time, gcStats, heapProfile };
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return [{ name, reports: [{ name, measuredResults: measured }] }];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/** Print heap allocation reports for benchmarks with heap profiles */
|
|
500
|
-
export function printHeapReports(
|
|
501
|
-
groups: ReportGroup[],
|
|
502
|
-
options: HeapReportOptions,
|
|
503
|
-
): void {
|
|
504
|
-
for (const group of groups) {
|
|
505
|
-
const allReports = group.baseline
|
|
506
|
-
? [...group.reports, group.baseline]
|
|
507
|
-
: group.reports;
|
|
508
|
-
|
|
509
|
-
for (const report of allReports) {
|
|
510
|
-
const { heapProfile } = report.measuredResults;
|
|
511
|
-
if (!heapProfile) continue;
|
|
512
|
-
|
|
513
|
-
console.log(dim(`\n─── Heap profile: ${report.name} ───`));
|
|
514
|
-
const totalAll = totalProfileBytes(heapProfile);
|
|
515
|
-
const sites = flattenProfile(heapProfile);
|
|
516
|
-
const userSites = filterSites(sites, options.isUserCode);
|
|
517
|
-
const totalUserCode = userSites.reduce((sum, s) => sum + s.bytes, 0);
|
|
518
|
-
const aggregated = aggregateSites(options.userOnly ? userSites : sites);
|
|
519
|
-
const extra = {
|
|
520
|
-
totalAll,
|
|
521
|
-
totalUserCode,
|
|
522
|
-
sampleCount: heapProfile.samples?.length,
|
|
523
|
-
};
|
|
524
|
-
console.log(formatHeapReport(aggregated, { ...options, ...extra }));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
88
|
+
console.log(
|
|
89
|
+
withStatus("computing report", () => defaultReport(results, args, opts)),
|
|
90
|
+
);
|
|
91
|
+
await finishReports(results, args, opts);
|
|
527
92
|
}
|
|
528
93
|
|
|
529
|
-
/** Run
|
|
530
|
-
export async function
|
|
531
|
-
suite
|
|
94
|
+
/** Run matrix suite with full CLI handling (parse, run, report, export). */
|
|
95
|
+
export async function runDefaultMatrixBench(
|
|
96
|
+
suite: MatrixSuite,
|
|
532
97
|
configureArgs?: Configure<any>,
|
|
98
|
+
reportOptions?: MatrixReportOptions,
|
|
533
99
|
): Promise<void> {
|
|
534
100
|
const args = parseBenchArgs(configureArgs);
|
|
535
|
-
|
|
536
|
-
await browserBenchExports(args);
|
|
537
|
-
} else if (suite) {
|
|
538
|
-
await benchExports(suite, args);
|
|
539
|
-
} else if (args.file) {
|
|
540
|
-
await fileBenchExports(args.file, args);
|
|
541
|
-
} else {
|
|
542
|
-
throw new Error(
|
|
543
|
-
"Provide a benchmark file, --url for browser mode, or pass a BenchSuite directly.",
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/** Import a file and run it as a benchmark based on what it exports */
|
|
549
|
-
async function fileBenchExports(
|
|
550
|
-
filePath: string,
|
|
551
|
-
args: DefaultCliArgs,
|
|
552
|
-
): Promise<void> {
|
|
553
|
-
const fileUrl = pathToFileURL(resolve(filePath)).href;
|
|
554
|
-
const mod = await import(fileUrl);
|
|
555
|
-
const candidate = mod.default;
|
|
556
|
-
|
|
557
|
-
if (candidate && Array.isArray(candidate.groups)) {
|
|
558
|
-
// BenchSuite export
|
|
559
|
-
await benchExports(candidate as BenchSuite, args);
|
|
560
|
-
} else if (typeof candidate === "function") {
|
|
561
|
-
// Default function export: wrap as a single benchmark
|
|
562
|
-
const name = basename(filePath).replace(/\.[^.]+$/, "");
|
|
563
|
-
await benchExports(
|
|
564
|
-
{ name, groups: [{ name, benchmarks: [{ name, fn: candidate }] }] },
|
|
565
|
-
args,
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
// else: self-executing file already ran on import
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/** Convert CLI args to runner options */
|
|
572
|
-
export function cliToRunnerOptions(args: DefaultCliArgs): RunnerOptions {
|
|
573
|
-
const { profile, collect, iterations } = args;
|
|
574
|
-
if (profile)
|
|
575
|
-
return { maxIterations: iterations ?? 1, warmupTime: 0, collect };
|
|
576
|
-
if (args.adaptive) return createAdaptiveOptions(args);
|
|
577
|
-
|
|
578
|
-
return {
|
|
579
|
-
maxTime: iterations ? Number.POSITIVE_INFINITY : args.time * 1000,
|
|
580
|
-
maxIterations: iterations,
|
|
581
|
-
...cliCommonOptions(args),
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/** Create options for adaptive mode */
|
|
586
|
-
function createAdaptiveOptions(args: DefaultCliArgs): RunnerOptions {
|
|
587
|
-
return {
|
|
588
|
-
minTime: (args["min-time"] ?? 1) * 1000,
|
|
589
|
-
maxTime: defaultAdaptiveMaxTime * 1000,
|
|
590
|
-
targetConfidence: args.convergence,
|
|
591
|
-
adaptive: true,
|
|
592
|
-
...cliCommonOptions(args),
|
|
593
|
-
} as any;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/** Runner/matrix options shared across all CLI modes */
|
|
597
|
-
function cliCommonOptions(args: DefaultCliArgs) {
|
|
598
|
-
const { collect, cpu, warmup } = args;
|
|
599
|
-
const { "trace-opt": traceOpt, "skip-settle": noSettle } = args;
|
|
600
|
-
const { "pause-first": pauseFirst, "pause-interval": pauseInterval } = args;
|
|
601
|
-
const { "pause-duration": pauseDuration, "gc-stats": gcStats } = args;
|
|
602
|
-
const { "heap-sample": heapSample, "heap-interval": heapInterval } = args;
|
|
603
|
-
const { "heap-depth": heapDepth } = args;
|
|
604
|
-
return {
|
|
605
|
-
collect,
|
|
606
|
-
cpuCounters: cpu,
|
|
607
|
-
warmup,
|
|
608
|
-
traceOpt,
|
|
609
|
-
noSettle,
|
|
610
|
-
pauseFirst,
|
|
611
|
-
pauseInterval,
|
|
612
|
-
pauseDuration,
|
|
613
|
-
gcStats,
|
|
614
|
-
heapSample,
|
|
615
|
-
heapInterval,
|
|
616
|
-
heapDepth,
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
621
|
-
const { yellow, dim } = isTest
|
|
622
|
-
? { yellow: (s: string) => s, dim: (s: string) => s }
|
|
623
|
-
: pico;
|
|
624
|
-
|
|
625
|
-
/** Log V8 optimization tier distribution and deoptimizations */
|
|
626
|
-
export function reportOptStatus(groups: ReportGroup[]): void {
|
|
627
|
-
const optData = groups.flatMap(({ reports, baseline }) => {
|
|
628
|
-
const all = baseline ? [...reports, baseline] : reports;
|
|
629
|
-
return all
|
|
630
|
-
.filter(r => r.measuredResults.optStatus)
|
|
631
|
-
.map(r => ({
|
|
632
|
-
name: r.name,
|
|
633
|
-
opt: r.measuredResults.optStatus!,
|
|
634
|
-
samples: r.measuredResults.samples.length,
|
|
635
|
-
}));
|
|
636
|
-
});
|
|
637
|
-
if (optData.length === 0) return;
|
|
638
|
-
|
|
639
|
-
console.log(dim("\nV8 optimization:"));
|
|
640
|
-
for (const { name, opt, samples } of optData) {
|
|
641
|
-
const total = Object.values(opt.byTier).reduce((s, t) => s + t.count, 0);
|
|
642
|
-
const tierParts = Object.entries(opt.byTier)
|
|
643
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
644
|
-
.map(
|
|
645
|
-
([tier, info]) => `${tier} ${((info.count / total) * 100).toFixed(0)}%`,
|
|
646
|
-
)
|
|
647
|
-
.join(", ");
|
|
648
|
-
console.log(` ${name}: ${tierParts} ${dim(`(${samples} samples)`)}`);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const totalDeopts = optData.reduce((s, d) => s + d.opt.deoptCount, 0);
|
|
652
|
-
if (totalDeopts > 0) {
|
|
653
|
-
console.log(
|
|
654
|
-
yellow(
|
|
655
|
-
` ⚠ ${totalDeopts} deoptimization${totalDeopts > 1 ? "s" : ""} detected`,
|
|
656
|
-
),
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/** @return true if any result has the specified field with a defined value */
|
|
662
|
-
export function hasField(
|
|
663
|
-
results: ReportGroup[],
|
|
664
|
-
field: keyof MeasuredResults,
|
|
665
|
-
): boolean {
|
|
666
|
-
return results.some(({ reports, baseline }) => {
|
|
667
|
-
const all = baseline ? [...reports, baseline] : reports;
|
|
668
|
-
return all.some(
|
|
669
|
-
({ measuredResults }) => measuredResults[field] !== undefined,
|
|
670
|
-
);
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
export interface ExportOptions {
|
|
675
|
-
results: ReportGroup[];
|
|
676
|
-
args: DefaultCliArgs;
|
|
677
|
-
sections?: any[];
|
|
678
|
-
suiteName?: string;
|
|
679
|
-
currentVersion?: GitVersion;
|
|
680
|
-
baselineVersion?: GitVersion;
|
|
101
|
+
await matrixBenchExports(suite, args, reportOptions);
|
|
681
102
|
}
|
|
682
103
|
|
|
683
|
-
/**
|
|
684
|
-
async function
|
|
685
|
-
|
|
104
|
+
/** Run a matrix suite, print results, and handle exports. */
|
|
105
|
+
export async function matrixBenchExports(
|
|
106
|
+
suite: MatrixSuite,
|
|
686
107
|
args: DefaultCliArgs,
|
|
687
|
-
|
|
108
|
+
reportOptions?: MatrixReportOptions,
|
|
688
109
|
exportOptions?: MatrixExportOptions,
|
|
689
110
|
): Promise<void> {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
/** Export reports (HTML, JSON, Perfetto) based on CLI args */
|
|
697
|
-
export async function exportReports(options: ExportOptions): Promise<void> {
|
|
698
|
-
const { results, args, sections, suiteName } = options;
|
|
699
|
-
const { currentVersion, baselineVersion } = options;
|
|
700
|
-
const openInBrowser = args.html && !args["export-html"];
|
|
701
|
-
let closeServer: (() => void) | undefined;
|
|
702
|
-
|
|
703
|
-
if (args.html || args["export-html"]) {
|
|
704
|
-
const htmlOpts = {
|
|
705
|
-
cliArgs: args,
|
|
706
|
-
sections,
|
|
707
|
-
currentVersion,
|
|
708
|
-
baselineVersion,
|
|
709
|
-
};
|
|
710
|
-
const reportData = prepareHtmlData(results, htmlOpts);
|
|
711
|
-
const result = await generateHtmlReport(reportData, {
|
|
712
|
-
openBrowser: openInBrowser,
|
|
713
|
-
outputPath: args["export-html"],
|
|
714
|
-
});
|
|
715
|
-
closeServer = result.closeServer;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (args.json) {
|
|
719
|
-
await exportBenchmarkJson(results, args.json, args, suiteName);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (args.perfetto) {
|
|
723
|
-
exportPerfettoTrace(results, args.perfetto, args);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Keep process running when HTML report is opened in browser
|
|
727
|
-
if (openInBrowser) {
|
|
728
|
-
await waitForCtrlC();
|
|
729
|
-
closeServer?.();
|
|
730
|
-
}
|
|
731
|
-
}
|
|
111
|
+
const results = await runMatrixSuite(suite, args);
|
|
112
|
+
const report = withStatus("computing report", () =>
|
|
113
|
+
defaultMatrixReport(results, reportOptions, args),
|
|
114
|
+
);
|
|
115
|
+
console.log(report);
|
|
732
116
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
return new Promise(resolve => {
|
|
736
|
-
console.log(dim("\nPress Ctrl+C to exit"));
|
|
737
|
-
process.on("SIGINT", () => {
|
|
738
|
-
console.log();
|
|
739
|
-
resolve();
|
|
740
|
-
});
|
|
741
|
-
});
|
|
117
|
+
const groups = matrixToReportGroups(results);
|
|
118
|
+
await finishReports(groups, args, exportOptions);
|
|
742
119
|
}
|
|
743
120
|
|
|
744
|
-
/** Run matrix suite with CLI arguments.
|
|
745
|
-
* no options ==> defaultCases/defaultVariants, --filter ==> subset of defaults,
|
|
746
|
-
* --all --filter ==> subset of all, --all ==> all cases/variants */
|
|
121
|
+
/** Run matrix suite with CLI arguments. --filter narrows defaults, --all --filter narrows all. */
|
|
747
122
|
export async function runMatrixSuite(
|
|
748
123
|
suite: MatrixSuite,
|
|
749
124
|
args: DefaultCliArgs,
|
|
750
125
|
): Promise<MatrixResults[]> {
|
|
126
|
+
if (args.list) {
|
|
127
|
+
await listMatrixSuite(suite);
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
751
130
|
validateArgs(args);
|
|
752
131
|
const filter = args.filter ? parseMatrixFilter(args.filter) : undefined;
|
|
753
132
|
const options = cliToMatrixOptions(args);
|
|
754
133
|
|
|
755
134
|
const results: MatrixResults[] = [];
|
|
756
135
|
for (const matrix of suite.matrices) {
|
|
757
|
-
const
|
|
758
|
-
? await loadCasesModule(matrix.casesModule)
|
|
759
|
-
: undefined;
|
|
760
|
-
|
|
761
|
-
let filtered: FilteredMatrix<any> = matrix;
|
|
762
|
-
if (!args.all && casesModule) {
|
|
763
|
-
filtered = {
|
|
764
|
-
...matrix,
|
|
765
|
-
filteredCases: casesModule.defaultCases,
|
|
766
|
-
filteredVariants: casesModule.defaultVariants,
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// filter merges via intersection with defaults
|
|
771
|
-
if (filter) {
|
|
772
|
-
filtered = await filterMatrix(filtered, filter);
|
|
773
|
-
}
|
|
774
|
-
|
|
136
|
+
const filtered = await applyMatrixFilters(matrix, args.all, filter);
|
|
775
137
|
const { filteredCases, filteredVariants } = filtered;
|
|
776
138
|
results.push(
|
|
777
139
|
await runMatrix(filtered, {
|
|
@@ -784,143 +146,75 @@ export async function runMatrixSuite(
|
|
|
784
146
|
return results;
|
|
785
147
|
}
|
|
786
148
|
|
|
787
|
-
/**
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
maxTime: iterations ? undefined : time * 1000,
|
|
793
|
-
useWorker: worker,
|
|
794
|
-
...cliCommonOptions(args),
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/** Generate report for matrix results. Uses same sections as regular benchmarks. */
|
|
799
|
-
export function defaultMatrixReport(
|
|
800
|
-
results: MatrixResults[],
|
|
801
|
-
reportOptions?: MatrixReportOptions,
|
|
802
|
-
args?: DefaultCliArgs,
|
|
803
|
-
): string {
|
|
804
|
-
const options = args
|
|
805
|
-
? mergeMatrixDefaults(reportOptions, args, results)
|
|
806
|
-
: reportOptions;
|
|
807
|
-
return results.map(r => reportMatrixResults(r, options)).join("\n\n");
|
|
149
|
+
/** Require a file argument for a subcommand, exiting with usage on missing. */
|
|
150
|
+
function requireFile(filePath: string | undefined, subcommand: string): string {
|
|
151
|
+
if (filePath) return filePath;
|
|
152
|
+
console.error(`Usage: benchforge ${subcommand} <file.benchforge>`);
|
|
153
|
+
process.exit(1);
|
|
808
154
|
}
|
|
809
155
|
|
|
810
|
-
/**
|
|
811
|
-
function
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
userOnly: args["heap-user-only"],
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/** Apply default sections and extra columns for matrix reports */
|
|
821
|
-
function mergeMatrixDefaults(
|
|
822
|
-
reportOptions: MatrixReportOptions | undefined,
|
|
823
|
-
args: DefaultCliArgs,
|
|
824
|
-
results: MatrixResults[],
|
|
825
|
-
): MatrixReportOptions {
|
|
826
|
-
const result: MatrixReportOptions = { ...reportOptions };
|
|
827
|
-
|
|
828
|
-
if (!result.sections?.length) {
|
|
829
|
-
const groups = matrixToReportGroups(results);
|
|
830
|
-
result.sections = buildReportSections(
|
|
831
|
-
args.adaptive,
|
|
832
|
-
args["gc-stats"],
|
|
833
|
-
hasField(groups, "cpu"),
|
|
834
|
-
args["trace-opt"] && hasField(groups, "optStatus"),
|
|
835
|
-
);
|
|
156
|
+
/** Print available benchmarks in a suite for --list. */
|
|
157
|
+
function listSuite(suite: BenchSuite): void {
|
|
158
|
+
for (const group of suite.groups) {
|
|
159
|
+
console.log(group.name);
|
|
160
|
+
for (const bench of group.benchmarks) console.log(` ${bench.name}`);
|
|
161
|
+
if (group.baseline) console.log(` ${group.baseline.name} (baseline)`);
|
|
836
162
|
}
|
|
837
|
-
|
|
838
|
-
return result;
|
|
839
163
|
}
|
|
840
164
|
|
|
841
|
-
/**
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
reportOptions?: MatrixReportOptions,
|
|
165
|
+
/** Import a file and run it as a benchmark based on what it exports. */
|
|
166
|
+
async function fileBenchExports(
|
|
167
|
+
filePath: string,
|
|
168
|
+
args: DefaultCliArgs,
|
|
846
169
|
): Promise<void> {
|
|
847
|
-
const
|
|
848
|
-
await
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/** Convert MatrixResults to ReportGroup[] for export compatibility */
|
|
852
|
-
export function matrixToReportGroups(results: MatrixResults[]): ReportGroup[] {
|
|
853
|
-
return results.flatMap(matrix =>
|
|
854
|
-
matrix.variants.flatMap(variant =>
|
|
855
|
-
variant.cases.map(c => {
|
|
856
|
-
const { metadata } = c;
|
|
857
|
-
const report = {
|
|
858
|
-
name: variant.id,
|
|
859
|
-
measuredResults: c.measured,
|
|
860
|
-
metadata,
|
|
861
|
-
};
|
|
862
|
-
const baseline = c.baseline
|
|
863
|
-
? {
|
|
864
|
-
name: `${variant.id} (baseline)`,
|
|
865
|
-
measuredResults: c.baseline,
|
|
866
|
-
metadata,
|
|
867
|
-
}
|
|
868
|
-
: undefined;
|
|
869
|
-
return {
|
|
870
|
-
name: `${variant.id} / ${c.caseId}`,
|
|
871
|
-
reports: [report],
|
|
872
|
-
baseline,
|
|
873
|
-
};
|
|
874
|
-
}),
|
|
875
|
-
),
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
export interface MatrixExportOptions {
|
|
880
|
-
sections?: any[];
|
|
881
|
-
currentVersion?: GitVersion;
|
|
882
|
-
baselineVersion?: GitVersion;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
/** Strip surrounding quotes from a chrome arg token.
|
|
886
|
-
*
|
|
887
|
-
* (Needed because --chrome-args values pass through yargs and spawn() without
|
|
888
|
-
* shell processing, so literal quote characters reach Chrome/V8 unrecognized.)
|
|
889
|
-
*/
|
|
890
|
-
function stripQuotes(s: string): string {
|
|
891
|
-
/* (['"]): opening quote; (.*): content; \1: require same closing quote */
|
|
892
|
-
const unquote = s.replace(/^(['"])(.*)\1$/s, "$2");
|
|
893
|
-
|
|
894
|
-
/* value portion: --flag="--value" or --flag='--value'
|
|
895
|
-
(-[^=]+=): flag name and =; (['"])(.*)\2: quoted value */
|
|
896
|
-
const valueUnquote = unquote.replace(/^(-[^=]+=)(['"])(.*)\2$/s, "$1$3");
|
|
170
|
+
const fileUrl = pathToFileURL(resolve(filePath)).href;
|
|
171
|
+
const { default: candidate } = await import(fileUrl);
|
|
897
172
|
|
|
898
|
-
|
|
173
|
+
if (candidate && Array.isArray(candidate.matrices)) {
|
|
174
|
+
if (args.list) return listMatrixSuite(candidate as MatrixSuite);
|
|
175
|
+
return matrixBenchExports(candidate as MatrixSuite, args);
|
|
176
|
+
}
|
|
177
|
+
if (candidate && Array.isArray(candidate.groups)) {
|
|
178
|
+
if (args.list) return listSuite(candidate as BenchSuite);
|
|
179
|
+
return benchExports(candidate as BenchSuite, args);
|
|
180
|
+
}
|
|
181
|
+
if (typeof candidate === "function") {
|
|
182
|
+
const name = basename(filePath).replace(/\.[^.]+$/, "");
|
|
183
|
+
const bench = { name, fn: candidate };
|
|
184
|
+
const suite = { name, groups: [{ name, benchmarks: [bench] }] };
|
|
185
|
+
return benchExports(suite, args);
|
|
186
|
+
}
|
|
899
187
|
}
|
|
900
188
|
|
|
901
|
-
/**
|
|
902
|
-
async function
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
189
|
+
/** Print available cases and variants in a matrix suite for --list. */
|
|
190
|
+
async function listMatrixSuite(suite: MatrixSuite): Promise<void> {
|
|
191
|
+
for (const matrix of suite.matrices) {
|
|
192
|
+
console.log(matrix.name);
|
|
193
|
+
const caseIds = await resolveCaseIds(matrix);
|
|
194
|
+
if (caseIds) {
|
|
195
|
+
console.log(" cases:");
|
|
196
|
+
for (const id of caseIds) console.log(` ${id}`);
|
|
197
|
+
}
|
|
198
|
+
const variantIds = await resolveVariantIds(matrix);
|
|
199
|
+
console.log(" variants:");
|
|
200
|
+
for (const id of variantIds) console.log(` ${id}`);
|
|
909
201
|
}
|
|
910
|
-
return results;
|
|
911
202
|
}
|
|
912
203
|
|
|
913
|
-
/**
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
204
|
+
/** --filter bypasses defaults (implies --all for the filtered dimension). */
|
|
205
|
+
async function applyMatrixFilters(
|
|
206
|
+
matrix: FilteredMatrix<any>,
|
|
207
|
+
runAll: boolean,
|
|
208
|
+
filter?: MatrixFilter,
|
|
209
|
+
): Promise<FilteredMatrix<any>> {
|
|
210
|
+
const mod = matrix.casesModule
|
|
211
|
+
? await loadCasesModule(matrix.casesModule)
|
|
212
|
+
: undefined;
|
|
213
|
+
let withDefaults = matrix;
|
|
214
|
+
if (!runAll && !filter && mod) {
|
|
215
|
+
const { defaultCases: filteredCases, defaultVariants: filteredVariants } =
|
|
216
|
+
mod;
|
|
217
|
+
withDefaults = { ...matrix, filteredCases, filteredVariants };
|
|
218
|
+
}
|
|
219
|
+
return filter ? filterMatrix(withDefaults, filter) : withDefaults;
|
|
926
220
|
}
|