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
|
@@ -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
|
-
/** Forward Chrome's stdout/stderr to the terminal so V8 flag output is visible. */
|
|
97
|
-
function pipeChromeOutput(server: BrowserServer): void {
|
|
98
|
-
const proc = server.process();
|
|
99
|
-
const pipe = (stream: NodeJS.ReadableStream | null) =>
|
|
100
|
-
stream?.on("data", (chunk: Buffer) => {
|
|
101
|
-
for (const line of chunk.toString().split("\n")) {
|
|
102
|
-
const text = line.trim();
|
|
103
|
-
if (text) process.stderr.write(`[chrome] ${text}\n`);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
pipe(proc.stdout);
|
|
107
|
-
pipe(proc.stderr);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Start CDP GC tracing, returns the event collector array. */
|
|
111
|
-
async function startGcTracing(cdp: CDPSession): Promise<TraceEvent[]> {
|
|
112
|
-
const events: TraceEvent[] = [];
|
|
113
|
-
cdp.on("Tracing.dataCollected", ({ value }) => {
|
|
114
|
-
for (const e of value) events.push(e as unknown as TraceEvent);
|
|
115
|
-
});
|
|
116
|
-
await cdp.send("Tracing.start", {
|
|
117
|
-
traceConfig: { includedCategories: ["v8", "v8.gc"] },
|
|
118
|
-
});
|
|
119
|
-
return events;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Inject __start/__lap as in-page functions, expose __done for results collection.
|
|
123
|
-
* __start/__lap are pure in-page (zero CDP overhead). First __start() triggers
|
|
124
|
-
* instrument start. __done() stops instruments and collects timing data. */
|
|
125
|
-
async function setupLapMode(
|
|
126
|
-
page: Page,
|
|
127
|
-
cdp: CDPSession,
|
|
128
|
-
params: BrowserProfileParams,
|
|
129
|
-
samplingInterval: number,
|
|
130
|
-
timeout: number,
|
|
131
|
-
pageErrors: string[],
|
|
132
|
-
): Promise<LapModeHandle> {
|
|
133
|
-
const { heapSample } = params;
|
|
134
|
-
const { promise, resolve, reject } =
|
|
135
|
-
Promise.withResolvers<BrowserProfileResult>();
|
|
136
|
-
let instrumentsStarted = false;
|
|
137
|
-
|
|
138
|
-
await page.exposeFunction("__benchInstrumentStart", async () => {
|
|
139
|
-
if (instrumentsStarted) return;
|
|
140
|
-
instrumentsStarted = true;
|
|
141
|
-
if (heapSample) {
|
|
142
|
-
await cdp.send(
|
|
143
|
-
"HeapProfiler.startSampling",
|
|
144
|
-
heapSamplingParams(samplingInterval),
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
await page.exposeFunction(
|
|
150
|
-
"__benchCollect",
|
|
151
|
-
async (samples: number[], wallTimeMs: number) => {
|
|
152
|
-
let heapProfile: HeapProfile | undefined;
|
|
153
|
-
if (heapSample && instrumentsStarted) {
|
|
154
|
-
const result = await cdp.send("HeapProfiler.stopSampling");
|
|
155
|
-
heapProfile = result.profile as unknown as HeapProfile;
|
|
156
|
-
}
|
|
157
|
-
resolve({ samples, heapProfile, wallTimeMs });
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
await page.addInitScript(injectLapFunctions);
|
|
162
|
-
|
|
163
|
-
const timer = setTimeout(() => {
|
|
164
|
-
const lines = [`Timed out after ${timeout}s`];
|
|
165
|
-
if (pageErrors.length) {
|
|
166
|
-
lines.push("Page JS errors:", ...pageErrors.map(e => ` ${e}`));
|
|
167
|
-
} else {
|
|
168
|
-
lines.push("Page did not call __done() or define window.__bench");
|
|
169
|
-
}
|
|
170
|
-
reject(new Error(lines.join("\n")));
|
|
171
|
-
}, timeout * 1000);
|
|
172
|
-
|
|
173
|
-
return { promise, cancel: () => clearTimeout(timer) };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Bench function mode: run window.__bench in a timed iteration loop. */
|
|
177
|
-
async function runBenchLoop(
|
|
178
|
-
page: Page,
|
|
179
|
-
cdp: CDPSession,
|
|
180
|
-
params: BrowserProfileParams,
|
|
181
|
-
samplingInterval: number,
|
|
182
|
-
): Promise<BrowserProfileResult> {
|
|
183
|
-
const { heapSample } = params;
|
|
184
|
-
const maxTime = params.maxTime ?? 642;
|
|
185
|
-
const maxIter = params.maxIterations ?? Number.MAX_SAFE_INTEGER;
|
|
186
|
-
|
|
187
|
-
if (heapSample) {
|
|
188
|
-
await cdp.send(
|
|
189
|
-
"HeapProfiler.startSampling",
|
|
190
|
-
heapSamplingParams(samplingInterval),
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const { samples, totalMs } = await page.evaluate(
|
|
195
|
-
async ({ maxTime, maxIter }) => {
|
|
196
|
-
const bench = (globalThis as any).__bench;
|
|
197
|
-
const samples: number[] = [];
|
|
198
|
-
const startAll = performance.now();
|
|
199
|
-
const deadline = startAll + maxTime;
|
|
200
|
-
for (let i = 0; i < maxIter && performance.now() < deadline; i++) {
|
|
201
|
-
const t0 = performance.now();
|
|
202
|
-
await bench();
|
|
203
|
-
samples.push(performance.now() - t0);
|
|
204
|
-
}
|
|
205
|
-
return { samples, totalMs: performance.now() - startAll };
|
|
206
|
-
},
|
|
207
|
-
{ maxTime, maxIter },
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
let heapProfile: HeapProfile | undefined;
|
|
211
|
-
if (heapSample) {
|
|
212
|
-
const result = await cdp.send("HeapProfiler.stopSampling");
|
|
213
|
-
heapProfile = result.profile as unknown as HeapProfile;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { samples, heapProfile, wallTimeMs: totalMs };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Stop CDP tracing and parse GC events into GcStats. */
|
|
220
|
-
async function collectTracing(
|
|
221
|
-
cdp: CDPSession,
|
|
222
|
-
traceEvents: TraceEvent[],
|
|
223
|
-
): Promise<GcStats> {
|
|
224
|
-
const complete = new Promise<void>(resolve =>
|
|
225
|
-
cdp.once("Tracing.tracingComplete", () => resolve()),
|
|
226
|
-
);
|
|
227
|
-
await cdp.send("Tracing.end");
|
|
228
|
-
await complete;
|
|
229
|
-
return browserGcStats(traceEvents);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function heapSamplingParams(samplingInterval: number) {
|
|
233
|
-
return {
|
|
234
|
-
samplingInterval,
|
|
235
|
-
includeObjectsCollectedByMajorGC: true,
|
|
236
|
-
includeObjectsCollectedByMinorGC: true,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** In-page timing functions injected via addInitScript (zero CDP overhead).
|
|
241
|
-
* __start/__lap collect timestamps, __done delegates to exposed __benchCollect. */
|
|
242
|
-
function injectLapFunctions(): void {
|
|
243
|
-
const g = globalThis as any;
|
|
244
|
-
g.__benchSamples = [];
|
|
245
|
-
g.__benchLastTime = 0;
|
|
246
|
-
g.__benchFirstStart = 0;
|
|
247
|
-
|
|
248
|
-
g.__start = () => {
|
|
249
|
-
const now = performance.now();
|
|
250
|
-
g.__benchLastTime = now;
|
|
251
|
-
if (!g.__benchFirstStart) {
|
|
252
|
-
g.__benchFirstStart = now;
|
|
253
|
-
return g.__benchInstrumentStart();
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
g.__lap = () => {
|
|
258
|
-
const now = performance.now();
|
|
259
|
-
g.__benchSamples.push(now - g.__benchLastTime);
|
|
260
|
-
g.__benchLastTime = now;
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
g.__done = () => {
|
|
264
|
-
const wall = g.__benchFirstStart
|
|
265
|
-
? performance.now() - g.__benchFirstStart
|
|
266
|
-
: 0;
|
|
267
|
-
return g.__benchCollect(g.__benchSamples.slice(), wall);
|
|
268
|
-
};
|
|
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
|
-
/** Clean CLI args for JSON export (remove undefined values) */
|
|
51
|
-
function cleanCliArgs(args: DefaultCliArgs): Record<string, any> {
|
|
52
|
-
const toCamel = (k: string) =>
|
|
53
|
-
k.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
54
|
-
const entries = Object.entries(args)
|
|
55
|
-
.filter(([, v]) => v !== undefined && v !== null)
|
|
56
|
-
.map(([k, v]) => [toCamel(k), v]);
|
|
57
|
-
return Object.fromEntries(entries);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Convert a report group, mapping each report to the JSON result format */
|
|
61
|
-
function convertGroup(group: ReportGroup): BenchmarkGroup {
|
|
62
|
-
return {
|
|
63
|
-
name: "Benchmark Group", // Could be enhanced to include actual group names
|
|
64
|
-
baseline: group.baseline ? convertReport(group.baseline) : undefined,
|
|
65
|
-
benchmarks: group.reports.map(convertReport),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Extract measured stats and optional metrics into JSON result shape */
|
|
70
|
-
function convertReport(report: any): BenchmarkResult {
|
|
71
|
-
const { name, measuredResults: m } = report;
|
|
72
|
-
const { time, heapSize, gcTime, cpu } = m;
|
|
73
|
-
const minMaxMean = (s: any) =>
|
|
74
|
-
s ? { min: s.min, max: s.max, mean: s.avg } : undefined;
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
name,
|
|
78
|
-
status: "completed",
|
|
79
|
-
samples: m.samples || [],
|
|
80
|
-
time: {
|
|
81
|
-
...minMaxMean(time)!,
|
|
82
|
-
p50: time.p50,
|
|
83
|
-
p75: time.p75,
|
|
84
|
-
p99: time.p99,
|
|
85
|
-
p999: time.p999,
|
|
86
|
-
},
|
|
87
|
-
heapSize: minMaxMean(heapSize),
|
|
88
|
-
gcTime: minMaxMean(gcTime),
|
|
89
|
-
cpu: cpu
|
|
90
|
-
? {
|
|
91
|
-
instructions: cpu.instructions,
|
|
92
|
-
cycles: cpu.cycles,
|
|
93
|
-
cacheMisses: m.cpuCacheMiss,
|
|
94
|
-
branchMisses: cpu.branchMisses,
|
|
95
|
-
}
|
|
96
|
-
: undefined,
|
|
97
|
-
execution: {
|
|
98
|
-
iterations: m.samples?.length || 0,
|
|
99
|
-
totalTime: m.totalTime || 0,
|
|
100
|
-
warmupRuns: undefined, // Not available in current data structure
|
|
101
|
-
},
|
|
102
|
-
};
|
|
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,202 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
-
|
|
6
|
-
import { groupReports, type ReportGroup } from "../BenchmarkReport.ts";
|
|
7
|
-
import type { HeapProfile } from "../heap-sample/HeapSampler.ts";
|
|
8
|
-
import {
|
|
9
|
-
type ResolvedFrame,
|
|
10
|
-
type ResolvedProfile,
|
|
11
|
-
resolveProfile,
|
|
12
|
-
} from "../heap-sample/ResolvedProfile.ts";
|
|
13
|
-
|
|
14
|
-
/** speedscope file format (https://www.speedscope.app/file-format-schema.json) */
|
|
15
|
-
interface SpeedscopeFile {
|
|
16
|
-
$schema: "https://www.speedscope.app/file-format-schema.json";
|
|
17
|
-
shared: { frames: SpeedscopeFrame[] };
|
|
18
|
-
profiles: SpeedscopeProfile[];
|
|
19
|
-
name?: string;
|
|
20
|
-
exporter?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface SpeedscopeFrame {
|
|
24
|
-
name: string;
|
|
25
|
-
file?: string;
|
|
26
|
-
line?: number;
|
|
27
|
-
col?: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SpeedscopeProfile {
|
|
31
|
-
type: "sampled";
|
|
32
|
-
name: string;
|
|
33
|
-
unit: "bytes";
|
|
34
|
-
startValue: number;
|
|
35
|
-
endValue: number;
|
|
36
|
-
samples: number[][]; // each sample is stack of frame indices
|
|
37
|
-
weights: number[]; // bytes per sample
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Export heap profiles from benchmark results to speedscope JSON format.
|
|
41
|
-
* Creates one speedscope profile per benchmark that has a heapProfile.
|
|
42
|
-
* @returns resolved output path, or undefined if no profiles were found */
|
|
43
|
-
export function exportSpeedscope(
|
|
44
|
-
groups: ReportGroup[],
|
|
45
|
-
outputPath: string,
|
|
46
|
-
): string | undefined {
|
|
47
|
-
const frames: SpeedscopeFrame[] = [];
|
|
48
|
-
const frameIndex = new Map<string, number>();
|
|
49
|
-
const profiles: SpeedscopeProfile[] = [];
|
|
50
|
-
|
|
51
|
-
for (const group of groups) {
|
|
52
|
-
for (const report of groupReports(group)) {
|
|
53
|
-
const { heapProfile } = report.measuredResults;
|
|
54
|
-
if (!heapProfile) continue;
|
|
55
|
-
const resolved = resolveProfile(heapProfile);
|
|
56
|
-
profiles.push(buildProfile(report.name, resolved, frames, frameIndex));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (profiles.length === 0) {
|
|
61
|
-
console.log("No heap profiles to export.");
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const file: SpeedscopeFile = {
|
|
66
|
-
$schema: "https://www.speedscope.app/file-format-schema.json",
|
|
67
|
-
shared: { frames },
|
|
68
|
-
profiles,
|
|
69
|
-
exporter: "benchforge",
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const absPath = resolve(outputPath);
|
|
73
|
-
writeFileSync(absPath, JSON.stringify(file));
|
|
74
|
-
console.log(`Speedscope profile exported to: ${outputPath}`);
|
|
75
|
-
return absPath;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Export to a temp file and open in speedscope via npx */
|
|
79
|
-
export function exportAndLaunchSpeedscope(groups: ReportGroup[]): void {
|
|
80
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
81
|
-
const outputPath = join(tmpdir(), `benchforge-${timestamp}.speedscope.json`);
|
|
82
|
-
const absPath = exportSpeedscope(groups, outputPath);
|
|
83
|
-
if (absPath) {
|
|
84
|
-
launchSpeedscope(absPath);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Launch speedscope viewer on a file via npx */
|
|
89
|
-
export function launchSpeedscope(filePath: string): void {
|
|
90
|
-
console.log("Opening speedscope...");
|
|
91
|
-
const child = spawn("npx", ["speedscope", filePath], {
|
|
92
|
-
detached: true,
|
|
93
|
-
stdio: "ignore",
|
|
94
|
-
});
|
|
95
|
-
child.unref();
|
|
96
|
-
child.on("error", () => {
|
|
97
|
-
console.error(
|
|
98
|
-
`Failed to launch speedscope. Run manually:\n npx speedscope ${filePath}`,
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Convert a single HeapProfile to speedscope format (for standalone use) */
|
|
104
|
-
export function heapProfileToSpeedscope(
|
|
105
|
-
name: string,
|
|
106
|
-
profile: HeapProfile,
|
|
107
|
-
): SpeedscopeFile {
|
|
108
|
-
const frames: SpeedscopeFrame[] = [];
|
|
109
|
-
const frameIndex = new Map<string, number>();
|
|
110
|
-
const resolved = resolveProfile(profile);
|
|
111
|
-
const p = buildProfile(name, resolved, frames, frameIndex);
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
$schema: "https://www.speedscope.app/file-format-schema.json",
|
|
115
|
-
shared: { frames },
|
|
116
|
-
profiles: [p],
|
|
117
|
-
exporter: "benchforge",
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Build a single speedscope profile from a resolved heap profile */
|
|
122
|
-
function buildProfile(
|
|
123
|
-
name: string,
|
|
124
|
-
resolved: ResolvedProfile,
|
|
125
|
-
sharedFrames: SpeedscopeFrame[],
|
|
126
|
-
frameIndex: Map<string, number>,
|
|
127
|
-
): SpeedscopeProfile {
|
|
128
|
-
// Build nodeId -> stack of frame indices
|
|
129
|
-
const nodeStacks = new Map<number, number[]>();
|
|
130
|
-
for (const node of resolved.nodes) {
|
|
131
|
-
const stack = node.stack.map(f => internFrame(f, sharedFrames, frameIndex));
|
|
132
|
-
nodeStacks.set(node.nodeId, stack);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const samples: number[][] = [];
|
|
136
|
-
const weights: number[] = [];
|
|
137
|
-
|
|
138
|
-
if (!resolved.sortedSamples || resolved.sortedSamples.length === 0) {
|
|
139
|
-
console.error(
|
|
140
|
-
`Speedscope export: no samples in heap profile for "${name}", skipping`,
|
|
141
|
-
);
|
|
142
|
-
return {
|
|
143
|
-
type: "sampled",
|
|
144
|
-
name,
|
|
145
|
-
unit: "bytes",
|
|
146
|
-
startValue: 0,
|
|
147
|
-
endValue: 0,
|
|
148
|
-
samples,
|
|
149
|
-
weights,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
for (const sample of resolved.sortedSamples) {
|
|
154
|
-
const stack = nodeStacks.get(sample.nodeId);
|
|
155
|
-
if (stack) {
|
|
156
|
-
samples.push(stack);
|
|
157
|
-
weights.push(sample.size);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const totalBytes = weights.reduce((sum, w) => sum + w, 0);
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
type: "sampled",
|
|
165
|
-
name,
|
|
166
|
-
unit: "bytes",
|
|
167
|
-
startValue: 0,
|
|
168
|
-
endValue: totalBytes,
|
|
169
|
-
samples,
|
|
170
|
-
weights,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Intern a call frame, returning its index in the shared frames array */
|
|
175
|
-
function internFrame(
|
|
176
|
-
frame: ResolvedFrame,
|
|
177
|
-
sharedFrames: SpeedscopeFrame[],
|
|
178
|
-
frameIndex: Map<string, number>,
|
|
179
|
-
): number {
|
|
180
|
-
const { name, url, line, col } = frame;
|
|
181
|
-
const key = `${name}\0${url}\0${line}\0${col}`;
|
|
182
|
-
|
|
183
|
-
let idx = frameIndex.get(key);
|
|
184
|
-
if (idx === undefined) {
|
|
185
|
-
idx = sharedFrames.length;
|
|
186
|
-
// Match speedscope's convention: anonymous functions include location in name
|
|
187
|
-
const shortFile = url ? url.split("/").pop() : undefined;
|
|
188
|
-
const displayName =
|
|
189
|
-
name !== "(anonymous)"
|
|
190
|
-
? name
|
|
191
|
-
: shortFile
|
|
192
|
-
? `(anonymous ${shortFile}:${line})`
|
|
193
|
-
: "(anonymous)";
|
|
194
|
-
const entry: SpeedscopeFrame = { name: displayName };
|
|
195
|
-
if (url) entry.file = url;
|
|
196
|
-
if (line > 0) entry.line = line;
|
|
197
|
-
if (col != null) entry.col = col;
|
|
198
|
-
sharedFrames.push(entry);
|
|
199
|
-
frameIndex.set(key, idx);
|
|
200
|
-
}
|
|
201
|
-
return idx;
|
|
202
|
-
}
|