benchforge 0.1.0
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/README.md +432 -0
- package/bin/benchforge +3 -0
- package/dist/bin/benchforge.mjs +9 -0
- package/dist/bin/benchforge.mjs.map +1 -0
- package/dist/browser/index.js +914 -0
- package/dist/index.mjs +3 -0
- package/dist/src-CGuaC3Wo.mjs +3676 -0
- package/dist/src-CGuaC3Wo.mjs.map +1 -0
- package/package.json +49 -0
- package/src/BenchMatrix.ts +380 -0
- package/src/Benchmark.ts +33 -0
- package/src/BenchmarkReport.ts +156 -0
- package/src/GitUtils.ts +79 -0
- package/src/HtmlDataPrep.ts +148 -0
- package/src/MeasuredResults.ts +127 -0
- package/src/NodeGC.ts +48 -0
- package/src/PermutationTest.ts +115 -0
- package/src/StandardSections.ts +268 -0
- package/src/StatisticalUtils.ts +176 -0
- package/src/TypeUtil.ts +8 -0
- package/src/bin/benchforge.ts +4 -0
- package/src/browser/BrowserGcStats.ts +44 -0
- package/src/browser/BrowserHeapSampler.ts +248 -0
- package/src/cli/CliArgs.ts +64 -0
- package/src/cli/FilterBenchmarks.ts +68 -0
- package/src/cli/RunBenchCLI.ts +856 -0
- package/src/export/JsonExport.ts +103 -0
- package/src/export/JsonFormat.ts +91 -0
- package/src/export/PerfettoExport.ts +203 -0
- package/src/heap-sample/HeapSampleReport.ts +196 -0
- package/src/heap-sample/HeapSampler.ts +78 -0
- package/src/html/HtmlReport.ts +131 -0
- package/src/html/HtmlTemplate.ts +284 -0
- package/src/html/Types.ts +88 -0
- package/src/html/browser/CIPlot.ts +287 -0
- package/src/html/browser/HistogramKde.ts +118 -0
- package/src/html/browser/LegendUtils.ts +163 -0
- package/src/html/browser/RenderPlots.ts +263 -0
- package/src/html/browser/SampleTimeSeries.ts +389 -0
- package/src/html/browser/Types.ts +96 -0
- package/src/html/browser/index.ts +1 -0
- package/src/html/index.ts +17 -0
- package/src/index.ts +92 -0
- package/src/matrix/CaseLoader.ts +36 -0
- package/src/matrix/MatrixFilter.ts +103 -0
- package/src/matrix/MatrixReport.ts +290 -0
- package/src/matrix/VariantLoader.ts +46 -0
- package/src/runners/AdaptiveWrapper.ts +391 -0
- package/src/runners/BasicRunner.ts +368 -0
- package/src/runners/BenchRunner.ts +60 -0
- package/src/runners/CreateRunner.ts +11 -0
- package/src/runners/GcStats.ts +107 -0
- package/src/runners/RunnerOrchestrator.ts +374 -0
- package/src/runners/RunnerUtils.ts +2 -0
- package/src/runners/TimingUtils.ts +13 -0
- package/src/runners/WorkerScript.ts +256 -0
- package/src/table-util/ConvergenceFormatters.ts +19 -0
- package/src/table-util/Formatters.ts +152 -0
- package/src/table-util/README.md +70 -0
- package/src/table-util/TableReport.ts +293 -0
- package/src/table-util/test/TableReport.test.ts +105 -0
- package/src/table-util/test/TableValueExtractor.test.ts +41 -0
- package/src/table-util/test/TableValueExtractor.ts +100 -0
- package/src/test/AdaptiveRunner.test.ts +185 -0
- package/src/test/AdaptiveStatistics.integration.ts +119 -0
- package/src/test/BenchmarkReport.test.ts +82 -0
- package/src/test/BrowserBench.e2e.test.ts +44 -0
- package/src/test/BrowserBench.test.ts +79 -0
- package/src/test/GcStats.test.ts +94 -0
- package/src/test/PermutationTest.test.ts +121 -0
- package/src/test/RunBenchCLI.test.ts +166 -0
- package/src/test/RunnerOrchestrator.test.ts +102 -0
- package/src/test/StatisticalUtils.test.ts +112 -0
- package/src/test/TestUtils.ts +93 -0
- package/src/test/fixtures/test-bench-script.ts +30 -0
- package/src/tests/AdaptiveConvergence.test.ts +177 -0
- package/src/tests/AdaptiveSampling.test.ts +240 -0
- package/src/tests/BenchMatrix.test.ts +366 -0
- package/src/tests/MatrixFilter.test.ts +117 -0
- package/src/tests/MatrixReport.test.ts +139 -0
- package/src/tests/RealDataValidation.test.ts +177 -0
- package/src/tests/fixtures/baseline/impl.ts +4 -0
- package/src/tests/fixtures/bevy30-samples.ts +158 -0
- package/src/tests/fixtures/cases/asyncCases.ts +7 -0
- package/src/tests/fixtures/cases/cases.ts +8 -0
- package/src/tests/fixtures/cases/variants/product.ts +2 -0
- package/src/tests/fixtures/cases/variants/sum.ts +2 -0
- package/src/tests/fixtures/discover/fast.ts +1 -0
- package/src/tests/fixtures/discover/slow.ts +4 -0
- package/src/tests/fixtures/invalid/bad.ts +1 -0
- package/src/tests/fixtures/loader/fast.ts +1 -0
- package/src/tests/fixtures/loader/slow.ts +4 -0
- package/src/tests/fixtures/loader/stateful.ts +2 -0
- package/src/tests/fixtures/stateful/stateful.ts +2 -0
- package/src/tests/fixtures/variants/extra.ts +1 -0
- package/src/tests/fixtures/variants/impl.ts +1 -0
- package/src/tests/fixtures/worker/fast.ts +1 -0
- package/src/tests/fixtures/worker/slow.ts +4 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { test } from "vitest";
|
|
2
|
+
import { checkConvergence } from "../runners/AdaptiveWrapper.ts";
|
|
3
|
+
import { bevy30SamplesNs } from "./fixtures/bevy30-samples.ts";
|
|
4
|
+
|
|
5
|
+
test("convergence with insufficient samples", () => {
|
|
6
|
+
const samples = [1e6, 2e6, 3e6]; // 3 samples in nanoseconds
|
|
7
|
+
const result = checkConvergence(samples);
|
|
8
|
+
|
|
9
|
+
if (result.converged) throw new Error("Should not converge with few samples");
|
|
10
|
+
if (result.confidence >= 10)
|
|
11
|
+
throw new Error("Confidence too high for 3 samples");
|
|
12
|
+
if (!result.reason.includes("Collecting samples")) {
|
|
13
|
+
throw new Error("Wrong reason for non-convergence");
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("convergence with stable samples", () => {
|
|
18
|
+
// Create very stable samples (all within 1% of each other)
|
|
19
|
+
const base = 50e6; // 50ms in nanoseconds
|
|
20
|
+
const samples = Array.from(
|
|
21
|
+
{ length: 200 },
|
|
22
|
+
() => base + (Math.random() - 0.5) * base * 0.01,
|
|
23
|
+
);
|
|
24
|
+
const result = checkConvergence(samples);
|
|
25
|
+
|
|
26
|
+
if (!result.converged) throw new Error("Should converge with stable samples");
|
|
27
|
+
if (result.confidence !== 100) throw new Error("Should have 100% confidence");
|
|
28
|
+
if (!result.reason.includes("Stable")) {
|
|
29
|
+
throw new Error("Wrong reason for convergence");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("convergence with drifting median", () => {
|
|
34
|
+
// Create samples with increasing median over time
|
|
35
|
+
const samples = Array.from(
|
|
36
|
+
{ length: 200 },
|
|
37
|
+
(_, i) => 50e6 + i * 0.5e6 + (Math.random() - 0.5) * 1e6,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const result = checkConvergence(samples);
|
|
41
|
+
|
|
42
|
+
if (result.converged)
|
|
43
|
+
throw new Error("Should not converge with drifting median");
|
|
44
|
+
if (result.confidence >= 80)
|
|
45
|
+
throw new Error("Confidence too high for drifting data");
|
|
46
|
+
if (!result.reason.includes("Median drifting")) {
|
|
47
|
+
throw new Error("Should identify median drift");
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("convergence with outliers", () => {
|
|
52
|
+
// Create stable samples with occasional outliers every 20 samples
|
|
53
|
+
const base = 50e6;
|
|
54
|
+
const samples = Array.from({ length: 200 }, (_, i) =>
|
|
55
|
+
i % 20 === 0 ? base * 2 : base + (Math.random() - 0.5) * base * 0.01,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const result = checkConvergence(samples);
|
|
59
|
+
|
|
60
|
+
// May or may not converge depending on outlier impact calculation
|
|
61
|
+
if (result.converged && result.confidence !== 100) {
|
|
62
|
+
throw new Error("Should have 100% confidence if converged");
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("convergence with real bevy30 data - early samples", () => {
|
|
67
|
+
// Test with first 100 samples (should show initial instability)
|
|
68
|
+
const early = bevy30SamplesNs.slice(0, 100);
|
|
69
|
+
const result = checkConvergence(early);
|
|
70
|
+
|
|
71
|
+
// Early samples include warm-up, may not be fully converged
|
|
72
|
+
if (result.confidence > 100 || result.confidence < 0) {
|
|
73
|
+
throw new Error(`Confidence out of range: ${result.confidence}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(
|
|
77
|
+
`Early samples (100): converged=${result.converged}, confidence=${result.confidence}%`,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("convergence with real bevy30 data - middle samples", () => {
|
|
82
|
+
// Test with middle 200 samples (should be more stable)
|
|
83
|
+
const middle = bevy30SamplesNs.slice(200, 400);
|
|
84
|
+
const result = checkConvergence(middle);
|
|
85
|
+
|
|
86
|
+
if (result.confidence > 100 || result.confidence < 0) {
|
|
87
|
+
throw new Error(`Confidence out of range: ${result.confidence}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(
|
|
91
|
+
`Middle samples (200): converged=${result.converged}, confidence=${result.confidence}%`,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("convergence with real bevy30 data - all samples", () => {
|
|
96
|
+
const result = checkConvergence(bevy30SamplesNs);
|
|
97
|
+
|
|
98
|
+
if (result.confidence > 100 || result.confidence < 0) {
|
|
99
|
+
throw new Error(`Confidence out of range: ${result.confidence}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// With 30 seconds of data, should have high confidence
|
|
103
|
+
if (result.confidence < 80) {
|
|
104
|
+
console.warn(`Low confidence with 30s of data: ${result.confidence}%`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(
|
|
108
|
+
`All samples (610): converged=${result.converged}, confidence=${result.confidence}%`,
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("convergence progression over time", () => {
|
|
113
|
+
const checkpoints = [50, 100, 150, 200, 300, 400, 500, 610];
|
|
114
|
+
const progressions = checkpoints.map(n => {
|
|
115
|
+
const result = checkConvergence(bevy30SamplesNs.slice(0, n));
|
|
116
|
+
return { samples: n, confidence: result.confidence };
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Confidence should generally increase with more samples
|
|
120
|
+
console.log("Convergence progression:");
|
|
121
|
+
for (const { samples, confidence } of progressions) {
|
|
122
|
+
console.log(` ${samples} samples: ${confidence.toFixed(1)}%`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const earlyConfidence = progressions[0].confidence;
|
|
126
|
+
const lateConfidence = progressions.at(-1)!.confidence;
|
|
127
|
+
|
|
128
|
+
if (lateConfidence < earlyConfidence) {
|
|
129
|
+
console.warn(
|
|
130
|
+
"Confidence decreased over time - may indicate benchmark instability",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("window size adaptation for different execution times", () => {
|
|
136
|
+
// Fast samples (microseconds)
|
|
137
|
+
const fastSamples = Array.from(
|
|
138
|
+
{ length: 100 },
|
|
139
|
+
() => 10e3 + Math.random() * 1e3, // 10-11us
|
|
140
|
+
);
|
|
141
|
+
const fastResult = checkConvergence(fastSamples);
|
|
142
|
+
|
|
143
|
+
// Slow samples (milliseconds)
|
|
144
|
+
const slowSamples = Array.from(
|
|
145
|
+
{ length: 100 },
|
|
146
|
+
() => 50e6 + Math.random() * 1e6, // 50-51ms
|
|
147
|
+
);
|
|
148
|
+
const slowResult = checkConvergence(slowSamples);
|
|
149
|
+
|
|
150
|
+
console.log(`Fast samples (10μs): confidence=${fastResult.confidence}%`);
|
|
151
|
+
console.log(`Slow samples (50ms): confidence=${slowResult.confidence}%`);
|
|
152
|
+
|
|
153
|
+
if (fastResult.confidence > 100 || slowResult.confidence > 100) {
|
|
154
|
+
throw new Error("Confidence exceeds 100%");
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("outlier impact calculation", () => {
|
|
159
|
+
// 95 stable samples + 5 outliers (2x slower)
|
|
160
|
+
const base = 50e6; // 50ms
|
|
161
|
+
const stable = Array.from(
|
|
162
|
+
{ length: 95 },
|
|
163
|
+
() => base + (Math.random() - 0.5) * 1e6,
|
|
164
|
+
);
|
|
165
|
+
const samples = [...stable, ...Array(5).fill(base * 2)];
|
|
166
|
+
|
|
167
|
+
const result = checkConvergence(samples);
|
|
168
|
+
|
|
169
|
+
// With 5% outliers doubling execution time, should impact convergence
|
|
170
|
+
console.log(
|
|
171
|
+
`With 5% outliers: converged=${result.converged}, confidence=${result.confidence}%`,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (result.reason.includes("Outlier impact") && result.confidence > 90) {
|
|
175
|
+
throw new Error("Should detect outlier impact or have lower confidence");
|
|
176
|
+
}
|
|
177
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { test } from "vitest";
|
|
2
|
+
import type { BenchmarkSpec } from "../Benchmark.ts";
|
|
3
|
+
import type { MeasuredResults } from "../MeasuredResults.ts";
|
|
4
|
+
import { createAdaptiveWrapper } from "../runners/AdaptiveWrapper.ts";
|
|
5
|
+
import type { BenchRunner } from "../runners/BenchRunner.ts";
|
|
6
|
+
import { bevy30SamplesMs } from "./fixtures/bevy30-samples.ts";
|
|
7
|
+
|
|
8
|
+
/** Assert convergence data exists, return the result for further checks. */
|
|
9
|
+
function requireConvergence(result: MeasuredResults): MeasuredResults {
|
|
10
|
+
if (!result.convergence) throw new Error("Missing convergence data");
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Mock runner that returns pre-recorded samples */
|
|
15
|
+
function createMockRunner(samples: number[]): BenchRunner {
|
|
16
|
+
let sampleIndex = 0;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
async runBench(benchmark, options) {
|
|
20
|
+
const { minTime = 100, maxIterations = 10 } = options;
|
|
21
|
+
const batchSamples: number[] = [];
|
|
22
|
+
const startTime = performance.now();
|
|
23
|
+
|
|
24
|
+
while (
|
|
25
|
+
sampleIndex < samples.length &&
|
|
26
|
+
batchSamples.length < (maxIterations ?? 10) &&
|
|
27
|
+
performance.now() - startTime < minTime
|
|
28
|
+
) {
|
|
29
|
+
batchSamples.push(samples[sampleIndex++]);
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sorted = [...batchSamples].sort((a, b) => a - b);
|
|
34
|
+
const avg = batchSamples.reduce((a, b) => a + b, 0) / batchSamples.length;
|
|
35
|
+
const p50 = sorted[Math.floor(sorted.length / 2)];
|
|
36
|
+
const time = { min: sorted[0], max: sorted.at(-1)!, avg, p50 };
|
|
37
|
+
return [
|
|
38
|
+
{ name: benchmark.name, samples: batchSamples, time },
|
|
39
|
+
] as MeasuredResults[];
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test("adaptive wrapper stops early with stable samples", async () => {
|
|
45
|
+
const stableSamples = Array.from(
|
|
46
|
+
{ length: 500 },
|
|
47
|
+
() => 50 + Math.random() * 0.5,
|
|
48
|
+
);
|
|
49
|
+
const mockRunner = createMockRunner(stableSamples);
|
|
50
|
+
|
|
51
|
+
const adaptiveRunner = createAdaptiveWrapper(mockRunner, {});
|
|
52
|
+
const bench: BenchmarkSpec = { name: "stable-test", fn: () => {} };
|
|
53
|
+
|
|
54
|
+
const startTime = performance.now();
|
|
55
|
+
const results = await adaptiveRunner.runBench(bench, {
|
|
56
|
+
minTime: 500, // 0.5s minimum
|
|
57
|
+
maxTime: 5000, // 5s maximum
|
|
58
|
+
});
|
|
59
|
+
const duration = performance.now() - startTime;
|
|
60
|
+
|
|
61
|
+
// Should stop early due to convergence
|
|
62
|
+
if (duration > 3000) {
|
|
63
|
+
console.warn(`Took ${duration}ms - may not have converged early`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = requireConvergence(results[0]);
|
|
67
|
+
|
|
68
|
+
console.log(
|
|
69
|
+
`Stable samples: ${result.samples.length} samples, ${result.convergence!.confidence}% confidence`,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (result.convergence!.confidence < 95) {
|
|
73
|
+
throw new Error("Should achieve high confidence with stable samples");
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("adaptive wrapper continues with unstable samples", async () => {
|
|
78
|
+
const unstableSamples = Array.from(
|
|
79
|
+
{ length: 500 },
|
|
80
|
+
() => 30 + Math.random() * 40,
|
|
81
|
+
);
|
|
82
|
+
const mockRunner = createMockRunner(unstableSamples);
|
|
83
|
+
|
|
84
|
+
const adaptiveRunner = createAdaptiveWrapper(mockRunner, {});
|
|
85
|
+
const bench: BenchmarkSpec = { name: "unstable-test", fn: () => {} };
|
|
86
|
+
const results = await adaptiveRunner.runBench(bench, {
|
|
87
|
+
minTime: 100, // 0.1s minimum
|
|
88
|
+
maxTime: 500, // 0.5s maximum
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const result = requireConvergence(results[0]);
|
|
92
|
+
|
|
93
|
+
console.log(
|
|
94
|
+
`Unstable samples: ${result.samples.length} samples, ${result.convergence!.confidence}% confidence`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (result.convergence!.confidence > 80) {
|
|
98
|
+
console.warn("Achieved high confidence despite unstable samples");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("adaptive wrapper with real bevy30 data", async () => {
|
|
103
|
+
const bench: BenchmarkSpec = { name: "bevy-test", fn: () => {} };
|
|
104
|
+
|
|
105
|
+
const configs = [
|
|
106
|
+
{ minTime: 1000, maxTime: 5000, label: "1-5s" },
|
|
107
|
+
{ minTime: 2000, maxTime: 10000, label: "2-10s" },
|
|
108
|
+
{ minTime: 5000, maxTime: 30000, label: "5-30s" },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (const config of configs) {
|
|
112
|
+
// Reset sample index for each test
|
|
113
|
+
const runner = createMockRunner(bevy30SamplesMs);
|
|
114
|
+
const adaptive = createAdaptiveWrapper(runner, {});
|
|
115
|
+
|
|
116
|
+
const results = await adaptive.runBench(bench, config);
|
|
117
|
+
|
|
118
|
+
const result = requireConvergence(results[0]);
|
|
119
|
+
|
|
120
|
+
console.log(
|
|
121
|
+
`Config ${config.label}: ${result.samples.length} samples, ${result.convergence!.confidence}% confidence`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("adaptive wrapper respects target confidence", async () => {
|
|
127
|
+
const mockRunner = createMockRunner(bevy30SamplesMs);
|
|
128
|
+
|
|
129
|
+
const wrapper = createAdaptiveWrapper(mockRunner, { convergence: 50 });
|
|
130
|
+
const bench: BenchmarkSpec = { name: "low-confidence-test", fn: () => {} };
|
|
131
|
+
|
|
132
|
+
const startTime = performance.now();
|
|
133
|
+
const results = await wrapper.runBench(bench, {
|
|
134
|
+
minTime: 500,
|
|
135
|
+
maxTime: 10000,
|
|
136
|
+
});
|
|
137
|
+
const duration = performance.now() - startTime;
|
|
138
|
+
|
|
139
|
+
const result = requireConvergence(results[0]);
|
|
140
|
+
|
|
141
|
+
console.log(
|
|
142
|
+
`Low target (50%): ${result.samples.length} samples in ${duration}ms, ${result.convergence!.confidence}% confidence`,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Should stop relatively quickly with low target
|
|
146
|
+
if (duration > 5000 && result.convergence!.confidence > 50) {
|
|
147
|
+
console.warn("Took longer than expected for low confidence target");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("adaptive wrapper handles warm-up period", async () => {
|
|
152
|
+
// Simulate warm-up: slow samples at start, then stable
|
|
153
|
+
// Decreasing from 100ms to 60ms, then stable at ~50ms
|
|
154
|
+
const warmup = Array.from({ length: 20 }, (_, i) => 100 - i * 2);
|
|
155
|
+
const stable = Array.from({ length: 200 }, () => 50 + Math.random());
|
|
156
|
+
const warmupSamples = [...warmup, ...stable];
|
|
157
|
+
|
|
158
|
+
const mockRunner = createMockRunner(warmupSamples);
|
|
159
|
+
const adaptiveRunner = createAdaptiveWrapper(mockRunner, {});
|
|
160
|
+
|
|
161
|
+
const bench: BenchmarkSpec = { name: "warmup-test", fn: () => {} };
|
|
162
|
+
const results = await adaptiveRunner.runBench(bench, {
|
|
163
|
+
minTime: 1000,
|
|
164
|
+
maxTime: 5000,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const result = requireConvergence(results[0]);
|
|
168
|
+
if (!result.time) throw new Error("Missing time stats");
|
|
169
|
+
|
|
170
|
+
console.log(
|
|
171
|
+
`Warmup test: median=${result.time.p50?.toFixed(1)}ms, mean=${result.time.avg?.toFixed(1)}ms`,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Median should be close to stable value (50ms) despite warm-up
|
|
175
|
+
if (result.time.p50 && Math.abs(result.time.p50 - 50) > 5) {
|
|
176
|
+
console.warn(`Median ${result.time.p50}ms differs from stable 50ms`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("adaptive wrapper statistics calculation", async () => {
|
|
181
|
+
const samples = bevy30SamplesMs.slice(100, 200);
|
|
182
|
+
const mockRunner = createMockRunner(samples);
|
|
183
|
+
const adaptiveRunner = createAdaptiveWrapper(mockRunner, {});
|
|
184
|
+
|
|
185
|
+
const bench: BenchmarkSpec = { name: "stats-test", fn: () => {} };
|
|
186
|
+
const results = await adaptiveRunner.runBench(bench, {
|
|
187
|
+
minTime: 100,
|
|
188
|
+
maxTime: 1000,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = results[0];
|
|
192
|
+
if (!result.time) throw new Error("Missing time statistics");
|
|
193
|
+
|
|
194
|
+
const { min, p25, p50, p75, p95, p99, max } = result.time;
|
|
195
|
+
const ordered = [min, p25, p50, p75, p95, p99, max];
|
|
196
|
+
if (ordered.some(v => v == null)) throw new Error("Missing percentile data");
|
|
197
|
+
if (ordered.some((v, i) => i > 0 && v! < ordered[i - 1]!)) {
|
|
198
|
+
throw new Error("Percentiles not in correct order");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(
|
|
202
|
+
`Statistics: min=${min.toFixed(1)}, p50=${p50.toFixed(1)}, p99=${p99.toFixed(1)}, max=${max.toFixed(1)}`,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (result.time.cv === undefined || result.time.mad === undefined) {
|
|
206
|
+
throw new Error("Missing variability metrics (CV or MAD)");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(
|
|
210
|
+
`Variability: CV=${(result.time.cv * 100).toFixed(1)}%, MAD=${result.time.mad.toFixed(2)}`,
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("adaptive wrapper total time tracking", async () => {
|
|
215
|
+
const mockRunner = createMockRunner(bevy30SamplesMs.slice(0, 100));
|
|
216
|
+
const adaptiveRunner = createAdaptiveWrapper(mockRunner, {});
|
|
217
|
+
|
|
218
|
+
const bench: BenchmarkSpec = { name: "time-tracking-test", fn: () => {} };
|
|
219
|
+
|
|
220
|
+
const startTime = performance.now();
|
|
221
|
+
const results = await adaptiveRunner.runBench(bench, {
|
|
222
|
+
minTime: 200,
|
|
223
|
+
maxTime: 1000,
|
|
224
|
+
});
|
|
225
|
+
const actualDuration = (performance.now() - startTime) / 1000;
|
|
226
|
+
|
|
227
|
+
const result = results[0];
|
|
228
|
+
if (!result.totalTime) throw new Error("Missing totalTime");
|
|
229
|
+
|
|
230
|
+
console.log(
|
|
231
|
+
`Total time: reported=${result.totalTime.toFixed(2)}s, actual=${actualDuration.toFixed(2)}s`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Total time should be close to actual duration
|
|
235
|
+
if (Math.abs(result.totalTime - actualDuration) > 0.5) {
|
|
236
|
+
console.warn(
|
|
237
|
+
`Time tracking mismatch: ${Math.abs(result.totalTime - actualDuration).toFixed(2)}s difference`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
});
|