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,271 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type BrowserServer,
|
|
3
|
-
type CDPSession,
|
|
4
|
-
chromium,
|
|
5
|
-
type Page,
|
|
6
|
-
} from "playwright";
|
|
7
|
-
import type {
|
|
8
|
-
HeapProfile,
|
|
9
|
-
HeapSampleOptions,
|
|
10
|
-
} from "../heap-sample/HeapSampler.ts";
|
|
11
|
-
import type { GcStats } from "../runners/GcStats.ts";
|
|
12
|
-
import { browserGcStats, type TraceEvent } from "./BrowserGcStats.ts";
|
|
13
|
-
|
|
14
|
-
export interface BrowserProfileParams {
|
|
15
|
-
url: string;
|
|
16
|
-
heapSample?: boolean;
|
|
17
|
-
heapOptions?: HeapSampleOptions;
|
|
18
|
-
gcStats?: boolean;
|
|
19
|
-
headless?: boolean;
|
|
20
|
-
chromeArgs?: string[];
|
|
21
|
-
timeout?: number; // seconds
|
|
22
|
-
maxTime?: number; // ms, bench function iteration time limit
|
|
23
|
-
maxIterations?: number; // exact iteration count (bench function mode)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface BrowserProfileResult {
|
|
27
|
-
heapProfile?: HeapProfile;
|
|
28
|
-
gcStats?: GcStats;
|
|
29
|
-
/** Wall-clock ms (lap mode: first start to done, bench function: total loop) */
|
|
30
|
-
wallTimeMs?: number;
|
|
31
|
-
/** Per-iteration timing samples (ms) from bench function or lap mode */
|
|
32
|
-
samples?: number[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface LapModeHandle {
|
|
36
|
-
promise: Promise<BrowserProfileResult>;
|
|
37
|
-
cancel: () => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Run browser benchmark, auto-detecting page API mode.
|
|
41
|
-
* Bench function (window.__bench): CLI controls iteration and timing.
|
|
42
|
-
* Lap mode (__start/__lap/__done): page controls the measured region. */
|
|
43
|
-
export async function profileBrowser(
|
|
44
|
-
params: BrowserProfileParams,
|
|
45
|
-
): Promise<BrowserProfileResult> {
|
|
46
|
-
const { url, headless = true, chromeArgs, timeout = 60 } = params;
|
|
47
|
-
const { gcStats: collectGc } = params;
|
|
48
|
-
const { samplingInterval = 32768 } = params.heapOptions ?? {};
|
|
49
|
-
|
|
50
|
-
const server = await chromium.launchServer({ headless, args: chromeArgs });
|
|
51
|
-
pipeChromeOutput(server);
|
|
52
|
-
const browser = await chromium.connect(server.wsEndpoint());
|
|
53
|
-
try {
|
|
54
|
-
const page = await browser.newPage();
|
|
55
|
-
page.setDefaultTimeout(timeout * 1000);
|
|
56
|
-
const cdp = await page.context().newCDPSession(page);
|
|
57
|
-
|
|
58
|
-
const pageErrors: string[] = [];
|
|
59
|
-
page.on("pageerror", err => pageErrors.push(err.message));
|
|
60
|
-
|
|
61
|
-
const traceEvents = collectGc ? await startGcTracing(cdp) : [];
|
|
62
|
-
const lapMode = await setupLapMode(
|
|
63
|
-
page,
|
|
64
|
-
cdp,
|
|
65
|
-
params,
|
|
66
|
-
samplingInterval,
|
|
67
|
-
timeout,
|
|
68
|
-
pageErrors,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
await page.goto(url, { waitUntil: "load" });
|
|
72
|
-
const hasBench = await page.evaluate(
|
|
73
|
-
() => typeof (globalThis as any).__bench === "function",
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
let result: BrowserProfileResult;
|
|
77
|
-
if (hasBench) {
|
|
78
|
-
lapMode.cancel();
|
|
79
|
-
lapMode.promise.catch(() => {}); // suppress unused rejection
|
|
80
|
-
result = await runBenchLoop(page, cdp, params, samplingInterval);
|
|
81
|
-
} else {
|
|
82
|
-
result = await lapMode.promise;
|
|
83
|
-
lapMode.cancel();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (collectGc) {
|
|
87
|
-
result = { ...result, gcStats: await collectTracing(cdp, traceEvents) };
|
|
88
|
-
}
|
|
89
|
-
return result;
|
|
90
|
-
} finally {
|
|
91
|
-
await browser.close();
|
|
92
|
-
await server.close();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Inject __start/__lap as in-page functions, expose __done for results collection.
|
|
97
|
-
* __start/__lap are pure in-page (zero CDP overhead). First __start() triggers
|
|
98
|
-
* instrument start. __done() stops instruments and collects timing data. */
|
|
99
|
-
async function setupLapMode(
|
|
100
|
-
page: Page,
|
|
101
|
-
cdp: CDPSession,
|
|
102
|
-
params: BrowserProfileParams,
|
|
103
|
-
samplingInterval: number,
|
|
104
|
-
timeout: number,
|
|
105
|
-
pageErrors: string[],
|
|
106
|
-
): Promise<LapModeHandle> {
|
|
107
|
-
const { heapSample } = params;
|
|
108
|
-
const { promise, resolve, reject } =
|
|
109
|
-
Promise.withResolvers<BrowserProfileResult>();
|
|
110
|
-
let instrumentsStarted = false;
|
|
111
|
-
|
|
112
|
-
await page.exposeFunction("__benchInstrumentStart", async () => {
|
|
113
|
-
if (instrumentsStarted) return;
|
|
114
|
-
instrumentsStarted = true;
|
|
115
|
-
if (heapSample) {
|
|
116
|
-
await cdp.send(
|
|
117
|
-
"HeapProfiler.startSampling",
|
|
118
|
-
heapSamplingParams(samplingInterval),
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await page.exposeFunction(
|
|
124
|
-
"__benchCollect",
|
|
125
|
-
async (samples: number[], wallTimeMs: number) => {
|
|
126
|
-
let heapProfile: HeapProfile | undefined;
|
|
127
|
-
if (heapSample && instrumentsStarted) {
|
|
128
|
-
const result = await cdp.send("HeapProfiler.stopSampling");
|
|
129
|
-
heapProfile = result.profile as unknown as HeapProfile;
|
|
130
|
-
}
|
|
131
|
-
resolve({ samples, heapProfile, wallTimeMs });
|
|
132
|
-
},
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
await page.addInitScript(injectLapFunctions);
|
|
136
|
-
|
|
137
|
-
const timer = setTimeout(() => {
|
|
138
|
-
const lines = [`Timed out after ${timeout}s`];
|
|
139
|
-
if (pageErrors.length) {
|
|
140
|
-
lines.push("Page JS errors:", ...pageErrors.map(e => ` ${e}`));
|
|
141
|
-
} else {
|
|
142
|
-
lines.push("Page did not call __done() or define window.__bench");
|
|
143
|
-
}
|
|
144
|
-
reject(new Error(lines.join("\n")));
|
|
145
|
-
}, timeout * 1000);
|
|
146
|
-
|
|
147
|
-
return { promise, cancel: () => clearTimeout(timer) };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** In-page timing functions injected via addInitScript (zero CDP overhead).
|
|
151
|
-
* __start/__lap collect timestamps, __done delegates to exposed __benchCollect. */
|
|
152
|
-
function injectLapFunctions(): void {
|
|
153
|
-
const g = globalThis as any;
|
|
154
|
-
g.__benchSamples = [];
|
|
155
|
-
g.__benchLastTime = 0;
|
|
156
|
-
g.__benchFirstStart = 0;
|
|
157
|
-
|
|
158
|
-
g.__start = () => {
|
|
159
|
-
const now = performance.now();
|
|
160
|
-
g.__benchLastTime = now;
|
|
161
|
-
if (!g.__benchFirstStart) {
|
|
162
|
-
g.__benchFirstStart = now;
|
|
163
|
-
return g.__benchInstrumentStart();
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
g.__lap = () => {
|
|
168
|
-
const now = performance.now();
|
|
169
|
-
g.__benchSamples.push(now - g.__benchLastTime);
|
|
170
|
-
g.__benchLastTime = now;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
g.__done = () => {
|
|
174
|
-
const wall = g.__benchFirstStart
|
|
175
|
-
? performance.now() - g.__benchFirstStart
|
|
176
|
-
: 0;
|
|
177
|
-
return g.__benchCollect(g.__benchSamples.slice(), wall);
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function heapSamplingParams(samplingInterval: number) {
|
|
182
|
-
return {
|
|
183
|
-
samplingInterval,
|
|
184
|
-
includeObjectsCollectedByMajorGC: true,
|
|
185
|
-
includeObjectsCollectedByMinorGC: true,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Start CDP GC tracing, returns the event collector array. */
|
|
190
|
-
async function startGcTracing(cdp: CDPSession): Promise<TraceEvent[]> {
|
|
191
|
-
const events: TraceEvent[] = [];
|
|
192
|
-
cdp.on("Tracing.dataCollected", ({ value }) => {
|
|
193
|
-
for (const e of value) events.push(e as unknown as TraceEvent);
|
|
194
|
-
});
|
|
195
|
-
await cdp.send("Tracing.start", {
|
|
196
|
-
traceConfig: { includedCategories: ["v8", "v8.gc"] },
|
|
197
|
-
});
|
|
198
|
-
return events;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Bench function mode: run window.__bench in a timed iteration loop. */
|
|
202
|
-
async function runBenchLoop(
|
|
203
|
-
page: Page,
|
|
204
|
-
cdp: CDPSession,
|
|
205
|
-
params: BrowserProfileParams,
|
|
206
|
-
samplingInterval: number,
|
|
207
|
-
): Promise<BrowserProfileResult> {
|
|
208
|
-
const { heapSample } = params;
|
|
209
|
-
const maxTime = params.maxTime ?? 642;
|
|
210
|
-
const maxIter = params.maxIterations ?? Number.MAX_SAFE_INTEGER;
|
|
211
|
-
|
|
212
|
-
if (heapSample) {
|
|
213
|
-
await cdp.send(
|
|
214
|
-
"HeapProfiler.startSampling",
|
|
215
|
-
heapSamplingParams(samplingInterval),
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const { samples, totalMs } = await page.evaluate(
|
|
220
|
-
async ({ maxTime, maxIter }) => {
|
|
221
|
-
const bench = (globalThis as any).__bench;
|
|
222
|
-
const samples: number[] = [];
|
|
223
|
-
const startAll = performance.now();
|
|
224
|
-
const deadline = startAll + maxTime;
|
|
225
|
-
for (let i = 0; i < maxIter && performance.now() < deadline; i++) {
|
|
226
|
-
const t0 = performance.now();
|
|
227
|
-
await bench();
|
|
228
|
-
samples.push(performance.now() - t0);
|
|
229
|
-
}
|
|
230
|
-
return { samples, totalMs: performance.now() - startAll };
|
|
231
|
-
},
|
|
232
|
-
{ maxTime, maxIter },
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
let heapProfile: HeapProfile | undefined;
|
|
236
|
-
if (heapSample) {
|
|
237
|
-
const result = await cdp.send("HeapProfiler.stopSampling");
|
|
238
|
-
heapProfile = result.profile as unknown as HeapProfile;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return { samples, heapProfile, wallTimeMs: totalMs };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Stop CDP tracing and parse GC events into GcStats. */
|
|
245
|
-
async function collectTracing(
|
|
246
|
-
cdp: CDPSession,
|
|
247
|
-
traceEvents: TraceEvent[],
|
|
248
|
-
): Promise<GcStats> {
|
|
249
|
-
const complete = new Promise<void>(resolve =>
|
|
250
|
-
cdp.once("Tracing.tracingComplete", () => resolve()),
|
|
251
|
-
);
|
|
252
|
-
await cdp.send("Tracing.end");
|
|
253
|
-
await complete;
|
|
254
|
-
return browserGcStats(traceEvents);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** Forward Chrome's stdout/stderr to the terminal so V8 flag output is visible. */
|
|
258
|
-
function pipeChromeOutput(server: BrowserServer): void {
|
|
259
|
-
const proc = server.process();
|
|
260
|
-
const pipe = (stream: NodeJS.ReadableStream | null) =>
|
|
261
|
-
stream?.on("data", (chunk: Buffer) => {
|
|
262
|
-
for (const line of chunk.toString().split("\n")) {
|
|
263
|
-
const text = line.trim();
|
|
264
|
-
if (text) process.stderr.write(`[chrome] ${text}\n`);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
pipe(proc.stdout);
|
|
268
|
-
pipe(proc.stderr);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export { profileBrowser as profileBrowserHeap };
|
package/src/export/JsonExport.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
2
|
-
import type { ReportGroup } from "../BenchmarkReport.ts";
|
|
3
|
-
import type { DefaultCliArgs } from "../cli/CliArgs.ts";
|
|
4
|
-
import type {
|
|
5
|
-
BenchmarkGroup,
|
|
6
|
-
BenchmarkJsonData,
|
|
7
|
-
BenchmarkResult,
|
|
8
|
-
} from "./JsonFormat.ts";
|
|
9
|
-
|
|
10
|
-
/** Export benchmark results to JSON file */
|
|
11
|
-
export async function exportBenchmarkJson(
|
|
12
|
-
groups: ReportGroup[],
|
|
13
|
-
outputPath: string,
|
|
14
|
-
args: DefaultCliArgs,
|
|
15
|
-
suiteName = "Benchmark Suite",
|
|
16
|
-
): Promise<void> {
|
|
17
|
-
const jsonData = prepareJsonData(groups, args, suiteName);
|
|
18
|
-
const jsonString = JSON.stringify(jsonData, null, 2);
|
|
19
|
-
|
|
20
|
-
await writeFile(outputPath, jsonString, "utf-8");
|
|
21
|
-
console.log(`Benchmark data exported to: ${outputPath}`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Convert ReportGroup data to JSON format */
|
|
25
|
-
function prepareJsonData(
|
|
26
|
-
groups: ReportGroup[],
|
|
27
|
-
args: DefaultCliArgs,
|
|
28
|
-
suiteName: string,
|
|
29
|
-
): BenchmarkJsonData {
|
|
30
|
-
return {
|
|
31
|
-
meta: {
|
|
32
|
-
timestamp: new Date().toISOString(),
|
|
33
|
-
version: process.env.npm_package_version || "unknown",
|
|
34
|
-
args: cleanCliArgs(args),
|
|
35
|
-
environment: {
|
|
36
|
-
node: process.version,
|
|
37
|
-
platform: process.platform,
|
|
38
|
-
arch: process.arch,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
suites: [
|
|
42
|
-
{
|
|
43
|
-
name: suiteName,
|
|
44
|
-
groups: groups.map(convertGroup),
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Convert a report group, mapping each report to the JSON result format */
|
|
51
|
-
function convertGroup(group: ReportGroup): BenchmarkGroup {
|
|
52
|
-
return {
|
|
53
|
-
name: "Benchmark Group", // Could be enhanced to include actual group names
|
|
54
|
-
baseline: group.baseline ? convertReport(group.baseline) : undefined,
|
|
55
|
-
benchmarks: group.reports.map(convertReport),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Extract measured stats and optional metrics into JSON result shape */
|
|
60
|
-
function convertReport(report: any): BenchmarkResult {
|
|
61
|
-
const { name, measuredResults: m } = report;
|
|
62
|
-
const { time, heapSize, gcTime, cpu } = m;
|
|
63
|
-
const minMaxMean = (s: any) =>
|
|
64
|
-
s ? { min: s.min, max: s.max, mean: s.avg } : undefined;
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
name,
|
|
68
|
-
status: "completed",
|
|
69
|
-
samples: m.samples || [],
|
|
70
|
-
time: {
|
|
71
|
-
...minMaxMean(time)!,
|
|
72
|
-
p50: time.p50,
|
|
73
|
-
p75: time.p75,
|
|
74
|
-
p99: time.p99,
|
|
75
|
-
p999: time.p999,
|
|
76
|
-
},
|
|
77
|
-
heapSize: minMaxMean(heapSize),
|
|
78
|
-
gcTime: minMaxMean(gcTime),
|
|
79
|
-
cpu: cpu
|
|
80
|
-
? {
|
|
81
|
-
instructions: cpu.instructions,
|
|
82
|
-
cycles: cpu.cycles,
|
|
83
|
-
cacheMisses: m.cpuCacheMiss,
|
|
84
|
-
branchMisses: cpu.branchMisses,
|
|
85
|
-
}
|
|
86
|
-
: undefined,
|
|
87
|
-
execution: {
|
|
88
|
-
iterations: m.samples?.length || 0,
|
|
89
|
-
totalTime: m.totalTime || 0,
|
|
90
|
-
warmupRuns: undefined, // Not available in current data structure
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Clean CLI args for JSON export (remove undefined values) */
|
|
96
|
-
function cleanCliArgs(args: DefaultCliArgs): Record<string, any> {
|
|
97
|
-
const toCamel = (k: string) =>
|
|
98
|
-
k.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
99
|
-
const entries = Object.entries(args)
|
|
100
|
-
.filter(([, v]) => v !== undefined && v !== null)
|
|
101
|
-
.map(([k, v]) => [toCamel(k), v]);
|
|
102
|
-
return Object.fromEntries(entries);
|
|
103
|
-
}
|
package/src/export/JsonFormat.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/** Complete benchmark data structure for JSON export */
|
|
2
|
-
export interface BenchmarkJsonData {
|
|
3
|
-
meta: {
|
|
4
|
-
timestamp: string;
|
|
5
|
-
version: string;
|
|
6
|
-
args: Record<string, any>;
|
|
7
|
-
environment: {
|
|
8
|
-
node: string;
|
|
9
|
-
platform: string;
|
|
10
|
-
arch?: string;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
suites: BenchmarkSuite[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface BenchmarkSuite {
|
|
17
|
-
name: string;
|
|
18
|
-
groups: BenchmarkGroup[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface BenchmarkGroup {
|
|
22
|
-
name: string;
|
|
23
|
-
baseline?: BenchmarkResult;
|
|
24
|
-
benchmarks: BenchmarkResult[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface BenchmarkResult {
|
|
28
|
-
name: string;
|
|
29
|
-
status: "completed" | "running" | "failed";
|
|
30
|
-
|
|
31
|
-
/** Raw execution time samples in milliseconds */
|
|
32
|
-
samples: number[];
|
|
33
|
-
|
|
34
|
-
/** Statistical summaries */
|
|
35
|
-
time: {
|
|
36
|
-
min: number;
|
|
37
|
-
max: number;
|
|
38
|
-
mean: number;
|
|
39
|
-
p50: number;
|
|
40
|
-
p75: number;
|
|
41
|
-
p99: number;
|
|
42
|
-
p999: number;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/** Optional performance metrics */
|
|
46
|
-
heapSize?: {
|
|
47
|
-
min: number;
|
|
48
|
-
max: number;
|
|
49
|
-
mean: number;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
gcTime?: {
|
|
53
|
-
min: number;
|
|
54
|
-
max: number;
|
|
55
|
-
mean: number;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
cpu?: {
|
|
59
|
-
instructions?: number;
|
|
60
|
-
cycles?: number;
|
|
61
|
-
cacheMisses?: number;
|
|
62
|
-
branchMisses?: number;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/** Execution metadata */
|
|
66
|
-
execution: {
|
|
67
|
-
iterations: number;
|
|
68
|
-
totalTime: number;
|
|
69
|
-
warmupRuns?: number;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/** Adaptive mode results */
|
|
73
|
-
adaptive?: {
|
|
74
|
-
confidenceInterval: {
|
|
75
|
-
lower: number;
|
|
76
|
-
upper: number;
|
|
77
|
-
margin: number;
|
|
78
|
-
marginPercent: number;
|
|
79
|
-
confidence: number;
|
|
80
|
-
};
|
|
81
|
-
converged: boolean;
|
|
82
|
-
stopReason: "threshold_met" | "max_time" | "max_iterations";
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/** Error information */
|
|
86
|
-
error?: {
|
|
87
|
-
message: string;
|
|
88
|
-
type: string;
|
|
89
|
-
stackTrace?: string;
|
|
90
|
-
};
|
|
91
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import pc from "picocolors";
|
|
2
|
-
import type { HeapProfile, ProfileNode } from "./HeapSampler.ts";
|
|
3
|
-
|
|
4
|
-
/** Sum selfSize across all nodes in profile (before any filtering) */
|
|
5
|
-
export function totalProfileBytes(profile: HeapProfile): number {
|
|
6
|
-
let total = 0;
|
|
7
|
-
function walk(node: ProfileNode): void {
|
|
8
|
-
total += node.selfSize;
|
|
9
|
-
for (const child of node.children || []) walk(child);
|
|
10
|
-
}
|
|
11
|
-
walk(profile.head);
|
|
12
|
-
return total;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CallFrame {
|
|
16
|
-
fn: string;
|
|
17
|
-
url: string;
|
|
18
|
-
line: number; // 1-indexed for display
|
|
19
|
-
col: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface HeapSite {
|
|
23
|
-
fn: string;
|
|
24
|
-
url: string;
|
|
25
|
-
line: number; // 1-indexed for display
|
|
26
|
-
col: number;
|
|
27
|
-
bytes: number;
|
|
28
|
-
stack?: CallFrame[]; // call stack from root to this frame
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Flatten profile tree into sorted list of allocation sites with call stacks */
|
|
32
|
-
export function flattenProfile(profile: HeapProfile): HeapSite[] {
|
|
33
|
-
const sites: HeapSite[] = [];
|
|
34
|
-
|
|
35
|
-
function walk(node: ProfileNode, stack: CallFrame[]): void {
|
|
36
|
-
const { functionName, url, lineNumber, columnNumber } = node.callFrame;
|
|
37
|
-
const fn = functionName || "(anonymous)";
|
|
38
|
-
const col = columnNumber ?? 0;
|
|
39
|
-
const frame: CallFrame = { fn, url: url || "", line: lineNumber + 1, col };
|
|
40
|
-
const newStack = [...stack, frame];
|
|
41
|
-
|
|
42
|
-
if (node.selfSize > 0) {
|
|
43
|
-
sites.push({
|
|
44
|
-
...frame,
|
|
45
|
-
bytes: node.selfSize,
|
|
46
|
-
stack: newStack,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
for (const child of node.children || []) walk(child, newStack);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
walk(profile.head, []);
|
|
53
|
-
return sites.sort((a, b) => b.bytes - a.bytes);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type UserCodeFilter = (site: CallFrame) => boolean;
|
|
57
|
-
|
|
58
|
-
/** Check if site is user code (not node internals) */
|
|
59
|
-
export function isNodeUserCode(site: CallFrame): boolean {
|
|
60
|
-
if (!site.url) return false;
|
|
61
|
-
if (site.url.startsWith("node:")) return false;
|
|
62
|
-
if (site.url.includes("(native)")) return false;
|
|
63
|
-
if (site.url.includes("internal/")) return false;
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Check if site is user code (not browser internals) */
|
|
68
|
-
export function isBrowserUserCode(site: CallFrame): boolean {
|
|
69
|
-
if (!site.url) return false;
|
|
70
|
-
if (site.url.startsWith("chrome-extension://")) return false;
|
|
71
|
-
if (site.url.startsWith("devtools://")) return false;
|
|
72
|
-
if (site.url.includes("(native)")) return false;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Filter sites to user code only */
|
|
77
|
-
export function filterSites(
|
|
78
|
-
sites: HeapSite[],
|
|
79
|
-
isUser: UserCodeFilter = isNodeUserCode,
|
|
80
|
-
): HeapSite[] {
|
|
81
|
-
return sites.filter(isUser);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Aggregate sites by location (combine same file:line:col) */
|
|
85
|
-
export function aggregateSites(sites: HeapSite[]): HeapSite[] {
|
|
86
|
-
const byLocation = new Map<string, HeapSite>();
|
|
87
|
-
|
|
88
|
-
for (const site of sites) {
|
|
89
|
-
const key = `${site.url}:${site.line}:${site.col}`;
|
|
90
|
-
const existing = byLocation.get(key);
|
|
91
|
-
if (existing) {
|
|
92
|
-
existing.bytes += site.bytes;
|
|
93
|
-
} else {
|
|
94
|
-
byLocation.set(key, { ...site });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return [...byLocation.values()].sort((a, b) => b.bytes - a.bytes);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function fmtBytes(bytes: number): string {
|
|
102
|
-
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
103
|
-
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
104
|
-
return `${bytes} B`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface HeapReportOptions {
|
|
108
|
-
topN: number;
|
|
109
|
-
stackDepth?: number;
|
|
110
|
-
verbose?: boolean;
|
|
111
|
-
userOnly?: boolean; // filter to user code only (hide node internals)
|
|
112
|
-
isUserCode?: UserCodeFilter; // predicate for user vs internal code
|
|
113
|
-
totalAll?: number; // total across all nodes (before filtering)
|
|
114
|
-
totalUserCode?: number; // total for user code only
|
|
115
|
-
sampleCount?: number; // number of samples taken
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Format heap report for console output */
|
|
119
|
-
export function formatHeapReport(
|
|
120
|
-
sites: HeapSite[],
|
|
121
|
-
options: HeapReportOptions,
|
|
122
|
-
): string {
|
|
123
|
-
const { topN, stackDepth = 3, verbose = false } = options;
|
|
124
|
-
const { totalAll, totalUserCode, sampleCount } = options;
|
|
125
|
-
const isUser = options.isUserCode ?? isNodeUserCode;
|
|
126
|
-
const lines: string[] = [];
|
|
127
|
-
lines.push(`Heap allocation sites (top ${topN}, garbage included):`);
|
|
128
|
-
|
|
129
|
-
for (const site of sites.slice(0, topN)) {
|
|
130
|
-
if (verbose) {
|
|
131
|
-
formatVerboseSite(lines, site, stackDepth, isUser);
|
|
132
|
-
} else {
|
|
133
|
-
formatCompactSite(lines, site, stackDepth, isUser);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
lines.push("");
|
|
138
|
-
if (totalAll !== undefined)
|
|
139
|
-
lines.push(`Total (all): ${fmtBytes(totalAll)}`);
|
|
140
|
-
if (totalUserCode !== undefined)
|
|
141
|
-
lines.push(`Total (user-code): ${fmtBytes(totalUserCode)}`);
|
|
142
|
-
if (sampleCount !== undefined)
|
|
143
|
-
lines.push(`Samples: ${sampleCount.toLocaleString()}`);
|
|
144
|
-
|
|
145
|
-
return lines.join("\n");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Compact single-line format: `49 MB fn1 <- fn2 <- fn3` */
|
|
149
|
-
function formatCompactSite(
|
|
150
|
-
lines: string[],
|
|
151
|
-
site: HeapSite,
|
|
152
|
-
stackDepth: number,
|
|
153
|
-
isUser: UserCodeFilter,
|
|
154
|
-
): void {
|
|
155
|
-
const bytes = fmtBytes(site.bytes).padStart(10);
|
|
156
|
-
const fns = [site.fn];
|
|
157
|
-
|
|
158
|
-
if (site.stack && site.stack.length > 1) {
|
|
159
|
-
const callers = site.stack.slice(0, -1).reverse().slice(0, stackDepth);
|
|
160
|
-
for (const frame of callers) {
|
|
161
|
-
if (!frame.url || !isUser(frame)) continue;
|
|
162
|
-
fns.push(frame.fn);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const line = `${bytes} ${fns.join(" <- ")}`;
|
|
167
|
-
lines.push(isUser(site) ? line : pc.dim(line));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** Verbose multi-line format with file:// paths and line numbers */
|
|
171
|
-
function formatVerboseSite(
|
|
172
|
-
lines: string[],
|
|
173
|
-
site: HeapSite,
|
|
174
|
-
stackDepth: number,
|
|
175
|
-
isUser: UserCodeFilter,
|
|
176
|
-
): void {
|
|
177
|
-
const bytes = fmtBytes(site.bytes).padStart(10);
|
|
178
|
-
const loc = site.url ? `${site.url}:${site.line}:${site.col}` : "(unknown)";
|
|
179
|
-
const dimFn = isUser(site) ? (s: string) => s : pc.dim;
|
|
180
|
-
|
|
181
|
-
lines.push(dimFn(`${bytes} ${site.fn} ${loc}`));
|
|
182
|
-
|
|
183
|
-
if (site.stack && site.stack.length > 1) {
|
|
184
|
-
const callers = site.stack.slice(0, -1).reverse().slice(0, stackDepth);
|
|
185
|
-
for (const frame of callers) {
|
|
186
|
-
if (!frame.url || !isUser(frame)) continue;
|
|
187
|
-
const callerLoc = `${frame.url}:${frame.line}:${frame.col}`;
|
|
188
|
-
lines.push(dimFn(` <- ${frame.fn} ${callerLoc}`));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Get total bytes from sites */
|
|
194
|
-
export function totalBytes(sites: HeapSite[]): number {
|
|
195
|
-
return sites.reduce((sum, s) => sum + s.bytes, 0);
|
|
196
|
-
}
|