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
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
coefficientOfVariation,
|
|
5
|
-
medianAbsoluteDeviation,
|
|
6
|
-
percentile,
|
|
7
|
-
} from "../StatisticalUtils.ts";
|
|
1
|
+
import { median } from "../stats/StatisticalUtils.ts";
|
|
2
|
+
import type { BenchmarkSpec } from "./BenchmarkSpec.ts";
|
|
8
3
|
import type { BenchRunner, RunnerOptions } from "./BenchRunner.ts";
|
|
4
|
+
import type { MeasuredResults } from "./MeasuredResults.ts";
|
|
9
5
|
import { msToNs } from "./RunnerUtils.ts";
|
|
6
|
+
import { computeStats, outlierImpactRatio } from "./SampleStats.ts";
|
|
10
7
|
|
|
8
|
+
/** Options for adaptive sampling: collects until statistical convergence or timeout. */
|
|
11
9
|
export interface AdaptiveOptions extends RunnerOptions {
|
|
10
|
+
/** Enable adaptive sampling (default: true when using adaptive runner) */
|
|
12
11
|
adaptive?: boolean;
|
|
12
|
+
/** Minimum measurement time in ms before convergence can stop sampling (default: 1000) */
|
|
13
13
|
minTime?: number;
|
|
14
|
+
/** Maximum measurement time in ms, hard stop (default: 10000) */
|
|
14
15
|
maxTime?: number;
|
|
16
|
+
/** Target confidence percentage to stop early (default: 95) */
|
|
15
17
|
targetConfidence?: number;
|
|
16
|
-
|
|
18
|
+
/** Confidence threshold 0-100 (alias for targetConfidence) */
|
|
19
|
+
convergence?: number;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
type Metrics = {
|
|
@@ -39,101 +42,85 @@ const initialBatch = 100;
|
|
|
39
42
|
const continueBatch = 100;
|
|
40
43
|
const continueIterations = 10;
|
|
41
44
|
|
|
42
|
-
/**
|
|
45
|
+
/** Wrap a runner with adaptive sampling (convergence detection or timeout). */
|
|
43
46
|
export function createAdaptiveWrapper(
|
|
44
47
|
baseRunner: BenchRunner,
|
|
45
48
|
options: AdaptiveOptions,
|
|
46
49
|
): BenchRunner {
|
|
47
50
|
return {
|
|
48
51
|
async runBench<T = unknown>(
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
bench: BenchmarkSpec<T>,
|
|
53
|
+
opts: RunnerOptions,
|
|
51
54
|
params?: T,
|
|
52
55
|
): Promise<MeasuredResults[]> {
|
|
53
|
-
return runAdaptiveBench(
|
|
54
|
-
baseRunner,
|
|
55
|
-
benchmark,
|
|
56
|
-
runnerOptions,
|
|
57
|
-
options,
|
|
58
|
-
params,
|
|
59
|
-
);
|
|
56
|
+
return runAdaptiveBench(baseRunner, bench, opts, options, params);
|
|
60
57
|
},
|
|
61
58
|
};
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
/**
|
|
61
|
+
/** Check convergence by comparing sliding windows of samples for stability. */
|
|
65
62
|
export function checkConvergence(samples: number[]): ConvergenceResult {
|
|
66
63
|
const windowSize = getWindowSize(samples);
|
|
67
64
|
const minSamples = windowSize * 2;
|
|
68
|
-
|
|
69
65
|
if (samples.length < minSamples) {
|
|
70
|
-
|
|
66
|
+
const confidence = (samples.length / minSamples) * 100;
|
|
67
|
+
const reason = `Collecting samples: ${samples.length}/${minSamples}`;
|
|
68
|
+
return { converged: false, confidence, reason };
|
|
71
69
|
}
|
|
72
|
-
|
|
73
|
-
const metrics = getStability(samples, windowSize);
|
|
74
|
-
return buildConvergence(metrics);
|
|
70
|
+
return buildConvergence(getStability(samples, windowSize));
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
/**
|
|
73
|
+
/** Run benchmark with adaptive sampling until convergence or timeout. */
|
|
78
74
|
async function runAdaptiveBench<T>(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
runner: BenchRunner,
|
|
76
|
+
bench: BenchmarkSpec<T>,
|
|
77
|
+
opts: RunnerOptions,
|
|
78
|
+
adaptive: AdaptiveOptions,
|
|
83
79
|
params?: T,
|
|
84
80
|
): Promise<MeasuredResults[]> {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
const overrides = opts as AdaptiveOptions;
|
|
82
|
+
const min = overrides.minTime ?? adaptive.minTime ?? minTime;
|
|
83
|
+
const max = overrides.maxTime ?? adaptive.maxTime ?? maxTime;
|
|
84
|
+
const target =
|
|
85
|
+
overrides.convergence ?? adaptive.convergence ?? targetConfidence;
|
|
90
86
|
const allSamples: number[] = [];
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
runnerOptions,
|
|
88
|
+
const { warmup, startTime: hrtimeStart } = await collectInitial(
|
|
89
|
+
runner,
|
|
90
|
+
bench,
|
|
91
|
+
opts,
|
|
97
92
|
params,
|
|
98
93
|
allSamples,
|
|
99
94
|
);
|
|
100
|
-
|
|
101
|
-
// Start timing AFTER warmup - warmup time doesn't count against maxTime
|
|
95
|
+
// Start timing after warmup so warmup time doesn't count against maxTime
|
|
102
96
|
const startTime = performance.now();
|
|
103
|
-
|
|
104
97
|
const limits = {
|
|
105
98
|
minTime: min,
|
|
106
99
|
maxTime: max,
|
|
107
100
|
targetConfidence: target,
|
|
108
101
|
startTime,
|
|
109
102
|
};
|
|
110
|
-
await collectAdaptive(
|
|
111
|
-
baseRunner,
|
|
112
|
-
benchmark,
|
|
113
|
-
runnerOptions,
|
|
114
|
-
params,
|
|
115
|
-
allSamples,
|
|
116
|
-
limits,
|
|
117
|
-
);
|
|
103
|
+
await collectAdaptive(runner, bench, opts, params, allSamples, limits);
|
|
118
104
|
|
|
119
|
-
const
|
|
105
|
+
const samplesNs = allSamples.map(s => s * msToNs);
|
|
106
|
+
const convergence = checkConvergence(samplesNs);
|
|
120
107
|
return buildResults(
|
|
121
108
|
allSamples,
|
|
122
109
|
startTime,
|
|
123
110
|
convergence,
|
|
124
|
-
|
|
111
|
+
bench.name,
|
|
125
112
|
warmup,
|
|
113
|
+
hrtimeStart,
|
|
126
114
|
);
|
|
127
115
|
}
|
|
128
116
|
|
|
129
|
-
/**
|
|
117
|
+
/** Scale window size inversely with execution time -- fast ops need more samples. */
|
|
130
118
|
function getWindowSize(samples: number[]): number {
|
|
131
|
-
if (samples.length < 20) return windowSize;
|
|
119
|
+
if (samples.length < 20) return windowSize;
|
|
132
120
|
|
|
133
121
|
const recentMs = samples.slice(-20).map(s => s / msToNs);
|
|
134
|
-
const recentMedian =
|
|
122
|
+
const recentMedian = median(recentMs);
|
|
135
123
|
|
|
136
|
-
// Inverse scaling with execution time
|
|
137
124
|
if (recentMedian < 0.01) return 200; // <10μs
|
|
138
125
|
if (recentMedian < 0.1) return 100; // <100μs
|
|
139
126
|
if (recentMedian < 1) return 50; // <1ms
|
|
@@ -141,91 +128,67 @@ function getWindowSize(samples: number[]): number {
|
|
|
141
128
|
return 20; // >10ms
|
|
142
129
|
}
|
|
143
130
|
|
|
144
|
-
/**
|
|
145
|
-
function buildProgressResult(
|
|
146
|
-
currentSamples: number,
|
|
147
|
-
minSamples: number,
|
|
148
|
-
): ConvergenceResult {
|
|
149
|
-
return {
|
|
150
|
-
converged: false,
|
|
151
|
-
confidence: (currentSamples / minSamples) * 100,
|
|
152
|
-
reason: `Collecting samples: ${currentSamples}/${minSamples}`,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** @return stability metrics between windows */
|
|
157
|
-
function getStability(samples: number[], windowSize: number): Metrics {
|
|
158
|
-
const recent = samples.slice(-windowSize);
|
|
159
|
-
const previous = samples.slice(-windowSize * 2, -windowSize);
|
|
160
|
-
|
|
161
|
-
const recentMs = recent.map(s => s / msToNs);
|
|
162
|
-
const previousMs = previous.map(s => s / msToNs);
|
|
163
|
-
|
|
164
|
-
const medianRecent = percentile(recentMs, 0.5);
|
|
165
|
-
const medianPrevious = percentile(previousMs, 0.5);
|
|
166
|
-
const medianDrift = Math.abs(medianRecent - medianPrevious) / medianPrevious;
|
|
167
|
-
|
|
168
|
-
const impactRecent = getOutlierImpact(recentMs);
|
|
169
|
-
const impactPrevious = getOutlierImpact(previousMs);
|
|
170
|
-
const impactDrift = Math.abs(impactRecent.ratio - impactPrevious.ratio);
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
medianDrift,
|
|
174
|
-
impactDrift,
|
|
175
|
-
medianStable: medianDrift < stability,
|
|
176
|
-
impactStable: impactDrift < stability,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** @return convergence from stability metrics */
|
|
131
|
+
/** Convert stability metrics to a convergence result with confidence score. */
|
|
181
132
|
function buildConvergence(metrics: Metrics): ConvergenceResult {
|
|
182
133
|
const { medianDrift, impactDrift, medianStable, impactStable } = metrics;
|
|
183
|
-
|
|
184
|
-
if (medianStable && impactStable) {
|
|
134
|
+
if (medianStable && impactStable)
|
|
185
135
|
return {
|
|
186
136
|
converged: true,
|
|
187
137
|
confidence: 100,
|
|
188
138
|
reason: "Stable performance pattern",
|
|
189
139
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const confidence = Math.min(
|
|
193
|
-
100,
|
|
194
|
-
(1 - medianDrift / stability) * 50 + (1 - impactDrift / stability) * 50,
|
|
195
|
-
);
|
|
196
|
-
|
|
140
|
+
const raw =
|
|
141
|
+
(1 - medianDrift / stability) * 50 + (1 - impactDrift / stability) * 50;
|
|
142
|
+
const confidence = Math.max(0, Math.min(100, raw));
|
|
197
143
|
const reason =
|
|
198
144
|
medianDrift > impactDrift
|
|
199
145
|
? `Median drifting: ${(medianDrift * 100).toFixed(1)}%`
|
|
200
146
|
: `Outlier impact changing: ${(impactDrift * 100).toFixed(1)}%`;
|
|
147
|
+
return { converged: false, confidence, reason };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Compare median and outlier-impact drift between recent and previous windows. */
|
|
151
|
+
function getStability(samples: number[], windowSize: number): Metrics {
|
|
152
|
+
const toMs = (s: number) => s / msToNs;
|
|
153
|
+
const recentMs = samples.slice(-windowSize).map(toMs);
|
|
154
|
+
const previousMs = samples.slice(-windowSize * 2, -windowSize).map(toMs);
|
|
201
155
|
|
|
202
|
-
|
|
156
|
+
const medianRecent = median(recentMs);
|
|
157
|
+
const medianPrevious = median(previousMs);
|
|
158
|
+
const medianDrift = Math.abs(medianRecent - medianPrevious) / medianPrevious;
|
|
159
|
+
|
|
160
|
+
const impactRecent = outlierImpactRatio(recentMs);
|
|
161
|
+
const impactPrevious = outlierImpactRatio(previousMs);
|
|
162
|
+
const impactDrift = Math.abs(impactRecent - impactPrevious);
|
|
163
|
+
|
|
164
|
+
const medianStable = medianDrift < stability;
|
|
165
|
+
const impactStable = impactDrift < stability;
|
|
166
|
+
return { medianDrift, impactDrift, medianStable, impactStable };
|
|
203
167
|
}
|
|
204
168
|
|
|
205
|
-
/**
|
|
169
|
+
/** Collect the initial batch (warmup + settle), returning warmup samples. */
|
|
206
170
|
async function collectInitial<T>(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
171
|
+
runner: BenchRunner,
|
|
172
|
+
bench: BenchmarkSpec<T>,
|
|
173
|
+
opts: RunnerOptions,
|
|
210
174
|
params: T | undefined,
|
|
211
175
|
allSamples: number[],
|
|
212
|
-
): Promise<number[]
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
...(runnerOptions as any),
|
|
176
|
+
): Promise<{ warmup?: number[]; startTime?: number }> {
|
|
177
|
+
const batchOpts = {
|
|
178
|
+
...(opts as any),
|
|
216
179
|
maxTime: initialBatch,
|
|
217
180
|
maxIterations: undefined,
|
|
218
181
|
};
|
|
219
|
-
const results = await
|
|
182
|
+
const results = await runner.runBench(bench, batchOpts, params);
|
|
220
183
|
appendSamples(results[0], allSamples);
|
|
221
|
-
return results[0].warmupSamples;
|
|
184
|
+
return { warmup: results[0].warmupSamples, startTime: results[0].startTime };
|
|
222
185
|
}
|
|
223
186
|
|
|
224
|
-
/**
|
|
187
|
+
/** Collect batches until convergence or timeout, with progress logging. */
|
|
225
188
|
async function collectAdaptive<T>(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
189
|
+
runner: BenchRunner,
|
|
190
|
+
bench: BenchmarkSpec<T>,
|
|
191
|
+
opts: RunnerOptions,
|
|
229
192
|
params: T | undefined,
|
|
230
193
|
allSamples: number[],
|
|
231
194
|
limits: {
|
|
@@ -242,150 +205,67 @@ async function collectAdaptive<T>(
|
|
|
242
205
|
const convergence = checkConvergence(samplesNs);
|
|
243
206
|
const elapsed = performance.now() - startTime;
|
|
244
207
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const conf = convergence.confidence.toFixed(0);
|
|
248
|
-
process.stderr.write(
|
|
249
|
-
`\r◊ ${benchmark.name}: ${conf}% confident (${elapsedSec}s) `,
|
|
250
|
-
);
|
|
251
|
-
lastLog = elapsed;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (shouldStop(convergence, targetConfidence, elapsed, minTime)) {
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
208
|
+
lastLog = logProgress(bench.name, convergence, elapsed, lastLog);
|
|
209
|
+
if (shouldStop(convergence, targetConfidence, elapsed, minTime)) break;
|
|
257
210
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
...(runnerOptions as any),
|
|
211
|
+
const batch = {
|
|
212
|
+
...(opts as any),
|
|
261
213
|
maxTime: continueBatch,
|
|
262
214
|
maxIterations: continueIterations,
|
|
263
215
|
skipWarmup: true,
|
|
264
216
|
};
|
|
265
|
-
const
|
|
266
|
-
appendSamples(
|
|
217
|
+
const results = await runner.runBench(bench, batch, params);
|
|
218
|
+
appendSamples(results[0], allSamples);
|
|
267
219
|
}
|
|
268
220
|
process.stderr.write("\r" + " ".repeat(60) + "\r");
|
|
269
221
|
}
|
|
270
222
|
|
|
271
|
-
/**
|
|
223
|
+
/** Build final MeasuredResults from collected samples and convergence state. */
|
|
272
224
|
function buildResults(
|
|
273
|
-
|
|
274
|
-
|
|
225
|
+
samples: number[],
|
|
226
|
+
elapsedStart: number,
|
|
275
227
|
convergence: ConvergenceResult,
|
|
276
228
|
name: string,
|
|
277
229
|
warmupSamples?: number[],
|
|
230
|
+
startTime?: number,
|
|
278
231
|
): MeasuredResults[] {
|
|
279
|
-
const totalTime = (performance.now() -
|
|
280
|
-
const
|
|
281
|
-
const timeStats = computeTimeStats(samplesNs);
|
|
282
|
-
|
|
232
|
+
const totalTime = (performance.now() - elapsedStart) / 1000;
|
|
233
|
+
const time = computeStats(samples);
|
|
283
234
|
return [
|
|
284
|
-
{
|
|
285
|
-
name,
|
|
286
|
-
samples: samplesMs,
|
|
287
|
-
warmupSamples,
|
|
288
|
-
time: timeStats,
|
|
289
|
-
totalTime,
|
|
290
|
-
convergence,
|
|
291
|
-
},
|
|
235
|
+
{ name, samples, warmupSamples, time, totalTime, startTime, convergence },
|
|
292
236
|
];
|
|
293
237
|
}
|
|
294
238
|
|
|
295
|
-
/**
|
|
296
|
-
function getOutlierImpact(samples: number[]): { ratio: number; count: number } {
|
|
297
|
-
if (samples.length === 0) return { ratio: 0, count: 0 };
|
|
298
|
-
|
|
299
|
-
const median = percentile(samples, 0.5);
|
|
300
|
-
const q75 = percentile(samples, 0.75);
|
|
301
|
-
const threshold = median + 1.5 * (q75 - median);
|
|
302
|
-
|
|
303
|
-
let excessTime = 0;
|
|
304
|
-
let count = 0;
|
|
305
|
-
|
|
306
|
-
for (const sample of samples) {
|
|
307
|
-
if (sample > threshold) {
|
|
308
|
-
excessTime += sample - median;
|
|
309
|
-
count++;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const totalTime = samples.reduce((a, b) => a + b, 0);
|
|
314
|
-
return {
|
|
315
|
-
ratio: totalTime > 0 ? excessTime / totalTime : 0,
|
|
316
|
-
count,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/** Append samples one-by-one to avoid stack overflow from spread on large arrays */
|
|
239
|
+
/** Append samples one-by-one to avoid stack overflow from spread on large arrays. */
|
|
321
240
|
function appendSamples(result: MeasuredResults, samples: number[]): void {
|
|
322
241
|
if (!result.samples?.length) return;
|
|
323
242
|
for (const sample of result.samples) samples.push(sample);
|
|
324
243
|
}
|
|
325
244
|
|
|
326
|
-
/**
|
|
327
|
-
function
|
|
245
|
+
/** Log adaptive sampling progress at ~1s intervals. */
|
|
246
|
+
function logProgress(
|
|
247
|
+
name: string,
|
|
328
248
|
convergence: ConvergenceResult,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const threshold = Math.max(targetConfidence, fallbackThreshold);
|
|
338
|
-
return elapsedTime >= minTime && convergence.confidence >= threshold;
|
|
249
|
+
elapsed: number,
|
|
250
|
+
lastLog: number,
|
|
251
|
+
): number {
|
|
252
|
+
if (elapsed - lastLog <= 1000) return lastLog;
|
|
253
|
+
const sec = (elapsed / 1000).toFixed(1);
|
|
254
|
+
const conf = convergence.confidence.toFixed(0);
|
|
255
|
+
process.stderr.write(`\r◊ ${name}: ${conf}% confident (${sec}s) `);
|
|
256
|
+
return elapsed;
|
|
339
257
|
}
|
|
340
258
|
|
|
341
|
-
/** @return
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
...percentiles,
|
|
353
|
-
...robust,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/** @return min, max, sum of samples */
|
|
358
|
-
function getMinMaxSum(samples: number[]) {
|
|
359
|
-
const min = samples.reduce(
|
|
360
|
-
(a, b) => Math.min(a, b),
|
|
361
|
-
Number.POSITIVE_INFINITY,
|
|
362
|
-
);
|
|
363
|
-
const max = samples.reduce(
|
|
364
|
-
(a, b) => Math.max(a, b),
|
|
365
|
-
Number.NEGATIVE_INFINITY,
|
|
259
|
+
/** @return true if convergence target met, or minTime elapsed with fallback confidence. */
|
|
260
|
+
function shouldStop(
|
|
261
|
+
convergence: ConvergenceResult,
|
|
262
|
+
target: number,
|
|
263
|
+
elapsed: number,
|
|
264
|
+
minElapsed: number,
|
|
265
|
+
): boolean {
|
|
266
|
+
if (convergence.converged && convergence.confidence >= target) return true;
|
|
267
|
+
return (
|
|
268
|
+
elapsed >= minElapsed &&
|
|
269
|
+
convergence.confidence >= Math.max(target, fallbackThreshold)
|
|
366
270
|
);
|
|
367
|
-
const sum = samples.reduce((a, b) => a + b, 0);
|
|
368
|
-
return { min, max, sum };
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/** @return percentiles in ms */
|
|
372
|
-
function getPercentiles(samples: number[]) {
|
|
373
|
-
return {
|
|
374
|
-
p25: percentile(samples, 0.25) / msToNs,
|
|
375
|
-
p50: percentile(samples, 0.5) / msToNs,
|
|
376
|
-
p75: percentile(samples, 0.75) / msToNs,
|
|
377
|
-
p95: percentile(samples, 0.95) / msToNs,
|
|
378
|
-
p99: percentile(samples, 0.99) / msToNs,
|
|
379
|
-
p999: percentile(samples, 0.999) / msToNs,
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/** @return robust variability metrics */
|
|
384
|
-
function getRobustMetrics(samplesMs: number[]) {
|
|
385
|
-
const impact = getOutlierImpact(samplesMs);
|
|
386
|
-
return {
|
|
387
|
-
cv: coefficientOfVariation(samplesMs),
|
|
388
|
-
mad: medianAbsoluteDeviation(samplesMs),
|
|
389
|
-
outlierRate: impact.ratio,
|
|
390
|
-
};
|
|
391
271
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { BenchmarkSpec } from "
|
|
2
|
-
import type { MeasuredResults } from "
|
|
1
|
+
import type { BenchmarkSpec } from "./BenchmarkSpec.ts";
|
|
2
|
+
import type { MeasuredResults } from "./MeasuredResults.ts";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** Benchmark execution strategy that collects timing samples. */
|
|
5
5
|
export interface BenchRunner {
|
|
6
6
|
runBench<T = unknown>(
|
|
7
7
|
benchmark: BenchmarkSpec<T>,
|
|
@@ -10,6 +10,7 @@ export interface BenchRunner {
|
|
|
10
10
|
): Promise<MeasuredResults[]>;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Configuration for benchmark execution: timing limits, warmup, profiling, and V8 options. */
|
|
13
14
|
export interface RunnerOptions {
|
|
14
15
|
/** Minimum time to run each benchmark (milliseconds) */
|
|
15
16
|
minTime?: number;
|
|
@@ -28,13 +29,11 @@ export interface RunnerOptions {
|
|
|
28
29
|
/** Minimum samples required - mitata only */
|
|
29
30
|
minSamples?: number;
|
|
30
31
|
/** Force GC after each iteration (requires --expose-gc) */
|
|
31
|
-
|
|
32
|
-
/** Enable CPU performance counters (requires root access) */
|
|
33
|
-
cpuCounters?: boolean;
|
|
32
|
+
gcForce?: boolean;
|
|
34
33
|
/** Trace V8 optimization tiers (requires --allow-natives-syntax) */
|
|
35
34
|
traceOpt?: boolean;
|
|
36
|
-
/**
|
|
37
|
-
|
|
35
|
+
/** Post-warmup settle time in ms for V8 background compilation (0 to skip) */
|
|
36
|
+
pauseWarmup?: number;
|
|
38
37
|
/** Iterations before first pause (then pauseInterval applies) */
|
|
39
38
|
pauseFirst?: number;
|
|
40
39
|
/** Iterations between pauses for V8 optimization (0 to disable) */
|
|
@@ -43,15 +42,21 @@ export interface RunnerOptions {
|
|
|
43
42
|
pauseDuration?: number;
|
|
44
43
|
/** Collect GC stats via --trace-gc-nvp (requires worker mode) */
|
|
45
44
|
gcStats?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
|
|
45
|
+
/** Allocation sampling attribution */
|
|
46
|
+
alloc?: boolean;
|
|
47
|
+
/** Allocation sampling interval in bytes */
|
|
48
|
+
allocInterval?: number;
|
|
49
|
+
/** Allocation sampling stack depth */
|
|
50
|
+
allocDepth?: number;
|
|
51
|
+
/** V8 CPU time sampling */
|
|
52
|
+
profile?: boolean;
|
|
53
|
+
/** CPU sampling interval in microseconds (default 1000) */
|
|
54
|
+
profileInterval?: number;
|
|
55
|
+
/** Collect per-function execution counts via V8 precise coverage */
|
|
56
|
+
callCounts?: boolean;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
/**
|
|
59
|
+
/** Invoke the benchmark function, forwarding setup params. */
|
|
55
60
|
export function executeBenchmark<T>(
|
|
56
61
|
benchmark: BenchmarkSpec<T>,
|
|
57
62
|
params?: T,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Benchmark function with optional module path for worker-mode serialization. */
|
|
2
2
|
export interface BenchmarkSpec<T = unknown> {
|
|
3
3
|
name: string;
|
|
4
4
|
fn: BenchmarkFunction<T>;
|
|
@@ -10,23 +10,22 @@ export interface BenchmarkSpec<T = unknown> {
|
|
|
10
10
|
setupExportName?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Benchmark function, optionally receiving setup parameters from the group. */
|
|
13
14
|
export type BenchmarkFunction<T = unknown> =
|
|
14
15
|
| ((params: T) => void)
|
|
15
16
|
| (() => void);
|
|
16
17
|
|
|
17
|
-
/** Group of benchmarks with shared setup */
|
|
18
|
+
/** Group of benchmarks with shared setup and optional baseline. */
|
|
18
19
|
export interface BenchGroup<T = unknown> {
|
|
19
20
|
name: string;
|
|
20
|
-
/** Prepare parameters for all benchmarks in this group */
|
|
21
21
|
setup?: () => T | Promise<T>;
|
|
22
22
|
benchmarks: BenchmarkSpec<T>[];
|
|
23
|
-
/** Baseline benchmark for comparison */
|
|
24
23
|
baseline?: BenchmarkSpec<T>;
|
|
25
|
-
/** Metadata for reporting (e.g
|
|
24
|
+
/** Metadata for reporting (e.g. lines of code). */
|
|
26
25
|
metadata?: Record<string, any>;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
/**
|
|
28
|
+
/** Named collection of benchmark groups. */
|
|
30
29
|
export interface BenchSuite {
|
|
31
30
|
name: string;
|
|
32
31
|
groups: BenchGroup<any>[];
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { BasicRunner } from "./BasicRunner.ts";
|
|
2
1
|
import type { BenchRunner } from "./BenchRunner.ts";
|
|
2
|
+
import { TimingRunner } from "./TimingRunner.ts";
|
|
3
3
|
|
|
4
|
-
export type KnownRunner = "
|
|
4
|
+
export type KnownRunner = "timing";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
export async function createRunner(
|
|
8
|
-
|
|
9
|
-
): Promise<BenchRunner> {
|
|
10
|
-
return new BasicRunner();
|
|
6
|
+
/** Create a benchmark runner by name. */
|
|
7
|
+
export async function createRunner(_name: KnownRunner): Promise<BenchRunner> {
|
|
8
|
+
return new TimingRunner();
|
|
11
9
|
}
|