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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { cliDefaults } from "../cli/CliArgs.ts";
|
|
2
|
+
import type { CoverageData } from "../profiling/node/CoverageTypes.ts";
|
|
3
|
+
import {
|
|
4
|
+
filterSites,
|
|
5
|
+
flattenProfile,
|
|
6
|
+
totalBytes,
|
|
7
|
+
} from "../profiling/node/HeapSampleReport.ts";
|
|
8
|
+
import type { HeapProfile } from "../profiling/node/HeapSampler.ts";
|
|
9
|
+
import { resolveProfile } from "../profiling/node/ResolvedProfile.ts";
|
|
10
|
+
import type { MeasuredResults } from "../runners/MeasuredResults.ts";
|
|
11
|
+
import type { DifferenceCI } from "../stats/StatisticalUtils.ts";
|
|
12
|
+
import type {
|
|
13
|
+
BenchmarkEntry,
|
|
14
|
+
BenchmarkGroup,
|
|
15
|
+
CoverageSummary,
|
|
16
|
+
HeapSummary,
|
|
17
|
+
ReportData,
|
|
18
|
+
ViewerSection,
|
|
19
|
+
} from "../viewer/ReportData.ts";
|
|
20
|
+
import {
|
|
21
|
+
type BenchmarkReport,
|
|
22
|
+
type ComparisonOptions,
|
|
23
|
+
hasField,
|
|
24
|
+
type ReportGroup,
|
|
25
|
+
type ReportSection,
|
|
26
|
+
type UnknownRecord,
|
|
27
|
+
} from "./BenchmarkReport.ts";
|
|
28
|
+
import { gcStatsSection } from "./GcSections.ts";
|
|
29
|
+
import type { GitVersion } from "./GitUtils.ts";
|
|
30
|
+
import {
|
|
31
|
+
buildTimeSection,
|
|
32
|
+
optSection,
|
|
33
|
+
runsSection,
|
|
34
|
+
} from "./StandardSections.ts";
|
|
35
|
+
import {
|
|
36
|
+
buildViewerSections,
|
|
37
|
+
hasLowBatchCount,
|
|
38
|
+
isSingleBatch,
|
|
39
|
+
minBatches,
|
|
40
|
+
} from "./ViewerSections.ts";
|
|
41
|
+
|
|
42
|
+
/** Options for prepareHtmlData: report sections, git versions, and CLI args */
|
|
43
|
+
export interface PrepareHtmlOptions extends ComparisonOptions {
|
|
44
|
+
cliArgs?: Record<string, unknown>;
|
|
45
|
+
sections?: ReportSection[];
|
|
46
|
+
currentVersion?: GitVersion;
|
|
47
|
+
baselineVersion?: GitVersion;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Context shared across reports in a group */
|
|
51
|
+
interface GroupContext {
|
|
52
|
+
baseM?: MeasuredResults;
|
|
53
|
+
baseMeta?: UnknownRecord;
|
|
54
|
+
sections?: ReportSection[];
|
|
55
|
+
comparison?: ComparisonOptions;
|
|
56
|
+
lowBatches: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Convert benchmark results into a ReportData payload for the HTML viewer */
|
|
60
|
+
export function prepareHtmlData(
|
|
61
|
+
groups: ReportGroup[],
|
|
62
|
+
options: PrepareHtmlOptions,
|
|
63
|
+
): ReportData {
|
|
64
|
+
const { cliArgs, currentVersion, baselineVersion, equivMargin, noBatchTrim } =
|
|
65
|
+
options;
|
|
66
|
+
const comparison: ComparisonOptions = { equivMargin, noBatchTrim };
|
|
67
|
+
const sections = options.sections ?? defaultSections(groups, cliArgs);
|
|
68
|
+
return {
|
|
69
|
+
groups: groups.map(g => prepareGroupData(g, sections, comparison)),
|
|
70
|
+
metadata: {
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
bencherVersion: process.env.npm_package_version || "unknown",
|
|
73
|
+
cliArgs,
|
|
74
|
+
cliDefaults,
|
|
75
|
+
gcTrackingEnabled: cliArgs?.["gc-stats"] === true,
|
|
76
|
+
currentVersion,
|
|
77
|
+
baselineVersion,
|
|
78
|
+
environment: {
|
|
79
|
+
node: process.version,
|
|
80
|
+
platform: process.platform,
|
|
81
|
+
arch: process.arch,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Build default sections when caller doesn't provide custom ones */
|
|
88
|
+
function defaultSections(
|
|
89
|
+
groups: ReportGroup[],
|
|
90
|
+
cliArgs?: Record<string, unknown>,
|
|
91
|
+
): ReportSection[] {
|
|
92
|
+
const hasGc = cliArgs?.["gc-stats"] === true;
|
|
93
|
+
const hasOpt = hasField(groups, "optStatus");
|
|
94
|
+
const stats = typeof cliArgs?.stats === "string" ? cliArgs.stats : undefined;
|
|
95
|
+
return [
|
|
96
|
+
buildTimeSection(stats),
|
|
97
|
+
hasGc ? gcStatsSection : undefined,
|
|
98
|
+
hasOpt ? optSection : undefined,
|
|
99
|
+
runsSection,
|
|
100
|
+
].filter((s): s is ReportSection => s !== undefined);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @return group data with structured ViewerSections and bootstrap CIs */
|
|
104
|
+
function prepareGroupData(
|
|
105
|
+
group: ReportGroup,
|
|
106
|
+
sections?: ReportSection[],
|
|
107
|
+
comparison?: ComparisonOptions,
|
|
108
|
+
): BenchmarkGroup {
|
|
109
|
+
const base = group.baseline;
|
|
110
|
+
const baseM = base?.measuredResults;
|
|
111
|
+
const baseline = base
|
|
112
|
+
? { ...prepareBenchmarkData(base), comparisonCI: undefined }
|
|
113
|
+
: undefined;
|
|
114
|
+
const curM = group.reports[0]?.measuredResults;
|
|
115
|
+
const singleBatch = isSingleBatch(baseM, curM);
|
|
116
|
+
const lowBatches = hasLowBatchCount(baseM, curM);
|
|
117
|
+
const baseMeta = base?.metadata;
|
|
118
|
+
const ctx: GroupContext = {
|
|
119
|
+
baseM,
|
|
120
|
+
baseMeta,
|
|
121
|
+
sections,
|
|
122
|
+
comparison,
|
|
123
|
+
lowBatches,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
name: group.name,
|
|
128
|
+
baseline,
|
|
129
|
+
warnings: buildWarnings(singleBatch, lowBatches),
|
|
130
|
+
benchmarks: group.reports.map(r => prepareReportEntry(r, ctx)),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @return benchmark data with samples, stats, and profiling summaries */
|
|
135
|
+
function prepareBenchmarkData(report: {
|
|
136
|
+
name: string;
|
|
137
|
+
measuredResults: MeasuredResults;
|
|
138
|
+
metadata?: UnknownRecord;
|
|
139
|
+
}): Omit<BenchmarkEntry, "comparisonCI" | "sections"> {
|
|
140
|
+
const { measuredResults: m, name } = report;
|
|
141
|
+
return {
|
|
142
|
+
name,
|
|
143
|
+
samples: m.samples,
|
|
144
|
+
warmupSamples: m.warmupSamples,
|
|
145
|
+
allocationSamples: m.allocationSamples,
|
|
146
|
+
heapSamples: m.heapSamples,
|
|
147
|
+
gcEvents: m.nodeGcTime?.events,
|
|
148
|
+
optSamples: m.optSamples,
|
|
149
|
+
pausePoints: m.pausePoints,
|
|
150
|
+
batchOffsets: m.batchOffsets,
|
|
151
|
+
stats: m.time,
|
|
152
|
+
heapSize: m.heapSize,
|
|
153
|
+
totalTime: m.totalTime,
|
|
154
|
+
heapSummary: m.heapProfile ? summarizeHeap(m.heapProfile) : undefined,
|
|
155
|
+
coverageSummary: m.coverage ? summarizeCoverage(m.coverage) : undefined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildWarnings(
|
|
160
|
+
singleBatch: boolean,
|
|
161
|
+
lowBatches: boolean,
|
|
162
|
+
): string[] | undefined {
|
|
163
|
+
const parts: string[] = [];
|
|
164
|
+
const singleMsg =
|
|
165
|
+
"Confidence intervals may be too narrow (single batch). Use --batches for more accurate intervals.";
|
|
166
|
+
if (singleBatch) parts.push(singleMsg);
|
|
167
|
+
if (lowBatches)
|
|
168
|
+
parts.push(
|
|
169
|
+
`Too few batches for reliable comparison (need ${minBatches}+).`,
|
|
170
|
+
);
|
|
171
|
+
return parts.length ? parts : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @return a single benchmark entry with sections and comparison CI */
|
|
175
|
+
function prepareReportEntry(
|
|
176
|
+
report: BenchmarkReport,
|
|
177
|
+
ctx: GroupContext,
|
|
178
|
+
): BenchmarkEntry {
|
|
179
|
+
const m = report.measuredResults;
|
|
180
|
+
const sectionCtx = {
|
|
181
|
+
current: m,
|
|
182
|
+
baseline: ctx.baseM,
|
|
183
|
+
currentMeta: report.metadata,
|
|
184
|
+
baselineMeta: ctx.baseMeta,
|
|
185
|
+
comparison: ctx.comparison,
|
|
186
|
+
};
|
|
187
|
+
const sections = ctx.sections
|
|
188
|
+
? buildViewerSections(ctx.sections, sectionCtx)
|
|
189
|
+
: undefined;
|
|
190
|
+
// Primary CI comes from the first primary row's comparisonCI (avoids duplicate bootstrap)
|
|
191
|
+
const comparisonCI = findPrimarySectionCI(sections);
|
|
192
|
+
return { ...prepareBenchmarkData(report), sections, comparisonCI };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Compute heap allocation summary from profile */
|
|
196
|
+
function summarizeHeap(profile: HeapProfile): HeapSummary {
|
|
197
|
+
const resolved = resolveProfile(profile);
|
|
198
|
+
const userSites = filterSites(flattenProfile(resolved));
|
|
199
|
+
return { totalBytes: resolved.totalBytes, userBytes: totalBytes(userSites) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Compute coverage summary from V8 coverage data */
|
|
203
|
+
function summarizeCoverage(coverage: CoverageData): CoverageSummary {
|
|
204
|
+
const fns = coverage.scripts.flatMap(s => s.functions);
|
|
205
|
+
const called = fns.filter(
|
|
206
|
+
fn => fn.ranges.length > 0 && fn.ranges[0].count > 0,
|
|
207
|
+
);
|
|
208
|
+
const totalCalls = called.reduce((sum, fn) => sum + fn.ranges[0].count, 0);
|
|
209
|
+
return { functionCount: called.length, totalCalls };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Extract the comparison CI from the first primary row across all sections */
|
|
213
|
+
function findPrimarySectionCI(
|
|
214
|
+
sections: ViewerSection[] | undefined,
|
|
215
|
+
): DifferenceCI | undefined {
|
|
216
|
+
if (!sections) return undefined;
|
|
217
|
+
for (const section of sections) {
|
|
218
|
+
for (const row of section.rows) {
|
|
219
|
+
if (row.primary && row.comparisonCI) return row.comparisonCI;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { StatKind } from "../stats/StatisticalUtils.ts";
|
|
2
|
+
|
|
3
|
+
/** Parsed spec for one timing column selected via --stats. */
|
|
4
|
+
export interface StatSpec {
|
|
5
|
+
key: string;
|
|
6
|
+
title: string;
|
|
7
|
+
statKind: StatKind;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Parse --stats into column specs. Throws on empty/invalid tokens. */
|
|
11
|
+
export function parseStatsArg(stats: string): StatSpec[] {
|
|
12
|
+
const tokens = stats
|
|
13
|
+
.split(",")
|
|
14
|
+
.map(t => t.trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
if (tokens.length === 0) {
|
|
17
|
+
throw new Error("--stats must list at least one column");
|
|
18
|
+
}
|
|
19
|
+
const seen = new Set<string>();
|
|
20
|
+
const specs: StatSpec[] = [];
|
|
21
|
+
for (const token of tokens) {
|
|
22
|
+
const spec = parseStatToken(token);
|
|
23
|
+
if (seen.has(spec.key)) continue;
|
|
24
|
+
seen.add(spec.key);
|
|
25
|
+
specs.push(spec);
|
|
26
|
+
}
|
|
27
|
+
return specs;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @return stat spec for a single --stats token. Throws on invalid input. */
|
|
31
|
+
function parseStatToken(token: string): StatSpec {
|
|
32
|
+
const lower = token.toLowerCase();
|
|
33
|
+
if (lower === "mean" || lower === "avg") {
|
|
34
|
+
return { key: "mean", title: "mean", statKind: "mean" };
|
|
35
|
+
}
|
|
36
|
+
if (lower === "median") {
|
|
37
|
+
return { key: "p50", title: "p50", statKind: { percentile: 0.5 } };
|
|
38
|
+
}
|
|
39
|
+
if (lower === "min") {
|
|
40
|
+
return { key: "min", title: "min", statKind: "min" };
|
|
41
|
+
}
|
|
42
|
+
if (lower === "max") {
|
|
43
|
+
return { key: "max", title: "max", statKind: "max" };
|
|
44
|
+
}
|
|
45
|
+
const m = lower.match(/^p(\d+)$/);
|
|
46
|
+
if (m) return parsePercentileToken(token, m[1]);
|
|
47
|
+
throw new Error(
|
|
48
|
+
`invalid --stats token "${token}": expected mean, median, min, max, or p<N> (e.g. p50, p99, p999)`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @return spec for a p<N> token, enforcing the 2-digit minimum and 9-prefix rule. */
|
|
53
|
+
function parsePercentileToken(token: string, digits: string): StatSpec {
|
|
54
|
+
if (digits.length < 2) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`invalid --stats token "${token}": percentile needs at least 2 digits (e.g. p05, p50, p99, p999)`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
// 3+ digit tokens express sub-percentile precision (p999 = 99.9%,
|
|
60
|
+
// p9999 = 99.99%). Require leading 9 so p100/p500 don't silently
|
|
61
|
+
// map to 10%/50% — use 2-digit p10/p50 for those.
|
|
62
|
+
if (digits.length > 2 && digits[0] !== "9") {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`invalid --stats token "${token}": percentiles with 3+ digits must start with 9 (e.g. p999, p9999); otherwise use 2-digit form (e.g. p50)`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const q = Number(digits) / 10 ** digits.length;
|
|
68
|
+
return {
|
|
69
|
+
key: `p${digits}`,
|
|
70
|
+
title: `p${digits}`,
|
|
71
|
+
statKind: { percentile: q },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MeasuredResults,
|
|
3
|
+
OptStatusInfo,
|
|
4
|
+
} from "../runners/MeasuredResults.ts";
|
|
5
|
+
import { isBootstrappable } from "../stats/StatisticalUtils.ts";
|
|
6
|
+
import type { ReportSection } from "./BenchmarkReport.ts";
|
|
7
|
+
import { formatConvergence, timeMs } from "./Formatters.ts";
|
|
8
|
+
import { gcSections } from "./GcSections.ts";
|
|
9
|
+
import { parseStatsArg } from "./ParseStats.ts";
|
|
10
|
+
|
|
11
|
+
/** Default timing section: mean, p50, p99. */
|
|
12
|
+
export const timeSection: ReportSection = buildTimeSection();
|
|
13
|
+
|
|
14
|
+
/** Report section: number of sample iterations. */
|
|
15
|
+
export const runsSection: ReportSection = {
|
|
16
|
+
title: "",
|
|
17
|
+
columns: [
|
|
18
|
+
{
|
|
19
|
+
key: "runs",
|
|
20
|
+
title: "runs",
|
|
21
|
+
formatter: v => String(v),
|
|
22
|
+
value: (r: MeasuredResults) => r.samples.length,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Report section: total sampling duration. */
|
|
28
|
+
export const totalTimeSection: ReportSection = {
|
|
29
|
+
title: "",
|
|
30
|
+
columns: [
|
|
31
|
+
{
|
|
32
|
+
key: "totalTime",
|
|
33
|
+
title: "time",
|
|
34
|
+
formatter: formatTotalTime,
|
|
35
|
+
value: (r: MeasuredResults) => r.totalTime,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Report sections: timing stats and convergence for adaptive mode. */
|
|
41
|
+
export const adaptiveSections: ReportSection[] = [
|
|
42
|
+
{
|
|
43
|
+
title: "time",
|
|
44
|
+
columns: [
|
|
45
|
+
{
|
|
46
|
+
key: "median",
|
|
47
|
+
title: "median",
|
|
48
|
+
formatter: timeMs,
|
|
49
|
+
comparable: true,
|
|
50
|
+
statKind: { percentile: 0.5 },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "mean",
|
|
54
|
+
title: "mean",
|
|
55
|
+
formatter: timeMs,
|
|
56
|
+
comparable: true,
|
|
57
|
+
statKind: "mean",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "p99",
|
|
61
|
+
title: "p99",
|
|
62
|
+
formatter: timeMs,
|
|
63
|
+
statKind: { percentile: 0.99 },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
title: "",
|
|
69
|
+
columns: [
|
|
70
|
+
{
|
|
71
|
+
key: "convergence",
|
|
72
|
+
title: "conv%",
|
|
73
|
+
formatter: formatConvergence,
|
|
74
|
+
value: (r: MeasuredResults) => r.convergence?.confidence,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/** Report section: V8 optimization tier distribution and deopt count. */
|
|
81
|
+
export const optSection: ReportSection = {
|
|
82
|
+
title: "v8 opt",
|
|
83
|
+
columns: [
|
|
84
|
+
{
|
|
85
|
+
key: "tiers",
|
|
86
|
+
title: "tiers",
|
|
87
|
+
formatter: v => (typeof v === "string" ? v : ""),
|
|
88
|
+
value: (r: MeasuredResults) => {
|
|
89
|
+
const opt = r.optStatus;
|
|
90
|
+
return opt ? formatTierSummary(opt) : undefined;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "deopt",
|
|
95
|
+
title: "deopt",
|
|
96
|
+
formatter: v => (typeof v === "number" ? String(v) : ""),
|
|
97
|
+
value: (r: MeasuredResults) => {
|
|
98
|
+
const opt = r.optStatus;
|
|
99
|
+
return opt && opt.deoptCount > 0 ? opt.deoptCount : undefined;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** Build a time section with user-chosen percentile/stat columns. */
|
|
106
|
+
export function buildTimeSection(stats = "mean,p50,p99"): ReportSection {
|
|
107
|
+
const specs = parseStatsArg(stats);
|
|
108
|
+
return {
|
|
109
|
+
title: "time",
|
|
110
|
+
columns: specs.map(s => ({
|
|
111
|
+
key: s.key,
|
|
112
|
+
title: s.title,
|
|
113
|
+
formatter: timeMs,
|
|
114
|
+
comparable: isBootstrappable(s.statKind),
|
|
115
|
+
statKind: s.statKind,
|
|
116
|
+
})),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Format V8 tier distribution sorted by count (e.g. "turbofan:85% sparkplug:15%"). */
|
|
121
|
+
export function formatTierSummary(
|
|
122
|
+
opt: OptStatusInfo,
|
|
123
|
+
sep = ":",
|
|
124
|
+
glue = " ",
|
|
125
|
+
): string {
|
|
126
|
+
const tiers = Object.entries(opt.byTier);
|
|
127
|
+
const total = tiers.reduce((s, [, t]) => s + t.count, 0);
|
|
128
|
+
const pct = (n: number) => `${((n / total) * 100).toFixed(0)}%`;
|
|
129
|
+
return tiers
|
|
130
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
131
|
+
.map(([name, t]) => `${name}${sep}${pct(t.count)}`)
|
|
132
|
+
.join(glue);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** @return default report sections from CLI flags (GC stats if enabled, plus run count). */
|
|
136
|
+
export function buildGenericSections(args: {
|
|
137
|
+
"gc-stats"?: boolean;
|
|
138
|
+
alloc?: boolean;
|
|
139
|
+
}): ReportSection[] {
|
|
140
|
+
return [...gcSections(args), runsSection];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Format total time; brackets indicate >= 30s. */
|
|
144
|
+
function formatTotalTime(v: unknown): string {
|
|
145
|
+
if (typeof v !== "number") return "";
|
|
146
|
+
return v >= 30 ? `[${v.toFixed(1)}s]` : `${v.toFixed(1)}s`;
|
|
147
|
+
}
|