benchforge 0.1.9 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +99 -260
- package/bin/benchforge +1 -2
- package/dist/AnalyzeArchive-8NCJhmhS.mjs +145 -0
- package/dist/AnalyzeArchive-8NCJhmhS.mjs.map +1 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs +1050 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs.map +1 -0
- package/dist/BenchRunner-DglX1NOn.d.mts +302 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs +27 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs.map +1 -0
- package/dist/Formatters-BWj3d4sv.mjs +95 -0
- package/dist/Formatters-BWj3d4sv.mjs.map +1 -0
- package/dist/{HeapSampler-B8dtKHn1.mjs → HeapSampler-Dq-hpXem.mjs} +4 -4
- package/dist/HeapSampler-Dq-hpXem.mjs.map +1 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs +3075 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs.map +1 -0
- package/dist/StatisticalUtils-BD92crgM.mjs +255 -0
- package/dist/StatisticalUtils-BD92crgM.mjs.map +1 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs +29 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs.map +1 -0
- package/dist/ViewerServer-BJhdnxlN.mjs +639 -0
- package/dist/ViewerServer-BJhdnxlN.mjs.map +1 -0
- package/dist/ViewerServer-CuMNdNBz.mjs +2 -0
- package/dist/bin/benchforge.mjs +4 -5
- package/dist/bin/benchforge.mjs.map +1 -1
- package/dist/index.d.mts +731 -522
- package/dist/index.mjs +98 -3
- package/dist/index.mjs.map +1 -0
- package/dist/runners/WorkerScript.d.mts +12 -4
- package/dist/runners/WorkerScript.mjs +92 -120
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/viewer/assets/CIPlot-BkOvMoMa.js +1 -0
- package/dist/viewer/assets/HistogramKde-CmSyUFY0.js +1 -0
- package/dist/viewer/assets/LegendUtils-BJpbn_jr.js +55 -0
- package/dist/viewer/assets/SampleTimeSeries-C4VBhXr3.js +1 -0
- package/dist/viewer/assets/index-Br9bp_cX.js +153 -0
- package/dist/viewer/assets/index-NzXXe_CC.css +1 -0
- package/dist/viewer/index.html +19 -0
- package/dist/viewer/speedscope/LICENSE +21 -0
- package/dist/viewer/speedscope/SourceCodePro-Regular.ttf-ILST5JV6.woff2 +0 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js +2 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js.map +7 -0
- package/dist/viewer/speedscope/favicon-16x16-VSI62OPJ.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-3EB2YCUY.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js +2 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js.map +7 -0
- package/dist/viewer/speedscope/favicon-FOKUP5Y5.ico +0 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js +2 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js.map +7 -0
- package/dist/viewer/speedscope/file-format-schema.json +274 -0
- package/dist/viewer/speedscope/index.html +19 -0
- package/dist/viewer/speedscope/jfrview_bg-BLJXNNQB.wasm +0 -0
- package/dist/viewer/speedscope/perf-vertx-stacks-01-collapsed-all-ZNUIGAJL.txt +199 -0
- package/dist/viewer/speedscope/release.txt +3 -0
- package/dist/viewer/speedscope/source-code-pro.LICENSE.md +93 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css +2 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css.map +7 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js +212 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js.map +7 -0
- package/package.json +52 -26
- package/src/bin/benchforge.ts +2 -2
- package/src/cli/AnalyzeArchive.ts +232 -0
- package/src/cli/BrowserBench.ts +322 -0
- package/src/cli/CliArgs.ts +164 -48
- package/src/cli/CliExport.ts +179 -0
- package/src/cli/CliOptions.ts +147 -0
- package/src/cli/CliReport.ts +197 -0
- package/src/cli/FilterBenchmarks.ts +18 -30
- package/src/cli/RunBenchCLI.ts +138 -844
- package/src/cli/SuiteRunner.ts +160 -0
- package/src/cli/ViewerServer.ts +282 -0
- package/src/export/AllocExport.ts +121 -0
- package/src/export/ArchiveExport.ts +146 -0
- package/src/export/ArchiveFormat.ts +50 -0
- package/src/export/CoverageExport.ts +148 -0
- package/src/export/EditorUri.ts +10 -0
- package/src/export/PerfettoExport.ts +91 -126
- package/src/export/SpeedscopeTypes.ts +98 -0
- package/src/export/TimeExport.ts +115 -0
- package/src/index.ts +87 -62
- package/src/matrix/BenchMatrix.ts +230 -0
- package/src/matrix/CaseLoader.ts +8 -6
- package/src/matrix/MatrixDirRunner.ts +153 -0
- package/src/matrix/MatrixFilter.ts +55 -53
- package/src/matrix/MatrixInlineRunner.ts +50 -0
- package/src/matrix/MatrixReport.ts +94 -254
- package/src/matrix/VariantLoader.ts +9 -9
- package/src/profiling/browser/BenchLoop.ts +51 -0
- package/src/profiling/browser/BrowserCDP.ts +133 -0
- package/src/profiling/browser/BrowserGcStats.ts +33 -0
- package/src/profiling/browser/BrowserProfiler.ts +160 -0
- package/src/profiling/browser/CdpClient.ts +82 -0
- package/src/profiling/browser/CdpPage.ts +138 -0
- package/src/profiling/browser/ChromeLauncher.ts +158 -0
- package/src/profiling/browser/ChromeTraceEvent.ts +28 -0
- package/src/profiling/browser/PageLoadMode.ts +61 -0
- package/src/profiling/node/CoverageSampler.ts +27 -0
- package/src/profiling/node/CoverageTypes.ts +23 -0
- package/src/profiling/node/HeapSampleReport.ts +261 -0
- package/src/{heap-sample → profiling/node}/HeapSampler.ts +55 -13
- package/src/profiling/node/ResolvedProfile.ts +98 -0
- package/src/profiling/node/TimeSampler.ts +57 -0
- package/src/report/BenchmarkReport.ts +146 -0
- package/src/report/Colors.ts +9 -0
- package/src/report/Formatters.ts +110 -0
- package/src/report/GcSections.ts +151 -0
- package/src/{GitUtils.ts → report/GitUtils.ts} +18 -19
- package/src/report/HtmlReport.ts +223 -0
- package/src/report/ParseStats.ts +73 -0
- package/src/report/StandardSections.ts +147 -0
- package/src/report/ViewerSections.ts +286 -0
- package/src/report/text/TableReport.ts +253 -0
- package/src/report/text/TextReport.ts +123 -0
- package/src/runners/AdaptiveWrapper.ts +167 -287
- package/src/runners/BenchRunner.ts +27 -22
- package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
- package/src/runners/CreateRunner.ts +5 -7
- package/src/runners/GcStats.ts +58 -61
- package/src/{MeasuredResults.ts → runners/MeasuredResults.ts} +43 -37
- package/src/runners/MergeBatches.ts +123 -0
- package/src/{NodeGC.ts → runners/NodeGC.ts} +2 -3
- package/src/runners/RunnerOrchestrator.ts +180 -296
- package/src/runners/RunnerUtils.ts +75 -1
- package/src/runners/SampleStats.ts +100 -0
- package/src/runners/TimingRunner.ts +244 -0
- package/src/runners/TimingUtils.ts +3 -2
- package/src/runners/WorkerScript.ts +162 -178
- package/src/stats/BootstrapDifference.ts +282 -0
- package/src/{PermutationTest.ts → stats/PermutationTest.ts} +31 -40
- package/src/stats/StatisticalUtils.ts +445 -0
- package/src/{tests → test}/AdaptiveConvergence.test.ts +10 -10
- package/src/test/AdaptiveRunner.test.ts +39 -41
- package/src/{tests → test}/AdaptiveSampling.test.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +9 -41
- package/src/{tests → test}/BenchMatrix.test.ts +31 -28
- package/src/test/BenchmarkReport.test.ts +63 -13
- package/src/test/BrowserBench.e2e.test.ts +186 -17
- package/src/test/BrowserBench.test.ts +10 -5
- package/src/test/BuildTimeSection.test.ts +130 -0
- package/src/test/CapSamples.test.ts +82 -0
- package/src/test/CoverageExport.test.ts +115 -0
- package/src/test/CoverageSampler.test.ts +33 -0
- package/src/test/HeapAttribution.test.ts +51 -0
- package/src/{tests → test}/MatrixFilter.test.ts +16 -16
- package/src/{tests → test}/MatrixReport.test.ts +1 -1
- package/src/test/PermutationTest.test.ts +1 -1
- package/src/{tests → test}/RealDataValidation.test.ts +6 -6
- package/src/test/RunBenchCLI.test.ts +57 -56
- package/src/test/RunnerOrchestrator.test.ts +12 -12
- package/src/test/StatisticalUtils.test.ts +48 -12
- package/src/{table-util/test → test}/TableReport.test.ts +2 -2
- package/src/test/TestUtils.ts +35 -30
- package/src/test/TimeExport.test.ts +139 -0
- package/src/test/TimeSampler.test.ts +37 -0
- package/src/test/ViewerLive.e2e.test.ts +159 -0
- package/src/test/ViewerStatic.static.e2e.test.ts +137 -0
- package/src/{tests → test}/fixtures/baseline/impl.ts +1 -1
- package/src/{tests → test}/fixtures/bevy30-samples.ts +3 -1
- package/src/test/fixtures/cases/asyncCases.ts +9 -0
- package/src/{tests → test}/fixtures/cases/cases.ts +5 -2
- package/src/test/fixtures/cases/variants/product.ts +2 -0
- package/src/test/fixtures/cases/variants/sum.ts +2 -0
- package/src/test/fixtures/discover/fast.ts +1 -0
- package/src/{tests → test}/fixtures/discover/slow.ts +1 -1
- package/src/test/fixtures/invalid/bad.ts +1 -0
- package/src/test/fixtures/loader/fast.ts +1 -0
- package/src/{tests → test}/fixtures/loader/slow.ts +1 -1
- package/src/test/fixtures/loader/stateful.ts +2 -0
- package/src/test/fixtures/stateful/stateful.ts +2 -0
- package/src/test/fixtures/variants/extra.ts +1 -0
- package/src/test/fixtures/variants/impl.ts +1 -0
- package/src/test/fixtures/worker/fast.ts +1 -0
- package/src/{tests → test}/fixtures/worker/slow.ts +1 -1
- package/src/viewer/DateFormat.ts +30 -0
- package/src/viewer/Helpers.ts +23 -0
- package/src/viewer/LineData.ts +120 -0
- package/src/viewer/Providers.ts +191 -0
- package/src/viewer/ReportData.ts +123 -0
- package/src/viewer/State.ts +49 -0
- package/src/viewer/Theme.ts +15 -0
- package/src/viewer/components/App.tsx +73 -0
- package/src/viewer/components/DropZone.tsx +71 -0
- package/src/viewer/components/LazyPlot.ts +33 -0
- package/src/viewer/components/SamplesPanel.tsx +214 -0
- package/src/viewer/components/Shell.tsx +26 -0
- package/src/viewer/components/SourcePanel.tsx +216 -0
- package/src/viewer/components/SummaryPanel.tsx +332 -0
- package/src/viewer/components/TabBar.tsx +131 -0
- package/src/viewer/components/TabContent.tsx +46 -0
- package/src/viewer/components/ThemeToggle.tsx +50 -0
- package/src/viewer/index.html +20 -0
- package/src/viewer/main.tsx +4 -0
- package/src/viewer/plots/CIPlot.ts +313 -0
- package/src/{html/browser → viewer/plots}/HistogramKde.ts +42 -47
- package/src/viewer/plots/LegendUtils.ts +134 -0
- package/src/viewer/plots/PlotTypes.ts +85 -0
- package/src/viewer/plots/RenderPlots.ts +230 -0
- package/src/viewer/plots/SampleTimeSeries.ts +306 -0
- package/src/viewer/plots/SvgHelpers.ts +136 -0
- package/src/viewer/plots/TimeSeriesMarks.ts +319 -0
- package/src/viewer/report.css +427 -0
- package/src/viewer/shell.css +357 -0
- package/src/viewer/tsconfig.json +11 -0
- package/dist/BenchRunner-CSKN9zPy.d.mts +0 -225
- package/dist/BrowserHeapSampler-DCeL42RE.mjs +0 -202
- package/dist/BrowserHeapSampler-DCeL42RE.mjs.map +0 -1
- package/dist/GcStats-ByEovUi1.mjs +0 -77
- package/dist/GcStats-ByEovUi1.mjs.map +0 -1
- package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
- package/dist/TimingUtils-ClclVQ7E.mjs +0 -597
- package/dist/TimingUtils-ClclVQ7E.mjs.map +0 -1
- package/dist/browser/index.js +0 -914
- package/dist/src-Cf_LXwlp.mjs +0 -2873
- package/dist/src-Cf_LXwlp.mjs.map +0 -1
- package/src/BenchMatrix.ts +0 -380
- package/src/BenchmarkReport.ts +0 -156
- package/src/HtmlDataPrep.ts +0 -148
- package/src/StandardSections.ts +0 -261
- package/src/StatisticalUtils.ts +0 -176
- package/src/TypeUtil.ts +0 -8
- package/src/browser/BrowserGcStats.ts +0 -44
- package/src/browser/BrowserHeapSampler.ts +0 -271
- package/src/export/JsonExport.ts +0 -103
- package/src/export/JsonFormat.ts +0 -91
- package/src/heap-sample/HeapSampleReport.ts +0 -196
- package/src/html/HtmlReport.ts +0 -131
- package/src/html/HtmlTemplate.ts +0 -284
- package/src/html/Types.ts +0 -88
- package/src/html/browser/CIPlot.ts +0 -287
- package/src/html/browser/LegendUtils.ts +0 -163
- package/src/html/browser/RenderPlots.ts +0 -263
- package/src/html/browser/SampleTimeSeries.ts +0 -389
- package/src/html/browser/Types.ts +0 -96
- package/src/html/browser/index.ts +0 -1
- package/src/html/index.ts +0 -17
- package/src/runners/BasicRunner.ts +0 -364
- package/src/table-util/ConvergenceFormatters.ts +0 -19
- package/src/table-util/Formatters.ts +0 -152
- package/src/table-util/README.md +0 -70
- package/src/table-util/TableReport.ts +0 -293
- package/src/tests/fixtures/cases/asyncCases.ts +0 -7
- package/src/tests/fixtures/cases/variants/product.ts +0 -2
- package/src/tests/fixtures/cases/variants/sum.ts +0 -2
- package/src/tests/fixtures/discover/fast.ts +0 -1
- package/src/tests/fixtures/invalid/bad.ts +0 -1
- package/src/tests/fixtures/loader/fast.ts +0 -1
- package/src/tests/fixtures/loader/stateful.ts +0 -2
- package/src/tests/fixtures/stateful/stateful.ts +0 -2
- package/src/tests/fixtures/variants/extra.ts +0 -1
- package/src/tests/fixtures/variants/impl.ts +0 -1
- package/src/tests/fixtures/worker/fast.ts +0 -1
- package/src/{table-util/test → test}/TableValueExtractor.test.ts +0 -0
- package/src/{table-util/test → test}/TableValueExtractor.ts +9 -9
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { expect, test } from "vitest";
|
|
4
|
-
import type { BenchSuite } from "../Benchmark.ts";
|
|
5
4
|
import { filterBenchmarks } from "../cli/FilterBenchmarks.ts";
|
|
5
|
+
import type { BenchSuite } from "../runners/BenchmarkSpec.ts";
|
|
6
6
|
import { runBenchCLITest } from "./TestUtils.ts";
|
|
7
7
|
|
|
8
8
|
const testSuite: BenchSuite = {
|
|
@@ -49,8 +49,26 @@ const suiteWithSetup: BenchSuite = {
|
|
|
49
49
|
],
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
/** Execute test fixture script and return output */
|
|
53
|
+
function executeTestScript(args = ""): string {
|
|
54
|
+
const script = path.join(
|
|
55
|
+
import.meta.dirname!,
|
|
56
|
+
"fixtures/test-bench-script.ts",
|
|
57
|
+
);
|
|
58
|
+
return execSync(`node --expose-gc --allow-natives-syntax ${script} ${args}`, {
|
|
59
|
+
encoding: "utf8",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Run a fixture file via bin/benchforge and return output */
|
|
64
|
+
function executeBenchforgeFile(file: string, args = ""): string {
|
|
65
|
+
const bin = path.join(import.meta.dirname!, "../../bin/benchforge");
|
|
66
|
+
const fixture = path.join(import.meta.dirname!, "fixtures", file);
|
|
67
|
+
return execSync(`${bin} ${fixture} ${args}`, { encoding: "utf8" });
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
test("runs all benchmarks", { timeout: 30000 }, async () => {
|
|
53
|
-
const output = await runBenchCLITest(testSuite, "--
|
|
71
|
+
const output = await runBenchCLITest(testSuite, "--duration 0.1");
|
|
54
72
|
|
|
55
73
|
expect(output).toContain("concatenation");
|
|
56
74
|
expect(output).toContain("template literal");
|
|
@@ -61,7 +79,10 @@ test("runs all benchmarks", { timeout: 30000 }, async () => {
|
|
|
61
79
|
});
|
|
62
80
|
|
|
63
81
|
test("filters by substring", { timeout: 15000 }, async () => {
|
|
64
|
-
const output = await runBenchCLITest(
|
|
82
|
+
const output = await runBenchCLITest(
|
|
83
|
+
testSuite,
|
|
84
|
+
"--filter concat --duration 0.1",
|
|
85
|
+
);
|
|
65
86
|
|
|
66
87
|
expect(output).toContain("concatenation");
|
|
67
88
|
expect(output).not.toContain("addition");
|
|
@@ -70,7 +91,7 @@ test("filters by substring", { timeout: 15000 }, async () => {
|
|
|
70
91
|
test("filters by regex", { timeout: 15000 }, async () => {
|
|
71
92
|
const output = await runBenchCLITest(
|
|
72
93
|
testSuite,
|
|
73
|
-
"--filter ^template --
|
|
94
|
+
"--filter ^template --duration 0.1",
|
|
74
95
|
);
|
|
75
96
|
expect(output).toContain("template literal");
|
|
76
97
|
expect(output).not.toContain("addition");
|
|
@@ -87,26 +108,8 @@ test("filter preserves suite structure", () => {
|
|
|
87
108
|
expect(filtered.groups[1].benchmarks).toHaveLength(0);
|
|
88
109
|
});
|
|
89
110
|
|
|
90
|
-
/** Execute test fixture script and return output */
|
|
91
|
-
function executeTestScript(args = ""): string {
|
|
92
|
-
const script = path.join(
|
|
93
|
-
import.meta.dirname!,
|
|
94
|
-
"fixtures/test-bench-script.ts",
|
|
95
|
-
);
|
|
96
|
-
return execSync(`node --expose-gc --allow-natives-syntax ${script} ${args}`, {
|
|
97
|
-
encoding: "utf8",
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Run a fixture file via bin/benchforge and return output */
|
|
102
|
-
function executeBenchforgeFile(file: string, args = ""): string {
|
|
103
|
-
const bin = path.join(import.meta.dirname!, "../../bin/benchforge");
|
|
104
|
-
const fixture = path.join(import.meta.dirname!, "fixtures", file);
|
|
105
|
-
return execSync(`${bin} ${fixture} ${args}`, { encoding: "utf8" });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
111
|
test("e2e: runs user script", { timeout: 30000 }, () => {
|
|
109
|
-
const output = executeTestScript("--
|
|
112
|
+
const output = executeTestScript("--duration 0.1");
|
|
110
113
|
|
|
111
114
|
expect(output).toContain("plus");
|
|
112
115
|
expect(output).toContain("multiply");
|
|
@@ -122,14 +125,14 @@ test("e2e: runs user script", { timeout: 30000 }, () => {
|
|
|
122
125
|
});
|
|
123
126
|
|
|
124
127
|
test("e2e: filter flag", { timeout: 30000 }, () => {
|
|
125
|
-
const output = executeTestScript('--filter "plus" --
|
|
128
|
+
const output = executeTestScript('--filter "plus" --duration 0.1');
|
|
126
129
|
|
|
127
130
|
expect(output).toContain("plus");
|
|
128
131
|
expect(output).not.toContain("multiply");
|
|
129
132
|
});
|
|
130
133
|
|
|
131
134
|
test("runs benchmarks with setup function", { timeout: 30000 }, async () => {
|
|
132
|
-
const output = await runBenchCLITest(suiteWithSetup, "--
|
|
135
|
+
const output = await runBenchCLITest(suiteWithSetup, "--duration 0.1");
|
|
133
136
|
|
|
134
137
|
expect(output).toContain("sum numbers");
|
|
135
138
|
expect(output).toContain("join strings");
|
|
@@ -137,40 +140,38 @@ test("runs benchmarks with setup function", { timeout: 30000 }, async () => {
|
|
|
137
140
|
expect(output).toContain("runs");
|
|
138
141
|
});
|
|
139
142
|
|
|
140
|
-
test(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
fn: ({ data }: any) => [...data].sort(),
|
|
155
|
-
},
|
|
156
|
-
benchmarks: [
|
|
157
|
-
{
|
|
158
|
-
name: "optimized sort",
|
|
159
|
-
fn: ({ data }: any) => [...data].sort((a, b) => a - b),
|
|
160
|
-
},
|
|
161
|
-
],
|
|
143
|
+
test("runs benchmarks with baseline comparison", {
|
|
144
|
+
timeout: 30000,
|
|
145
|
+
}, async () => {
|
|
146
|
+
const suiteWithBaseline: BenchSuite = {
|
|
147
|
+
name: "Baseline Test",
|
|
148
|
+
groups: [
|
|
149
|
+
{
|
|
150
|
+
name: "Sort Comparison",
|
|
151
|
+
setup: () => ({
|
|
152
|
+
data: Array.from({ length: 10 }, () => Math.random()),
|
|
153
|
+
}),
|
|
154
|
+
baseline: {
|
|
155
|
+
name: "baseline sort",
|
|
156
|
+
fn: ({ data }: any) => [...data].sort(),
|
|
162
157
|
},
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
benchmarks: [
|
|
159
|
+
{
|
|
160
|
+
name: "optimized sort",
|
|
161
|
+
fn: ({ data }: any) => [...data].sort((a, b) => a - b),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
const output = await runBenchCLITest(suiteWithBaseline, "--iterations 20");
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
170
|
+
expect(output).toContain("baseline sort");
|
|
171
|
+
expect(output).toContain("optimized sort");
|
|
172
|
+
expect(output).toContain("Δ%"); // Diff column should appear
|
|
173
|
+
expect(output).toContain("mean");
|
|
174
|
+
});
|
|
174
175
|
|
|
175
176
|
test("file mode: BenchSuite export", { timeout: 30000 }, () => {
|
|
176
177
|
const output = executeBenchforgeFile(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import type { BenchmarkSpec } from "../
|
|
2
|
+
import type { BenchmarkSpec } from "../runners/BenchmarkSpec.ts";
|
|
3
3
|
import { runBenchmark } from "../runners/RunnerOrchestrator.ts";
|
|
4
4
|
|
|
5
5
|
/** lightweight function for testing worker communication */
|
|
@@ -9,15 +9,15 @@ function simpleTestFunction(): number {
|
|
|
9
9
|
return sum;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
test("
|
|
12
|
+
test("TimingRunner runs benchmark in worker mode", async () => {
|
|
13
13
|
const spec: BenchmarkSpec = {
|
|
14
|
-
name: "
|
|
14
|
+
name: "timing-worker-test",
|
|
15
15
|
fn: simpleTestFunction,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const results = await runBenchmark({
|
|
19
19
|
spec,
|
|
20
|
-
runner: "
|
|
20
|
+
runner: "timing",
|
|
21
21
|
options: {
|
|
22
22
|
maxTime: 5,
|
|
23
23
|
maxIterations: 50,
|
|
@@ -28,7 +28,7 @@ test("BasicRunner runs benchmark in worker mode", async () => {
|
|
|
28
28
|
expect(results).toHaveLength(1);
|
|
29
29
|
const result = results[0];
|
|
30
30
|
|
|
31
|
-
expect(result.name).toBe("
|
|
31
|
+
expect(result.name).toBe("timing-worker-test");
|
|
32
32
|
expect(result.samples.length).toBeGreaterThan(0);
|
|
33
33
|
expect(result.samples.length).toBeLessThanOrEqual(500);
|
|
34
34
|
expect(result.time.min).toBeGreaterThan(0);
|
|
@@ -38,15 +38,15 @@ test("BasicRunner runs benchmark in worker mode", async () => {
|
|
|
38
38
|
expect(result.time.p99).toBeGreaterThan(0);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
test("
|
|
41
|
+
test("TimingRunner runs benchmark in non-worker mode", async () => {
|
|
42
42
|
const spec: BenchmarkSpec = {
|
|
43
|
-
name: "
|
|
43
|
+
name: "timing-test",
|
|
44
44
|
fn: simpleTestFunction,
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const results = await runBenchmark({
|
|
48
48
|
spec,
|
|
49
|
-
runner: "
|
|
49
|
+
runner: "timing",
|
|
50
50
|
options: {
|
|
51
51
|
maxTime: 5,
|
|
52
52
|
maxIterations: 50,
|
|
@@ -57,12 +57,12 @@ test("BasicRunner runs benchmark in non-worker mode", async () => {
|
|
|
57
57
|
expect(results).toHaveLength(1);
|
|
58
58
|
const result = results[0];
|
|
59
59
|
|
|
60
|
-
expect(result.name).toBe("
|
|
60
|
+
expect(result.name).toBe("timing-test");
|
|
61
61
|
expect(result.samples.length).toBeGreaterThan(0);
|
|
62
62
|
expect(result.time.p50).toBeGreaterThan(0);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
test("
|
|
65
|
+
test("TimingRunner with parameterized benchmark", async () => {
|
|
66
66
|
const spec: BenchmarkSpec<number> = {
|
|
67
67
|
name: "parameterized-test",
|
|
68
68
|
fn: (n: number) => {
|
|
@@ -74,7 +74,7 @@ test("BasicRunner with parameterized benchmark", async () => {
|
|
|
74
74
|
|
|
75
75
|
const results = await runBenchmark({
|
|
76
76
|
spec,
|
|
77
|
-
runner: "
|
|
77
|
+
runner: "timing",
|
|
78
78
|
options: { maxTime: 5, maxIterations: 20 },
|
|
79
79
|
useWorker: false,
|
|
80
80
|
params: 100,
|
|
@@ -94,7 +94,7 @@ test("RunnerOrchestrator propagates errors from worker", async () => {
|
|
|
94
94
|
|
|
95
95
|
const promise = runBenchmark({
|
|
96
96
|
spec,
|
|
97
|
-
runner: "
|
|
97
|
+
runner: "timing",
|
|
98
98
|
options: { maxTime: 1, maxIterations: 1 },
|
|
99
99
|
useWorker: true,
|
|
100
100
|
});
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
blockDifferenceCI,
|
|
4
|
+
sampleDifferenceCI,
|
|
5
|
+
} from "../stats/BootstrapDifference.ts";
|
|
2
6
|
import {
|
|
3
7
|
average,
|
|
4
|
-
|
|
5
|
-
bootstrapMedian,
|
|
8
|
+
blockBootstrap,
|
|
6
9
|
coefficientOfVariation,
|
|
7
10
|
findOutliers,
|
|
11
|
+
median,
|
|
8
12
|
medianAbsoluteDeviation,
|
|
9
13
|
percentile,
|
|
14
|
+
sampleBootstrap,
|
|
10
15
|
standardDeviation,
|
|
11
|
-
} from "../StatisticalUtils.ts";
|
|
16
|
+
} from "../stats/StatisticalUtils.ts";
|
|
12
17
|
import { assertValid, getSampleData } from "./TestUtils.ts";
|
|
13
18
|
|
|
14
19
|
test("calculates mean correctly", () => {
|
|
@@ -68,11 +73,21 @@ test("identifies outliers in mixed data", () => {
|
|
|
68
73
|
expect(outliers.indices).toContain(51);
|
|
69
74
|
});
|
|
70
75
|
|
|
71
|
-
test("
|
|
76
|
+
test("sampleBootstrap estimates median with CI", () => {
|
|
77
|
+
const stable = getSampleData(400, 450);
|
|
78
|
+
const result = sampleBootstrap(stable, median, { resamples: 1000 });
|
|
79
|
+
expect(result.ciLevel).toBe("sample");
|
|
80
|
+
expect(result.ci[0]).toBeLessThanOrEqual(result.estimate);
|
|
81
|
+
expect(result.ci[1]).toBeGreaterThanOrEqual(result.estimate);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("blockBootstrap estimates median with confidence intervals", () => {
|
|
72
85
|
const stable = getSampleData(400, 450);
|
|
73
86
|
const actual = percentile(stable, 0.5);
|
|
74
|
-
const
|
|
87
|
+
const blocks = Array.from({ length: 5 }, (_, i) => i * 10);
|
|
88
|
+
const result = blockBootstrap(stable, blocks, median, { resamples: 1000 });
|
|
75
89
|
|
|
90
|
+
expect(result.ciLevel).toBe("block");
|
|
76
91
|
expect(result.estimate).toBeCloseTo(actual, 1);
|
|
77
92
|
expect(result.ci[0]).toBeLessThanOrEqual(result.estimate);
|
|
78
93
|
expect(result.ci[1]).toBeGreaterThanOrEqual(result.estimate);
|
|
@@ -80,32 +95,53 @@ test("bootstrap estimates median with confidence intervals", () => {
|
|
|
80
95
|
expect(result.samples).toHaveLength(1000);
|
|
81
96
|
});
|
|
82
97
|
|
|
83
|
-
test("
|
|
98
|
+
test("sampleDifferenceCI detects improvement", () => {
|
|
99
|
+
const baseline = getSampleData(0, 100);
|
|
100
|
+
const improved = baseline.map(v => v * 0.8);
|
|
101
|
+
const result = sampleDifferenceCI(baseline, improved, median, {
|
|
102
|
+
resamples: 1000,
|
|
103
|
+
});
|
|
104
|
+
expect(result.ciLevel).toBe("sample");
|
|
105
|
+
expect(result.percent).toBeCloseTo(-20, 0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("blockDifferenceCI detects improvement", () => {
|
|
84
109
|
const baseline = getSampleData(0, 100);
|
|
85
110
|
const improved = baseline.map(v => v * 0.8);
|
|
86
|
-
const
|
|
111
|
+
const blocks = Array.from({ length: 10 }, (_, i) => i * 10);
|
|
112
|
+
const result = blockDifferenceCI(baseline, blocks, improved, median, {
|
|
113
|
+
resamples: 1000,
|
|
114
|
+
});
|
|
87
115
|
|
|
116
|
+
expect(result.ciLevel).toBe("block");
|
|
88
117
|
expect(result.percent).toBeCloseTo(-20, 0);
|
|
89
118
|
expect(result.ci[1]).toBeLessThan(0);
|
|
90
119
|
expect(result.direction).toBe("faster");
|
|
91
120
|
});
|
|
92
121
|
|
|
93
|
-
test("
|
|
122
|
+
test("blockDifferenceCI detects regression", () => {
|
|
94
123
|
const baseline = getSampleData(0, 100);
|
|
95
124
|
const slower = baseline.map(v => v * 1.2);
|
|
96
|
-
const
|
|
125
|
+
const blocks = Array.from({ length: 10 }, (_, i) => i * 10);
|
|
126
|
+
const result = blockDifferenceCI(baseline, blocks, slower, median, {
|
|
127
|
+
resamples: 1000,
|
|
128
|
+
});
|
|
97
129
|
|
|
130
|
+
expect(result.ciLevel).toBe("block");
|
|
98
131
|
expect(result.percent).toBeCloseTo(20, 0);
|
|
99
132
|
expect(result.ci[0]).toBeGreaterThan(0);
|
|
100
133
|
expect(result.direction).toBe("slower");
|
|
101
134
|
});
|
|
102
135
|
|
|
103
|
-
test("
|
|
136
|
+
test("blockDifferenceCI shows uncertainty for noise", () => {
|
|
104
137
|
const baseline = getSampleData(0, 100);
|
|
105
138
|
const noisy = baseline.map(v => v + (Math.random() - 0.5) * 2);
|
|
106
|
-
const
|
|
139
|
+
const blocks = Array.from({ length: 10 }, (_, i) => i * 10);
|
|
140
|
+
const result = blockDifferenceCI(baseline, blocks, noisy, median, {
|
|
141
|
+
resamples: 1000,
|
|
142
|
+
});
|
|
107
143
|
|
|
108
|
-
|
|
144
|
+
expect(result.ciLevel).toBe("block");
|
|
109
145
|
expect(result.ci[0]).toBeLessThanOrEqual(0);
|
|
110
146
|
expect(result.ci[1]).toBeGreaterThanOrEqual(0);
|
|
111
147
|
expect(result.direction).toBe("uncertain");
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import { integer } from "../Formatters.ts";
|
|
2
|
+
import { integer } from "../report/Formatters.ts";
|
|
3
3
|
import {
|
|
4
4
|
buildTable,
|
|
5
5
|
type ColumnGroup,
|
|
6
6
|
type ResultGroup,
|
|
7
|
-
} from "../TableReport.ts";
|
|
7
|
+
} from "../report/text/TableReport.ts";
|
|
8
8
|
|
|
9
9
|
interface TestRecord {
|
|
10
10
|
name: string;
|
package/src/test/TestUtils.ts
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
|
-
import type { BenchSuite } from "../Benchmark.ts";
|
|
2
|
-
import type { BenchmarkReport } from "../BenchmarkReport.ts";
|
|
3
1
|
import type { Configure, DefaultCliArgs } from "../cli/CliArgs.ts";
|
|
4
2
|
import { parseCliArgs } from "../cli/CliArgs.ts";
|
|
5
|
-
import { defaultReport
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { defaultReport } from "../cli/CliReport.ts";
|
|
4
|
+
import { runBenchmarks } from "../cli/SuiteRunner.ts";
|
|
5
|
+
import type { BenchmarkReport } from "../report/BenchmarkReport.ts";
|
|
6
|
+
import type { BenchSuite } from "../runners/BenchmarkSpec.ts";
|
|
7
|
+
import type { MeasuredResults } from "../runners/MeasuredResults.ts";
|
|
8
|
+
import { average, percentile } from "../stats/StatisticalUtils.ts";
|
|
9
|
+
import { bevy30SamplesMs } from "./fixtures/bevy30-samples.ts";
|
|
10
|
+
|
|
11
|
+
/** Validation helpers for statistical tests */
|
|
12
|
+
export const assertValid: {
|
|
13
|
+
pValue: (value: number) => void;
|
|
14
|
+
percentileOrder: (p25: number, p50: number, p75: number, p99: number) => void;
|
|
15
|
+
significance: (level: string) => void;
|
|
16
|
+
} = {
|
|
17
|
+
pValue: (value: number) => {
|
|
18
|
+
if (value < 0 || value > 1) {
|
|
19
|
+
throw new Error(`Expected p-value between 0 and 1, got ${value}`);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
percentileOrder: (p25: number, p50: number, p75: number, p99: number) => {
|
|
24
|
+
if (!(p25 <= p50 && p50 <= p75 && p75 <= p99)) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Percentiles not ordered: p25=${p25}, p50=${p50}, p75=${p75}, p99=${p99}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
significance: (level: string) => {
|
|
32
|
+
const valid = ["none", "weak", "good", "strong"];
|
|
33
|
+
if (!valid.includes(level)) {
|
|
34
|
+
throw new Error(`Invalid significance level: ${level}`);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|
|
9
38
|
|
|
10
39
|
/** @return formatted benchmark output for CLI testing */
|
|
11
40
|
export async function runBenchCLITest<T = DefaultCliArgs>(
|
|
@@ -67,27 +96,3 @@ export function createBenchmarkReport(
|
|
|
67
96
|
measuredResults: createMeasuredResults(sampleRange, overrides),
|
|
68
97
|
};
|
|
69
98
|
}
|
|
70
|
-
|
|
71
|
-
/** Validation helpers for statistical tests */
|
|
72
|
-
export const assertValid = {
|
|
73
|
-
pValue: (value: number) => {
|
|
74
|
-
if (value < 0 || value > 1) {
|
|
75
|
-
throw new Error(`Expected p-value between 0 and 1, got ${value}`);
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
percentileOrder: (p25: number, p50: number, p75: number, p99: number) => {
|
|
80
|
-
if (!(p25 <= p50 && p50 <= p75 && p75 <= p99)) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`Percentiles not ordered: p25=${p25}, p50=${p50}, p75=${p75}, p99=${p99}`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
significance: (level: string) => {
|
|
88
|
-
const valid = ["none", "weak", "good", "strong"];
|
|
89
|
-
if (!valid.includes(level)) {
|
|
90
|
-
throw new Error(`Invalid significance level: ${level}`);
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { timeProfileToSpeedscope } from "../export/TimeExport.ts";
|
|
3
|
+
import type { TimeProfile } from "../profiling/node/TimeSampler.ts";
|
|
4
|
+
|
|
5
|
+
/** Build a minimal TimeProfile for testing */
|
|
6
|
+
function mockProfile(): TimeProfile {
|
|
7
|
+
return {
|
|
8
|
+
nodes: [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
callFrame: { functionName: "", url: "", lineNumber: -1 },
|
|
12
|
+
children: [2],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 2,
|
|
16
|
+
callFrame: {
|
|
17
|
+
functionName: "main",
|
|
18
|
+
url: "file:///app.ts",
|
|
19
|
+
lineNumber: 9,
|
|
20
|
+
columnNumber: 0,
|
|
21
|
+
},
|
|
22
|
+
children: [3],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 3,
|
|
26
|
+
callFrame: {
|
|
27
|
+
functionName: "compute",
|
|
28
|
+
url: "file:///app.ts",
|
|
29
|
+
lineNumber: 19,
|
|
30
|
+
columnNumber: 4,
|
|
31
|
+
},
|
|
32
|
+
hitCount: 5,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
startTime: 0,
|
|
36
|
+
endTime: 5000,
|
|
37
|
+
samples: [3, 3, 2, 3, 3],
|
|
38
|
+
timeDeltas: [1000, 1000, 1000, 1000, 1000],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test("converts TimeProfile to valid SpeedScope format", () => {
|
|
43
|
+
const profile = mockProfile();
|
|
44
|
+
const file = timeProfileToSpeedscope("test-bench", profile);
|
|
45
|
+
|
|
46
|
+
expect(file.$schema).toBe(
|
|
47
|
+
"https://www.speedscope.app/file-format-schema.json",
|
|
48
|
+
);
|
|
49
|
+
expect(file.exporter).toBe("benchforge");
|
|
50
|
+
expect(file.profiles).toHaveLength(1);
|
|
51
|
+
|
|
52
|
+
const p = file.profiles[0];
|
|
53
|
+
expect(p.type).toBe("sampled");
|
|
54
|
+
expect(p.name).toBe("test-bench");
|
|
55
|
+
expect(p.unit).toBe("microseconds");
|
|
56
|
+
expect(p.samples).toHaveLength(5);
|
|
57
|
+
expect(p.weights).toEqual([1000, 1000, 1000, 1000, 1000]);
|
|
58
|
+
expect(p.endValue).toBe(5000);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("resolves stacks from leaf to root (root-first order)", () => {
|
|
62
|
+
const profile = mockProfile();
|
|
63
|
+
const file = timeProfileToSpeedscope("test", profile);
|
|
64
|
+
|
|
65
|
+
const p = file.profiles[0];
|
|
66
|
+
const frames = file.shared.frames;
|
|
67
|
+
|
|
68
|
+
// Sample at node 3 (compute) should have stack: [main, compute]
|
|
69
|
+
const deepStack = p.samples[0];
|
|
70
|
+
expect(deepStack).toHaveLength(2); // root is skipped
|
|
71
|
+
expect(frames[deepStack[0]].name).toBe("main");
|
|
72
|
+
expect(frames[deepStack[1]].name).toBe("compute");
|
|
73
|
+
|
|
74
|
+
// Sample at node 2 (main) should have stack: [main]
|
|
75
|
+
const shallowStack = p.samples[2];
|
|
76
|
+
expect(shallowStack).toHaveLength(1);
|
|
77
|
+
expect(frames[shallowStack[0]].name).toBe("main");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("deduplicates shared frames", () => {
|
|
81
|
+
const profile = mockProfile();
|
|
82
|
+
const file = timeProfileToSpeedscope("test", profile);
|
|
83
|
+
|
|
84
|
+
// "main" and "compute" — only 2 unique frames
|
|
85
|
+
expect(file.shared.frames).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("handles empty samples gracefully", () => {
|
|
89
|
+
const profile: TimeProfile = {
|
|
90
|
+
nodes: [
|
|
91
|
+
{ id: 1, callFrame: { functionName: "", url: "", lineNumber: -1 } },
|
|
92
|
+
],
|
|
93
|
+
startTime: 0,
|
|
94
|
+
endTime: 0,
|
|
95
|
+
samples: [],
|
|
96
|
+
timeDeltas: [],
|
|
97
|
+
};
|
|
98
|
+
const file = timeProfileToSpeedscope("empty", profile);
|
|
99
|
+
|
|
100
|
+
expect(file.profiles[0].samples).toHaveLength(0);
|
|
101
|
+
expect(file.profiles[0].weights).toHaveLength(0);
|
|
102
|
+
expect(file.profiles[0].endValue).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("converts 0-indexed V8 lines to 1-indexed", () => {
|
|
106
|
+
const profile = mockProfile();
|
|
107
|
+
const file = timeProfileToSpeedscope("test", profile);
|
|
108
|
+
|
|
109
|
+
const mainFrame = file.shared.frames.find(f => f.name === "main")!;
|
|
110
|
+
expect(mainFrame.line).toBe(10); // lineNumber 9 -> line 10
|
|
111
|
+
expect(mainFrame.col).toBe(1); // columnNumber 0 -> col 1
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("anonymous functions get location hint in name", () => {
|
|
115
|
+
const profile: TimeProfile = {
|
|
116
|
+
nodes: [
|
|
117
|
+
{
|
|
118
|
+
id: 1,
|
|
119
|
+
callFrame: { functionName: "", url: "", lineNumber: -1 },
|
|
120
|
+
children: [2],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 2,
|
|
124
|
+
callFrame: {
|
|
125
|
+
functionName: "",
|
|
126
|
+
url: "file:///lib/utils.ts",
|
|
127
|
+
lineNumber: 41,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
startTime: 0,
|
|
132
|
+
endTime: 1000,
|
|
133
|
+
samples: [2],
|
|
134
|
+
timeDeltas: [1000],
|
|
135
|
+
};
|
|
136
|
+
const file = timeProfileToSpeedscope("test", profile);
|
|
137
|
+
|
|
138
|
+
expect(file.shared.frames[0].name).toBe("(anonymous utils.ts:42)");
|
|
139
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { withTimeProfiling } from "../profiling/node/TimeSampler.ts";
|
|
3
|
+
|
|
4
|
+
test("withTimeProfiling returns valid V8 CPU profile", async () => {
|
|
5
|
+
// Burn some CPU to produce samples
|
|
6
|
+
const { result, profile } = await withTimeProfiling({}, () => {
|
|
7
|
+
let sum = 0;
|
|
8
|
+
for (let i = 0; i < 1e6; i++) sum += Math.sqrt(i);
|
|
9
|
+
return sum;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(result).toBeGreaterThan(0);
|
|
13
|
+
expect(profile.nodes.length).toBeGreaterThan(0);
|
|
14
|
+
expect(profile.startTime).toBeLessThan(profile.endTime);
|
|
15
|
+
expect(profile.samples).toBeDefined();
|
|
16
|
+
expect(profile.timeDeltas).toBeDefined();
|
|
17
|
+
expect(profile.samples!.length).toBe(profile.timeDeltas!.length);
|
|
18
|
+
|
|
19
|
+
// Verify node structure
|
|
20
|
+
const node = profile.nodes[0];
|
|
21
|
+
expect(node).toHaveProperty("id");
|
|
22
|
+
expect(node).toHaveProperty("callFrame");
|
|
23
|
+
expect(node.callFrame).toHaveProperty("functionName");
|
|
24
|
+
expect(node.callFrame).toHaveProperty("url");
|
|
25
|
+
expect(node.callFrame).toHaveProperty("lineNumber");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("withTimeProfiling respects custom interval", async () => {
|
|
29
|
+
const { profile } = await withTimeProfiling({ interval: 100 }, () => {
|
|
30
|
+
let sum = 0;
|
|
31
|
+
for (let i = 0; i < 1e6; i++) sum += Math.sqrt(i);
|
|
32
|
+
return sum;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Finer interval should produce more samples for the same work
|
|
36
|
+
expect(profile.samples!.length).toBeGreaterThan(0);
|
|
37
|
+
});
|