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,68 +1,53 @@
|
|
|
1
|
-
import type { CaseResult, MatrixResults } from "../BenchMatrix.ts";
|
|
2
|
-
import { injectDiffColumns, type ResultsMapper } from "../BenchmarkReport.ts";
|
|
3
|
-
import { totalProfileBytes } from "../heap-sample/HeapSampleReport.ts";
|
|
4
|
-
import { type GcStatsInfo, gcStatsSection } from "../StandardSections.ts";
|
|
5
1
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "../
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
key: string;
|
|
25
|
-
title: string;
|
|
26
|
-
groupTitle?: string; // optional column group header
|
|
27
|
-
extract: (caseResult: CaseResult) => unknown;
|
|
28
|
-
formatter?: (value: unknown) => string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Options for matrix report generation */
|
|
2
|
+
type ComparisonOptions,
|
|
3
|
+
computeDiffCI,
|
|
4
|
+
extractSectionValues,
|
|
5
|
+
findPrimaryColumn,
|
|
6
|
+
type ReportSection,
|
|
7
|
+
} from "../report/BenchmarkReport.ts";
|
|
8
|
+
import { truncate } from "../report/Formatters.ts";
|
|
9
|
+
import { runsSection, timeSection } from "../report/StandardSections.ts";
|
|
10
|
+
import { buildTable } from "../report/text/TableReport.ts";
|
|
11
|
+
import { sectionColumnGroups } from "../report/text/TextReport.ts";
|
|
12
|
+
import type { MeasuredResults } from "../runners/MeasuredResults.ts";
|
|
13
|
+
import type {
|
|
14
|
+
CaseResult,
|
|
15
|
+
MatrixResults,
|
|
16
|
+
VariantResult,
|
|
17
|
+
} from "./BenchMatrix.ts";
|
|
18
|
+
|
|
19
|
+
/** Options for {@link reportMatrixResults} */
|
|
32
20
|
export interface MatrixReportOptions {
|
|
33
|
-
|
|
34
|
-
sections?:
|
|
35
|
-
|
|
21
|
+
/** ReportSection sections (default: [timeSection, runsSection]) */
|
|
22
|
+
sections?: ReportSection[];
|
|
23
|
+
/** Custom title for the variant column (default: "variant") */
|
|
24
|
+
variantTitle?: string;
|
|
25
|
+
/** Comparison options (equivalence margin, batch trimming) */
|
|
26
|
+
comparison?: ComparisonOptions;
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
time: number;
|
|
42
|
-
samples: number;
|
|
43
|
-
diffCI?: DifferenceCI;
|
|
29
|
+
interface VariantCase {
|
|
30
|
+
variant: VariantResult;
|
|
31
|
+
cr: CaseResult;
|
|
44
32
|
}
|
|
45
33
|
|
|
46
|
-
|
|
34
|
+
type Row = Record<string, unknown> & { name: string };
|
|
35
|
+
|
|
36
|
+
const defaultSections: ReportSection[] = [timeSection, runsSection];
|
|
37
|
+
|
|
38
|
+
/** Format matrix results as text, with one table per case */
|
|
47
39
|
export function reportMatrixResults(
|
|
48
40
|
results: MatrixResults,
|
|
49
41
|
options?: MatrixReportOptions,
|
|
50
42
|
): string {
|
|
51
|
-
|
|
52
|
-
const header = `Matrix: ${results.name}`;
|
|
53
|
-
return [header, ...tables].join("\n\n");
|
|
54
|
-
}
|
|
43
|
+
if (results.variants.length === 0) return `Matrix: ${results.name}`;
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
function buildCaseTables(
|
|
58
|
-
results: MatrixResults,
|
|
59
|
-
options?: MatrixReportOptions,
|
|
60
|
-
): string[] {
|
|
61
|
-
if (results.variants.length === 0) return [];
|
|
62
|
-
|
|
63
|
-
// Get all case IDs from first variant (all variants have same cases)
|
|
45
|
+
// all variants have the same cases
|
|
64
46
|
const caseIds = results.variants[0].cases.map(c => c.caseId);
|
|
65
|
-
|
|
47
|
+
const tables = caseIds.map(caseId =>
|
|
48
|
+
buildCaseTable(results, caseId, options),
|
|
49
|
+
);
|
|
50
|
+
return [`Matrix: ${results.name}`, ...tables].join("\n\n");
|
|
66
51
|
}
|
|
67
52
|
|
|
68
53
|
/** Build table for a single case showing all variants */
|
|
@@ -71,170 +56,45 @@ function buildCaseTable(
|
|
|
71
56
|
caseId: string,
|
|
72
57
|
options?: MatrixReportOptions,
|
|
73
58
|
): string {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
if (options?.sections?.length) {
|
|
77
|
-
return buildSectionTable(results, caseId, options, caseTitle);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const rows = buildCaseRows(results, caseId, options?.extraColumns);
|
|
81
|
-
const hasBaseline = rows.some(r => r.diffCI);
|
|
82
|
-
const columns = buildColumns(hasBaseline, options);
|
|
83
|
-
|
|
84
|
-
const resultGroup: ResultGroup<MatrixReportRow> = { results: rows };
|
|
85
|
-
const table = buildTable(columns, [resultGroup]);
|
|
86
|
-
return `${caseTitle}\n${table}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Build table using ResultsMapper sections */
|
|
90
|
-
function buildSectionTable(
|
|
91
|
-
results: MatrixResults,
|
|
92
|
-
caseId: string,
|
|
93
|
-
options: MatrixReportOptions,
|
|
94
|
-
caseTitle: string,
|
|
95
|
-
): string {
|
|
96
|
-
const sections = options.sections!;
|
|
97
|
-
const variantTitle = options.variantTitle ?? "name";
|
|
98
|
-
|
|
99
|
-
const rows: Record<string, unknown>[] = [];
|
|
100
|
-
let hasBaseline = false;
|
|
101
|
-
|
|
102
|
-
for (const variant of results.variants) {
|
|
103
|
-
const caseResult = variant.cases.find(c => c.caseId === caseId);
|
|
104
|
-
if (!caseResult) continue;
|
|
105
|
-
|
|
106
|
-
const row: Record<string, unknown> = { name: truncate(variant.id, 25) };
|
|
107
|
-
|
|
108
|
-
for (const section of sections) {
|
|
109
|
-
Object.assign(
|
|
110
|
-
row,
|
|
111
|
-
section.extract(caseResult.measured, caseResult.metadata),
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (caseResult.baseline) {
|
|
116
|
-
hasBaseline = true;
|
|
117
|
-
const { samples: base } = caseResult.baseline;
|
|
118
|
-
row.diffCI = bootstrapDifferenceCI(base, caseResult.measured.samples);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
rows.push(row);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const columnGroups = buildSectionColumns(sections, variantTitle, hasBaseline);
|
|
125
|
-
const resultGroup: ResultGroup<Record<string, unknown>> = { results: rows };
|
|
126
|
-
const table = buildTable(columnGroups, [resultGroup]);
|
|
127
|
-
return `${caseTitle}\n${table}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Build column groups from ResultsMapper sections */
|
|
131
|
-
function buildSectionColumns(
|
|
132
|
-
sections: ResultsMapper[],
|
|
133
|
-
variantTitle: string,
|
|
134
|
-
hasBaseline: boolean,
|
|
135
|
-
): ColumnGroup<Record<string, unknown>>[] {
|
|
136
|
-
const nameCol: ColumnGroup<Record<string, unknown>> = {
|
|
137
|
-
columns: [{ key: "name", title: variantTitle }],
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const sectionColumns = sections.flatMap(s => s.columns());
|
|
141
|
-
const columnGroups = hasBaseline
|
|
142
|
-
? injectDiffColumns(sectionColumns)
|
|
143
|
-
: (sectionColumns as ColumnGroup<Record<string, unknown>>[]);
|
|
144
|
-
|
|
145
|
-
return [nameCol, ...columnGroups];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Build rows for all variants for a given case */
|
|
149
|
-
function buildCaseRows(
|
|
150
|
-
results: MatrixResults,
|
|
151
|
-
caseId: string,
|
|
152
|
-
extraColumns?: ExtraColumn[],
|
|
153
|
-
): MatrixReportRow[] {
|
|
154
|
-
return results.variants.flatMap(variant => {
|
|
155
|
-
const caseResult = variant.cases.find(c => c.caseId === caseId);
|
|
156
|
-
return caseResult ? [buildRow(variant.id, caseResult, extraColumns)] : [];
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Build a single row from case result */
|
|
161
|
-
function buildRow(
|
|
162
|
-
variantId: string,
|
|
163
|
-
caseResult: CaseResult,
|
|
164
|
-
extraColumns?: ExtraColumn[],
|
|
165
|
-
): MatrixReportRow {
|
|
166
|
-
const { measured, baseline } = caseResult;
|
|
167
|
-
const samples = measured.samples;
|
|
168
|
-
const time = measured.time?.avg ?? average(samples);
|
|
169
|
-
|
|
170
|
-
const row: MatrixReportRow = {
|
|
171
|
-
name: truncate(variantId, 25),
|
|
172
|
-
time,
|
|
173
|
-
samples: samples.length,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (baseline) {
|
|
177
|
-
row.diffCI = bootstrapDifferenceCI(baseline.samples, samples);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (extraColumns) {
|
|
181
|
-
for (const col of extraColumns) {
|
|
182
|
-
row[col.key] = col.extract(caseResult);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return row;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Build column configuration */
|
|
190
|
-
function buildColumns(
|
|
191
|
-
hasBaseline: boolean,
|
|
192
|
-
options?: MatrixReportOptions,
|
|
193
|
-
): ColumnGroup<MatrixReportRow>[] {
|
|
59
|
+
const title = formatCaseTitle(results, caseId);
|
|
60
|
+
const sections = options?.sections ?? defaultSections;
|
|
194
61
|
const variantTitle = options?.variantTitle ?? "variant";
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (extraColumns?.length) {
|
|
213
|
-
const byGroup = new Map<string | undefined, ExtraColumn[]>();
|
|
214
|
-
for (const col of extraColumns) {
|
|
215
|
-
const group = byGroup.get(col.groupTitle) ?? [];
|
|
216
|
-
group.push(col);
|
|
217
|
-
byGroup.set(col.groupTitle, group);
|
|
62
|
+
const primaryCol = findPrimaryColumn(sections);
|
|
63
|
+
|
|
64
|
+
const caseResults = collectCaseResults(results, caseId);
|
|
65
|
+
const shared = sharedBaseline(caseResults);
|
|
66
|
+
|
|
67
|
+
const rows: Row[] = caseResults.flatMap(({ variant, cr }) => {
|
|
68
|
+
const vals = extractSectionValues(cr.measured, sections, cr.metadata);
|
|
69
|
+
const row: Row = { name: truncate(variant.id, 25), ...vals };
|
|
70
|
+
if (cr.baseline && primaryCol?.statKind) {
|
|
71
|
+
const { statKind, higherIsBetter } = primaryCol;
|
|
72
|
+
row.diffCI = computeDiffCI(
|
|
73
|
+
cr.baseline,
|
|
74
|
+
cr.measured,
|
|
75
|
+
statKind,
|
|
76
|
+
options?.comparison,
|
|
77
|
+
higherIsBetter,
|
|
78
|
+
);
|
|
218
79
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
title: col.title,
|
|
225
|
-
formatter: col.formatter ?? String,
|
|
226
|
-
})),
|
|
80
|
+
const out: Row[] = [row];
|
|
81
|
+
if (cr.baseline && !shared)
|
|
82
|
+
out.push({
|
|
83
|
+
name: " \u21B3 baseline",
|
|
84
|
+
...extractSectionValues(cr.baseline, sections, cr.metadata),
|
|
227
85
|
});
|
|
228
|
-
|
|
229
|
-
}
|
|
86
|
+
return out;
|
|
87
|
+
});
|
|
230
88
|
|
|
231
|
-
|
|
232
|
-
|
|
89
|
+
if (shared)
|
|
90
|
+
rows.push({
|
|
91
|
+
name: "=> baseline",
|
|
92
|
+
...extractSectionValues(shared, sections),
|
|
93
|
+
});
|
|
233
94
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return formatDiffWithCI(value as DifferenceCI);
|
|
95
|
+
const hasDiff = rows.some(r => r.diffCI);
|
|
96
|
+
const cols = sectionColumnGroups(sections, hasDiff, variantTitle);
|
|
97
|
+
return `${title}\n${buildTable(cols, [{ results: rows }])}`;
|
|
238
98
|
}
|
|
239
99
|
|
|
240
100
|
/** Format case title with metadata if available */
|
|
@@ -242,49 +102,29 @@ function formatCaseTitle(results: MatrixResults, caseId: string): string {
|
|
|
242
102
|
const caseResult = results.variants[0]?.cases.find(c => c.caseId === caseId);
|
|
243
103
|
const metadata = caseResult?.metadata;
|
|
244
104
|
|
|
245
|
-
if (metadata
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
return caseId;
|
|
105
|
+
if (!metadata || Object.keys(metadata).length === 0) return caseId;
|
|
106
|
+
const meta = Object.entries(metadata)
|
|
107
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
108
|
+
.join(", ");
|
|
109
|
+
return `${caseId} (${meta})`;
|
|
252
110
|
}
|
|
253
111
|
|
|
254
|
-
/**
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
formatter: (v: unknown) => col.formatter?.(v) ?? "-",
|
|
264
|
-
}));
|
|
265
|
-
|
|
266
|
-
/** Format bytes with fallback to "-" for missing values */
|
|
267
|
-
function formatBytesOrDash(value: unknown): string {
|
|
268
|
-
return formatBytes(value) ?? "-";
|
|
112
|
+
/** Collect (variant, caseResult) pairs for a given caseId */
|
|
113
|
+
function collectCaseResults(
|
|
114
|
+
results: MatrixResults,
|
|
115
|
+
caseId: string,
|
|
116
|
+
): VariantCase[] {
|
|
117
|
+
return results.variants.flatMap(variant => {
|
|
118
|
+
const cr = variant.cases.find(c => c.caseId === caseId);
|
|
119
|
+
return cr ? [{ variant, cr }] : [];
|
|
120
|
+
});
|
|
269
121
|
}
|
|
270
122
|
|
|
271
|
-
/**
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/** Heap sampling total bytes column */
|
|
281
|
-
export const heapTotalColumn: ExtraColumn = {
|
|
282
|
-
key: "heapTotal",
|
|
283
|
-
title: "heap",
|
|
284
|
-
extract: r => {
|
|
285
|
-
const profile = r.measured.heapProfile;
|
|
286
|
-
if (!profile?.head) return undefined;
|
|
287
|
-
return totalProfileBytes(profile);
|
|
288
|
-
},
|
|
289
|
-
formatter: formatBytesOrDash,
|
|
290
|
-
};
|
|
123
|
+
/** @return shared baseline if all variants reference the same one (baselineVariant mode) */
|
|
124
|
+
function sharedBaseline(
|
|
125
|
+
caseResults: VariantCase[],
|
|
126
|
+
): MeasuredResults | undefined {
|
|
127
|
+
const baselines = caseResults.map(({ cr }) => cr.baseline).filter(Boolean);
|
|
128
|
+
if (baselines.length < 2) return undefined;
|
|
129
|
+
return baselines.every(b => b === baselines[0]) ? baselines[0] : undefined;
|
|
130
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import type { Variant } from "
|
|
3
|
+
import type { Variant } from "./BenchMatrix.ts";
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/** List variant IDs by scanning .ts files in a directory */
|
|
6
6
|
export async function discoverVariants(dirUrl: string): Promise<string[]> {
|
|
7
7
|
const dirPath = fileURLToPath(dirUrl);
|
|
8
8
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
@@ -12,7 +12,7 @@ export async function discoverVariants(dirUrl: string): Promise<string[]> {
|
|
|
12
12
|
.sort();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/** Import a variant module and return its run/setup exports as a Variant */
|
|
16
16
|
export async function loadVariant<T = unknown>(
|
|
17
17
|
dirUrl: string,
|
|
18
18
|
variantId: string,
|
|
@@ -22,7 +22,12 @@ export async function loadVariant<T = unknown>(
|
|
|
22
22
|
return extractVariant(module, variantId, moduleUrl);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
25
|
+
/** Resolve the import URL for a variant file */
|
|
26
|
+
export function variantModuleUrl(dirUrl: string, variantId: string): string {
|
|
27
|
+
return new URL(`${variantId}.ts`, dirUrl).href;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Validate and extract a Variant from a module's exports */
|
|
26
31
|
function extractVariant<T>(
|
|
27
32
|
module: Record<string, unknown>,
|
|
28
33
|
variantId: string,
|
|
@@ -39,8 +44,3 @@ function extractVariant<T>(
|
|
|
39
44
|
}
|
|
40
45
|
return { setup: setup as (data: T) => unknown, run: run as () => void };
|
|
41
46
|
}
|
|
42
|
-
|
|
43
|
-
/** Get module URL for a variant in a directory */
|
|
44
|
-
export function variantModuleUrl(dirUrl: string, variantId: string): string {
|
|
45
|
-
return new URL(`${variantId}.ts`, dirUrl).href;
|
|
46
|
-
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
instrumentOpts,
|
|
3
|
+
startInstruments,
|
|
4
|
+
stopInstruments,
|
|
5
|
+
} from "./BrowserCDP.ts";
|
|
6
|
+
import type { BrowserProfileResult, ProfileCtx } from "./BrowserProfiler.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Bench function mode: run window.__bench in a timed iteration loop.
|
|
10
|
+
*
|
|
11
|
+
* Simplified vs TimingRunner because it runs inside page.evaluate()
|
|
12
|
+
* where shared code, Node APIs, and V8 intrinsics are unavailable.
|
|
13
|
+
*
|
|
14
|
+
* Not feasible in browser page context:
|
|
15
|
+
* - heap tracking (no getHeapStatistics)
|
|
16
|
+
* - V8 opt status tracing (no %GetOptimizationStatus)
|
|
17
|
+
* - explicit GC or pause-for-compilation
|
|
18
|
+
*/
|
|
19
|
+
export async function runBenchLoop(
|
|
20
|
+
ctx: ProfileCtx,
|
|
21
|
+
): Promise<BrowserProfileResult> {
|
|
22
|
+
const { page, cdp, params, samplingInterval } = ctx;
|
|
23
|
+
const maxTime = params.maxTime ?? Number.MAX_SAFE_INTEGER;
|
|
24
|
+
const maxIter = params.maxIterations ?? Number.MAX_SAFE_INTEGER;
|
|
25
|
+
const opts = instrumentOpts(params, samplingInterval);
|
|
26
|
+
|
|
27
|
+
await startInstruments(cdp, opts);
|
|
28
|
+
|
|
29
|
+
const { samples, totalMs } = await page.evaluate(
|
|
30
|
+
async ({ maxTime, maxIter }) => {
|
|
31
|
+
const bench = (globalThis as any).__bench;
|
|
32
|
+
const estimated = Math.min(maxIter, Math.ceil(maxTime / 0.1));
|
|
33
|
+
const samples = new Array<number>(estimated);
|
|
34
|
+
let count = 0;
|
|
35
|
+
const startAll = performance.now();
|
|
36
|
+
const deadline = startAll + maxTime;
|
|
37
|
+
for (let i = 0; i < maxIter && performance.now() < deadline; i++) {
|
|
38
|
+
const t0 = performance.now();
|
|
39
|
+
await bench();
|
|
40
|
+
samples[count++] = performance.now() - t0;
|
|
41
|
+
}
|
|
42
|
+
samples.length = count;
|
|
43
|
+
return { samples, totalMs: performance.now() - startAll };
|
|
44
|
+
},
|
|
45
|
+
{ maxTime, maxIter },
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const collected = await stopInstruments(cdp, opts);
|
|
49
|
+
|
|
50
|
+
return { samples, wallTimeMs: totalMs, ...collected };
|
|
51
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { GcStats } from "../../runners/GcStats.ts";
|
|
2
|
+
import type { CoverageData, ScriptCoverage } from "../node/CoverageTypes.ts";
|
|
3
|
+
import type { HeapProfile } from "../node/HeapSampler.ts";
|
|
4
|
+
import type { TimeProfile } from "../node/TimeSampler.ts";
|
|
5
|
+
import { browserGcStats } from "./BrowserGcStats.ts";
|
|
6
|
+
import type { CdpClient } from "./CdpClient.ts";
|
|
7
|
+
import type { TraceEvent } from "./ChromeTraceEvent.ts";
|
|
8
|
+
|
|
9
|
+
/** Options controlling which CDP instruments (heap, CPU, coverage) to enable. */
|
|
10
|
+
export interface InstrumentOpts {
|
|
11
|
+
alloc: boolean;
|
|
12
|
+
profile: boolean;
|
|
13
|
+
callCounts: boolean;
|
|
14
|
+
samplingInterval: number;
|
|
15
|
+
profileInterval?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Build InstrumentOpts from profile params and heap sampling interval. */
|
|
19
|
+
export function instrumentOpts(
|
|
20
|
+
params: {
|
|
21
|
+
alloc?: boolean;
|
|
22
|
+
profile?: boolean;
|
|
23
|
+
callCounts?: boolean;
|
|
24
|
+
profileInterval?: number;
|
|
25
|
+
},
|
|
26
|
+
samplingInterval: number,
|
|
27
|
+
): InstrumentOpts {
|
|
28
|
+
const {
|
|
29
|
+
alloc = false,
|
|
30
|
+
profile = false,
|
|
31
|
+
callCounts = false,
|
|
32
|
+
profileInterval,
|
|
33
|
+
} = params;
|
|
34
|
+
return { alloc, profile, callCounts, samplingInterval, profileInterval };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Start CDP GC tracing; returns the mutable array that collects trace events. */
|
|
38
|
+
export async function startGcTracing(cdp: CdpClient): Promise<TraceEvent[]> {
|
|
39
|
+
const events: TraceEvent[] = [];
|
|
40
|
+
cdp.on("Tracing.dataCollected", ({ value }) => {
|
|
41
|
+
events.push(...(value as unknown as TraceEvent[]));
|
|
42
|
+
});
|
|
43
|
+
await cdp.send("Tracing.start", {
|
|
44
|
+
traceConfig: { includedCategories: ["v8", "v8.gc"] },
|
|
45
|
+
});
|
|
46
|
+
return events;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** End CDP tracing and aggregate collected events into GcStats. */
|
|
50
|
+
export async function collectTracing(
|
|
51
|
+
cdp: CdpClient,
|
|
52
|
+
traceEvents: TraceEvent[],
|
|
53
|
+
): Promise<GcStats> {
|
|
54
|
+
const done = new Promise<void>(r =>
|
|
55
|
+
cdp.once("Tracing.tracingComplete", () => r()),
|
|
56
|
+
);
|
|
57
|
+
await cdp.send("Tracing.end");
|
|
58
|
+
await done;
|
|
59
|
+
return browserGcStats(traceEvents);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Start CDP Profiler for CPU time sampling (caller manages Profiler.enable/disable) */
|
|
63
|
+
export async function startTimeProfiling(
|
|
64
|
+
cdp: CdpClient,
|
|
65
|
+
interval?: number,
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
if (interval) await cdp.send("Profiler.setSamplingInterval", { interval });
|
|
68
|
+
await cdp.send("Profiler.start");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Stop CDP CPU sampling and return the profile. */
|
|
72
|
+
export async function stopTimeProfiling(cdp: CdpClient): Promise<TimeProfile> {
|
|
73
|
+
const { profile } = await cdp.send("Profiler.stop");
|
|
74
|
+
return profile as unknown as TimeProfile;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Start precise coverage (caller manages Profiler.enable/disable). */
|
|
78
|
+
export async function startCoverageCollection(cdp: CdpClient): Promise<void> {
|
|
79
|
+
await cdp.send("Profiler.startPreciseCoverage", {
|
|
80
|
+
callCount: true,
|
|
81
|
+
detailed: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Collect precise coverage, filtering out browser-internal scripts. */
|
|
86
|
+
export async function collectCoverage(cdp: CdpClient): Promise<CoverageData> {
|
|
87
|
+
const { result } = await cdp.send("Profiler.takePreciseCoverage");
|
|
88
|
+
await cdp.send("Profiler.stopPreciseCoverage");
|
|
89
|
+
const scripts = (result as unknown as ScriptCoverage[]).filter(isPageScript);
|
|
90
|
+
return { scripts };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Stop active instruments and return collected profiles/coverage. */
|
|
94
|
+
export async function stopInstruments(
|
|
95
|
+
cdp: CdpClient,
|
|
96
|
+
opts: InstrumentOpts,
|
|
97
|
+
): Promise<{
|
|
98
|
+
heapProfile?: HeapProfile;
|
|
99
|
+
timeProfile?: TimeProfile;
|
|
100
|
+
coverage?: CoverageData;
|
|
101
|
+
}> {
|
|
102
|
+
const heapProfile = opts.alloc
|
|
103
|
+
? ((await cdp.send("HeapProfiler.stopSampling")).profile as HeapProfile)
|
|
104
|
+
: undefined;
|
|
105
|
+
const timeProfile = opts.profile ? await stopTimeProfiling(cdp) : undefined;
|
|
106
|
+
const coverage = opts.callCounts ? await collectCoverage(cdp) : undefined;
|
|
107
|
+
if (opts.profile || opts.callCounts) await cdp.send("Profiler.disable");
|
|
108
|
+
return { heapProfile, timeProfile, coverage };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Start requested CDP instruments (heap, CPU, coverage). */
|
|
112
|
+
export async function startInstruments(
|
|
113
|
+
cdp: CdpClient,
|
|
114
|
+
opts: InstrumentOpts,
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
if (opts.alloc) {
|
|
117
|
+
await cdp.send("HeapProfiler.startSampling", {
|
|
118
|
+
samplingInterval: opts.samplingInterval,
|
|
119
|
+
includeObjectsCollectedByMajorGC: true,
|
|
120
|
+
includeObjectsCollectedByMinorGC: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (opts.profile || opts.callCounts) await cdp.send("Profiler.enable");
|
|
124
|
+
if (opts.profile) await startTimeProfiling(cdp, opts.profileInterval);
|
|
125
|
+
if (opts.callCounts) await startCoverageCollection(cdp);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Exclude chrome:// and devtools:// internal scripts. */
|
|
129
|
+
function isPageScript(s: ScriptCoverage): boolean {
|
|
130
|
+
return (
|
|
131
|
+
!!s.url && !s.url.startsWith("chrome") && !s.url.startsWith("devtools")
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateGcStats,
|
|
3
|
+
type GcEvent,
|
|
4
|
+
type GcStats,
|
|
5
|
+
} from "../../runners/GcStats.ts";
|
|
6
|
+
import type { TraceEvent } from "./ChromeTraceEvent.ts";
|
|
7
|
+
|
|
8
|
+
/** Convert MinorGC/MajorGC trace events into GcEvent[]. */
|
|
9
|
+
export function parseGcTraceEvents(traceEvents: TraceEvent[]): GcEvent[] {
|
|
10
|
+
return traceEvents
|
|
11
|
+
.filter(e => e.ph === "X" && gcType(e.name))
|
|
12
|
+
.map(e => ({
|
|
13
|
+
type: gcType(e.name)!,
|
|
14
|
+
pauseMs: (e.dur ?? 0) / 1000,
|
|
15
|
+
collected: Math.max(
|
|
16
|
+
0,
|
|
17
|
+
Number(e.args?.usedHeapSizeBefore ?? 0) -
|
|
18
|
+
Number(e.args?.usedHeapSizeAfter ?? 0),
|
|
19
|
+
),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Parse and aggregate CDP trace events into GcStats. */
|
|
24
|
+
export function browserGcStats(traceEvents: TraceEvent[]): GcStats {
|
|
25
|
+
return aggregateGcStats(parseGcTraceEvents(traceEvents));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Map CDP event names (MinorGC/MajorGC) to GcEvent type. */
|
|
29
|
+
function gcType(name: string): GcEvent["type"] | undefined {
|
|
30
|
+
if (name === "MinorGC") return "scavenge";
|
|
31
|
+
if (name === "MajorGC") return "mark-compact";
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|