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