benchforge 0.1.0

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.
Files changed (98) hide show
  1. package/README.md +432 -0
  2. package/bin/benchforge +3 -0
  3. package/dist/bin/benchforge.mjs +9 -0
  4. package/dist/bin/benchforge.mjs.map +1 -0
  5. package/dist/browser/index.js +914 -0
  6. package/dist/index.mjs +3 -0
  7. package/dist/src-CGuaC3Wo.mjs +3676 -0
  8. package/dist/src-CGuaC3Wo.mjs.map +1 -0
  9. package/package.json +49 -0
  10. package/src/BenchMatrix.ts +380 -0
  11. package/src/Benchmark.ts +33 -0
  12. package/src/BenchmarkReport.ts +156 -0
  13. package/src/GitUtils.ts +79 -0
  14. package/src/HtmlDataPrep.ts +148 -0
  15. package/src/MeasuredResults.ts +127 -0
  16. package/src/NodeGC.ts +48 -0
  17. package/src/PermutationTest.ts +115 -0
  18. package/src/StandardSections.ts +268 -0
  19. package/src/StatisticalUtils.ts +176 -0
  20. package/src/TypeUtil.ts +8 -0
  21. package/src/bin/benchforge.ts +4 -0
  22. package/src/browser/BrowserGcStats.ts +44 -0
  23. package/src/browser/BrowserHeapSampler.ts +248 -0
  24. package/src/cli/CliArgs.ts +64 -0
  25. package/src/cli/FilterBenchmarks.ts +68 -0
  26. package/src/cli/RunBenchCLI.ts +856 -0
  27. package/src/export/JsonExport.ts +103 -0
  28. package/src/export/JsonFormat.ts +91 -0
  29. package/src/export/PerfettoExport.ts +203 -0
  30. package/src/heap-sample/HeapSampleReport.ts +196 -0
  31. package/src/heap-sample/HeapSampler.ts +78 -0
  32. package/src/html/HtmlReport.ts +131 -0
  33. package/src/html/HtmlTemplate.ts +284 -0
  34. package/src/html/Types.ts +88 -0
  35. package/src/html/browser/CIPlot.ts +287 -0
  36. package/src/html/browser/HistogramKde.ts +118 -0
  37. package/src/html/browser/LegendUtils.ts +163 -0
  38. package/src/html/browser/RenderPlots.ts +263 -0
  39. package/src/html/browser/SampleTimeSeries.ts +389 -0
  40. package/src/html/browser/Types.ts +96 -0
  41. package/src/html/browser/index.ts +1 -0
  42. package/src/html/index.ts +17 -0
  43. package/src/index.ts +92 -0
  44. package/src/matrix/CaseLoader.ts +36 -0
  45. package/src/matrix/MatrixFilter.ts +103 -0
  46. package/src/matrix/MatrixReport.ts +290 -0
  47. package/src/matrix/VariantLoader.ts +46 -0
  48. package/src/runners/AdaptiveWrapper.ts +391 -0
  49. package/src/runners/BasicRunner.ts +368 -0
  50. package/src/runners/BenchRunner.ts +60 -0
  51. package/src/runners/CreateRunner.ts +11 -0
  52. package/src/runners/GcStats.ts +107 -0
  53. package/src/runners/RunnerOrchestrator.ts +374 -0
  54. package/src/runners/RunnerUtils.ts +2 -0
  55. package/src/runners/TimingUtils.ts +13 -0
  56. package/src/runners/WorkerScript.ts +256 -0
  57. package/src/table-util/ConvergenceFormatters.ts +19 -0
  58. package/src/table-util/Formatters.ts +152 -0
  59. package/src/table-util/README.md +70 -0
  60. package/src/table-util/TableReport.ts +293 -0
  61. package/src/table-util/test/TableReport.test.ts +105 -0
  62. package/src/table-util/test/TableValueExtractor.test.ts +41 -0
  63. package/src/table-util/test/TableValueExtractor.ts +100 -0
  64. package/src/test/AdaptiveRunner.test.ts +185 -0
  65. package/src/test/AdaptiveStatistics.integration.ts +119 -0
  66. package/src/test/BenchmarkReport.test.ts +82 -0
  67. package/src/test/BrowserBench.e2e.test.ts +44 -0
  68. package/src/test/BrowserBench.test.ts +79 -0
  69. package/src/test/GcStats.test.ts +94 -0
  70. package/src/test/PermutationTest.test.ts +121 -0
  71. package/src/test/RunBenchCLI.test.ts +166 -0
  72. package/src/test/RunnerOrchestrator.test.ts +102 -0
  73. package/src/test/StatisticalUtils.test.ts +112 -0
  74. package/src/test/TestUtils.ts +93 -0
  75. package/src/test/fixtures/test-bench-script.ts +30 -0
  76. package/src/tests/AdaptiveConvergence.test.ts +177 -0
  77. package/src/tests/AdaptiveSampling.test.ts +240 -0
  78. package/src/tests/BenchMatrix.test.ts +366 -0
  79. package/src/tests/MatrixFilter.test.ts +117 -0
  80. package/src/tests/MatrixReport.test.ts +139 -0
  81. package/src/tests/RealDataValidation.test.ts +177 -0
  82. package/src/tests/fixtures/baseline/impl.ts +4 -0
  83. package/src/tests/fixtures/bevy30-samples.ts +158 -0
  84. package/src/tests/fixtures/cases/asyncCases.ts +7 -0
  85. package/src/tests/fixtures/cases/cases.ts +8 -0
  86. package/src/tests/fixtures/cases/variants/product.ts +2 -0
  87. package/src/tests/fixtures/cases/variants/sum.ts +2 -0
  88. package/src/tests/fixtures/discover/fast.ts +1 -0
  89. package/src/tests/fixtures/discover/slow.ts +4 -0
  90. package/src/tests/fixtures/invalid/bad.ts +1 -0
  91. package/src/tests/fixtures/loader/fast.ts +1 -0
  92. package/src/tests/fixtures/loader/slow.ts +4 -0
  93. package/src/tests/fixtures/loader/stateful.ts +2 -0
  94. package/src/tests/fixtures/stateful/stateful.ts +2 -0
  95. package/src/tests/fixtures/variants/extra.ts +1 -0
  96. package/src/tests/fixtures/variants/impl.ts +1 -0
  97. package/src/tests/fixtures/worker/fast.ts +1 -0
  98. package/src/tests/fixtures/worker/slow.ts +4 -0
@@ -0,0 +1,131 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { createServer, type Server } from "node:http";
3
+ import { dirname, extname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import open from "open";
6
+ import { generateHtmlDocument } from "./HtmlTemplate.ts";
7
+ import type {
8
+ HtmlReportOptions,
9
+ HtmlReportResult,
10
+ ReportData,
11
+ } from "./Types.ts";
12
+
13
+ /** Generate HTML report from prepared data and optionally open in browser */
14
+ export async function generateHtmlReport(
15
+ data: ReportData,
16
+ options: HtmlReportOptions,
17
+ ): Promise<HtmlReportResult> {
18
+ const html = generateHtmlDocument(data);
19
+
20
+ const reportDir = options.outputPath || (await createReportDir());
21
+ await mkdir(reportDir, { recursive: true });
22
+
23
+ await writeFile(join(reportDir, "index.html"), html, "utf-8");
24
+ const plots = await loadPlotsBundle();
25
+ await writeFile(join(reportDir, "plots.js"), plots, "utf-8");
26
+ await writeLatestRedirect(reportDir);
27
+
28
+ let server: Server | undefined;
29
+ let closeServer: (() => void) | undefined;
30
+
31
+ if (options.openBrowser) {
32
+ const baseDir = dirname(reportDir);
33
+ const reportName = reportDir.split("/").pop();
34
+ const result = await startReportServer(baseDir, 7979, 7978, 7977);
35
+ server = result.server;
36
+ closeServer = () => result.server.close();
37
+ const openUrl = `http://localhost:${result.port}/${reportName}/`;
38
+ await open(openUrl);
39
+ console.log(`Report opened in browser: ${openUrl}`);
40
+ } else {
41
+ console.log(`Report saved to: ${reportDir}/`);
42
+ }
43
+
44
+ return { reportDir, server, closeServer };
45
+ }
46
+
47
+ /** Start HTTP server for report directory, trying fallback ports if needed */
48
+ async function startReportServer(
49
+ baseDir: string,
50
+ ...ports: number[]
51
+ ): Promise<{ server: Server; port: number }> {
52
+ const mimeTypes: Record<string, string> = {
53
+ ".html": "text/html",
54
+ ".js": "application/javascript",
55
+ ".css": "text/css",
56
+ ".json": "application/json",
57
+ };
58
+
59
+ const server = createServer(async (req, res) => {
60
+ const url = req.url || "/";
61
+ const suffix = url.endsWith("/") ? url + "index.html" : url;
62
+ const filePath = join(baseDir, suffix);
63
+ try {
64
+ const content = await readFile(filePath);
65
+ const mime = mimeTypes[extname(filePath)] || "application/octet-stream";
66
+ res.setHeader("Content-Type", mime);
67
+ res.end(content);
68
+ } catch {
69
+ res.statusCode = 404;
70
+ res.end("Not found");
71
+ }
72
+ });
73
+
74
+ for (const port of ports) {
75
+ try {
76
+ return await tryListen(server, port);
77
+ } catch {
78
+ // Port in use, try next
79
+ }
80
+ }
81
+ return tryListen(server, 0);
82
+ }
83
+
84
+ /** Listen on a port, resolving with the actual port or rejecting on error */
85
+ function tryListen(
86
+ server: Server,
87
+ port: number,
88
+ ): Promise<{ server: Server; port: number }> {
89
+ return new Promise((resolve, reject) => {
90
+ server.once("error", reject);
91
+ server.listen(port, () => {
92
+ server.removeListener("error", reject);
93
+ const addr = server.address();
94
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
95
+ resolve({ server, port: actualPort });
96
+ });
97
+ });
98
+ }
99
+
100
+ /** Create a timestamped report directory under ./bench-report/ */
101
+ async function createReportDir(): Promise<string> {
102
+ const base = "./bench-report";
103
+ await mkdir(base, { recursive: true });
104
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
105
+ return join(base, `report-${ts}`);
106
+ }
107
+
108
+ /** Read the pre-built browser plots bundle from dist/ */
109
+ async function loadPlotsBundle(): Promise<string> {
110
+ const thisDir = dirname(fileURLToPath(import.meta.url));
111
+ const builtPath = join(thisDir, "browser/index.js");
112
+ const devPath = join(thisDir, "../../dist/browser/index.js");
113
+ try {
114
+ return await readFile(builtPath, "utf-8");
115
+ } catch {}
116
+ return readFile(devPath, "utf-8");
117
+ }
118
+
119
+ /** Write an index.html in the parent dir that redirects to this report */
120
+ async function writeLatestRedirect(reportDir: string): Promise<void> {
121
+ const baseDir = dirname(reportDir);
122
+ const reportName = reportDir.split("/").pop();
123
+ const html = `<!DOCTYPE html>
124
+ <html><head>
125
+ <meta http-equiv="refresh" content="0; url=./${reportName}/">
126
+ <script>location.href = "./${reportName}/";</script>
127
+ </head><body>
128
+ <a href="./${reportName}/">Latest report</a>
129
+ </body></html>`;
130
+ await writeFile(join(baseDir, "index.html"), html, "utf-8");
131
+ }
@@ -0,0 +1,284 @@
1
+ import type { GitVersion, GroupData, ReportData } from "./Types.ts";
2
+
3
+ const skipArgs = new Set(["_", "$0", "html", "export-html"]);
4
+
5
+ /** Format ISO date as local time with UTC: "Jan 9, 2026, 3:45 PM (2026-01-09T23:45:00Z)" */
6
+ export function formatDateWithTimezone(isoDate: string): string {
7
+ const date = new Date(isoDate);
8
+ const local = date.toLocaleString("en-US", {
9
+ month: "short",
10
+ day: "numeric",
11
+ year: "numeric",
12
+ hour: "numeric",
13
+ minute: "2-digit",
14
+ });
15
+ const utc = date.toISOString().replace(".000Z", "Z");
16
+ return `${local} (${utc})`;
17
+ }
18
+
19
+ /** Format relative time: "5m ago", "2h ago", "yesterday", "3 days ago" */
20
+ export function formatRelativeTime(isoDate: string): string {
21
+ const date = new Date(isoDate);
22
+ const now = new Date();
23
+ const diffMs = now.getTime() - date.getTime();
24
+ const diffMins = Math.floor(diffMs / 60000);
25
+ const diffHours = Math.floor(diffMs / 3600000);
26
+ const diffDays = Math.floor(diffMs / 86400000);
27
+
28
+ if (diffMins < 1) return "just now";
29
+ if (diffMins < 60) return `${diffMins}m ago`;
30
+ if (diffHours < 24) return `${diffHours}h ago`;
31
+ if (diffDays === 1) return "yesterday";
32
+ if (diffDays < 30) return `${diffDays} days ago`;
33
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
34
+ }
35
+
36
+ /** Format git version for display: "abc1234* (5m ago)" */
37
+ function formatVersion(version?: GitVersion): string {
38
+ if (!version || version.hash === "unknown") return "unknown";
39
+ const hashDisplay = version.dirty ? `${version.hash}*` : version.hash;
40
+ const timeDisplay = version.date ? formatRelativeTime(version.date) : "";
41
+ return timeDisplay ? `${hashDisplay} (${timeDisplay})` : hashDisplay;
42
+ }
43
+
44
+ /** Render current/baseline version info as an HTML div */
45
+ function versionInfoHtml(data: ReportData): string {
46
+ const { currentVersion, baselineVersion } = data.metadata;
47
+ if (!currentVersion && !baselineVersion) return "";
48
+ const parts: string[] = [];
49
+ if (currentVersion) parts.push(`Current: ${formatVersion(currentVersion)}`);
50
+ if (baselineVersion)
51
+ parts.push(`Baseline: ${formatVersion(baselineVersion)}`);
52
+ return `<div class="version-info">${parts.join(" | ")}</div>`;
53
+ }
54
+
55
+ const badgeLabels = {
56
+ faster: "Faster",
57
+ slower: "Slower",
58
+ uncertain: "Inconclusive",
59
+ };
60
+
61
+ /** Render faster/slower/uncertain badge with CI plot container */
62
+ function comparisonBadge(group: GroupData, groupIndex: number): string {
63
+ const ci = group.benchmarks[0]?.comparisonCI;
64
+ if (!ci) return "";
65
+ const label = badgeLabels[ci.direction];
66
+ return `
67
+ <span class="badge badge-${ci.direction}">${label}</span>
68
+ <div id="ci-plot-${groupIndex}" class="ci-plot-container"></div>
69
+ `;
70
+ }
71
+ const defaultArgs: Record<string, unknown> = {
72
+ worker: true,
73
+ time: 5,
74
+ warmup: 500,
75
+ "pause-interval": 0,
76
+ "pause-duration": 100,
77
+ };
78
+
79
+ /** @return true if this CLI arg should be hidden from the report header */
80
+ function shouldSkipArg(
81
+ key: string,
82
+ value: unknown,
83
+ adaptive: unknown,
84
+ ): boolean {
85
+ if (skipArgs.has(key) || value === undefined || value === false) return true;
86
+ if (defaultArgs[key] === value) return true;
87
+ if (!key.includes("-") && key !== key.toLowerCase()) return true; // skip yargs camelCase aliases
88
+ if (key === "convergence" && !adaptive) return true;
89
+ return false;
90
+ }
91
+
92
+ /** Reconstruct the CLI invocation string, omitting default/internal args */
93
+ function formatCliArgs(args?: Record<string, unknown>): string {
94
+ if (!args) return "bb bench";
95
+ const parts = ["bb bench"];
96
+ for (const [key, value] of Object.entries(args)) {
97
+ if (shouldSkipArg(key, value, args.adaptive)) continue;
98
+ parts.push(value === true ? `--${key}` : `--${key} ${value}`);
99
+ }
100
+ return parts.join(" ");
101
+ }
102
+
103
+ /** Generate complete HTML document with embedded data and visualizations */
104
+ export function generateHtmlDocument(data: ReportData): string {
105
+ return `<!DOCTYPE html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="UTF-8">
109
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
110
+ <title>Benchmark Report - ${new Date().toLocaleDateString()}</title>
111
+ <style>
112
+ * { margin: 0; padding: 0; box-sizing: border-box; }
113
+ body {
114
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
115
+ background: #f5f5f5;
116
+ padding: 20px;
117
+ line-height: 1.6;
118
+ }
119
+ .header {
120
+ background: white;
121
+ padding: 10px 15px;
122
+ border-radius: 8px;
123
+ margin-bottom: 20px;
124
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
125
+ display: flex;
126
+ justify-content: space-between;
127
+ align-items: center;
128
+ }
129
+ h1 { display: none; }
130
+ h2 {
131
+ color: #555;
132
+ margin: 30px 0 20px;
133
+ font-size: 20px;
134
+ border-bottom: 2px solid #e0e0e0;
135
+ padding-bottom: 10px;
136
+ }
137
+ .metadata { color: #666; font-size: 12px; }
138
+ .cli-args {
139
+ font-family: "SF Mono", Monaco, "Consolas", monospace;
140
+ font-size: 11px;
141
+ color: #555;
142
+ background: #f0f0f0;
143
+ padding: 6px 10px;
144
+ border-radius: 4px;
145
+ word-break: break-word;
146
+ }
147
+ .comparison-mode {
148
+ background: #fff3cd;
149
+ color: #856404;
150
+ padding: 8px 12px;
151
+ border-radius: 4px;
152
+ display: inline-block;
153
+ margin-top: 10px;
154
+ font-weight: 500;
155
+ }
156
+ .plot-grid {
157
+ display: grid;
158
+ grid-template-columns: 1fr 1fr;
159
+ gap: 20px;
160
+ margin-bottom: 30px;
161
+ }
162
+ .plot-grid.second-row { grid-template-columns: 1fr; }
163
+ .plot-container {
164
+ background: white;
165
+ padding: 20px;
166
+ border-radius: 8px;
167
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
168
+ }
169
+ .plot-container.full-width { grid-column: 1 / -1; }
170
+ .plot-title { font-size: 18px; font-weight: 600; margin-bottom: 8px; color: #333; }
171
+ .plot-description { font-size: 14px; color: #666; margin-bottom: 15px; }
172
+ .plot-area {
173
+ display: flex;
174
+ justify-content: center;
175
+ align-items: center;
176
+ min-height: 300px;
177
+ }
178
+ .plot-area svg { overflow: visible; }
179
+ .plot-area svg g[aria-label="x-axis label"] text { font-size: 14px; }
180
+ .summary-stats { background: #f8f9fa; padding: 15px; border-radius: 6px; margin-top: 20px; }
181
+ .stats-grid {
182
+ display: grid;
183
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
184
+ gap: 10px;
185
+ margin-top: 10px;
186
+ }
187
+ .stat-item { background: white; padding: 10px; border-radius: 4px; text-align: center; }
188
+ .stat-label { font-size: 12px; color: #666; text-transform: uppercase; letter-spacing: 0.5px; }
189
+ .stat-value { font-size: 18px; font-weight: 600; color: #333; margin-top: 4px; }
190
+ .loading { color: #666; font-style: italic; padding: 20px; text-align: center; }
191
+ .error { color: #d32f2f; background: #ffebee; padding: 15px; border-radius: 4px; margin: 10px 0; }
192
+ .ci-faster { color: #22c55e; }
193
+ .ci-slower { color: #ef4444; }
194
+ .ci-uncertain { color: #6b7280; }
195
+ .group-header {
196
+ display: flex;
197
+ align-items: center;
198
+ gap: 12px;
199
+ margin: 30px 0 20px;
200
+ padding-bottom: 10px;
201
+ border-bottom: 2px solid #e0e0e0;
202
+ }
203
+ .group-header h2 { margin: 0; border: none; padding: 0; }
204
+ .badge {
205
+ font-size: 12px;
206
+ font-weight: 600;
207
+ padding: 4px 10px;
208
+ border-radius: 12px;
209
+ text-transform: uppercase;
210
+ letter-spacing: 0.5px;
211
+ }
212
+ .badge-faster { background: #dcfce7; color: #166534; }
213
+ .badge-slower { background: #fee2e2; color: #991b1b; }
214
+ .badge-uncertain { background: #dbeafe; color: #1e40af; }
215
+ .version-info { font-size: 12px; color: #666; margin-top: 6px; }
216
+ .header-right { text-align: right; }
217
+ .ci-plot-container { display: inline-block; vertical-align: middle; margin-left: 8px; }
218
+ .ci-plot-container svg { display: block; }
219
+ </style>
220
+ </head>
221
+ <body>
222
+ <div class="header">
223
+ <div class="cli-args">${formatCliArgs(data.metadata.cliArgs)}</div>
224
+ <div class="header-right">
225
+ <div class="metadata">Generated: ${formatDateWithTimezone(new Date().toISOString())}</div>
226
+ ${versionInfoHtml(data)}
227
+ </div>
228
+ </div>
229
+
230
+ ${data.groups
231
+ .map(
232
+ (group, i) => `
233
+ <div id="group-${i}">
234
+ ${
235
+ group.benchmarks.length > 0
236
+ ? `
237
+ <div class="group-header">
238
+ <h2>${group.name}</h2>
239
+ ${comparisonBadge(group, i)}
240
+ </div>
241
+
242
+ <div class="plot-grid">
243
+ <div class="plot-container">
244
+ <div class="plot-title">Time per Sample</div>
245
+ <div class="plot-description">Execution time for each sample in collection order</div>
246
+ <div id="sample-timeseries-${i}" class="plot-area">
247
+ <div class="loading">Loading time series...</div>
248
+ </div>
249
+ </div>
250
+
251
+ <div class="plot-container">
252
+ <div class="plot-title">Time Distribution</div>
253
+ <div class="plot-description">Frequency distribution of execution times</div>
254
+ <div id="histogram-${i}" class="plot-area">
255
+ <div class="loading">Loading histogram...</div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <div id="stats-${i}"></div>
261
+ `
262
+ : '<div class="error">No benchmark data available for this group</div>'
263
+ }
264
+ </div>
265
+ `,
266
+ )
267
+ .join("")}
268
+
269
+ <script type="importmap">
270
+ {
271
+ "imports": {
272
+ "d3": "https://cdn.jsdelivr.net/npm/d3@7/+esm",
273
+ "@observablehq/plot": "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm"
274
+ }
275
+ }
276
+ </script>
277
+ <script type="module">
278
+ import { renderPlots } from "./plots.js";
279
+ const benchmarkData = ${JSON.stringify(data, null, 2)};
280
+ renderPlots(benchmarkData);
281
+ </script>
282
+ </body>
283
+ </html>`;
284
+ }
@@ -0,0 +1,88 @@
1
+ /** Data passed to the HTML report generator */
2
+ export interface ReportData {
3
+ groups: GroupData[];
4
+ metadata: {
5
+ timestamp: string;
6
+ bencherVersion: string;
7
+ cliArgs?: Record<string, unknown>;
8
+ gcTrackingEnabled?: boolean;
9
+ currentVersion?: GitVersion;
10
+ baselineVersion?: GitVersion;
11
+ };
12
+ }
13
+
14
+ export interface GroupData {
15
+ name: string;
16
+ baseline?: BenchmarkData;
17
+ benchmarks: BenchmarkData[];
18
+ }
19
+
20
+ export interface BenchmarkData {
21
+ name: string;
22
+ samples: number[];
23
+ warmupSamples?: number[];
24
+ allocationSamples?: number[];
25
+ heapSamples?: number[];
26
+ gcEvents?: GcEvent[];
27
+ optSamples?: number[];
28
+ pausePoints?: PausePoint[];
29
+ stats: {
30
+ min: number;
31
+ max: number;
32
+ avg: number;
33
+ p50: number;
34
+ p75: number;
35
+ p99: number;
36
+ p999: number;
37
+ };
38
+ heapSize?: { min: number; max: number; avg: number };
39
+ sectionStats?: FormattedStat[];
40
+ comparisonCI?: DifferenceCI;
41
+ }
42
+
43
+ export interface FormattedStat {
44
+ label: string;
45
+ value: string;
46
+ groupTitle?: string;
47
+ }
48
+
49
+ export interface GcEvent {
50
+ offset: number;
51
+ duration: number;
52
+ }
53
+
54
+ export interface PausePoint {
55
+ sampleIndex: number;
56
+ durationMs: number;
57
+ }
58
+
59
+ export interface GitVersion {
60
+ hash: string;
61
+ date: string;
62
+ dirty?: boolean;
63
+ }
64
+
65
+ export type CIDirection = "faster" | "slower" | "uncertain";
66
+
67
+ export interface HistogramBin {
68
+ x: number;
69
+ count: number;
70
+ }
71
+
72
+ export interface DifferenceCI {
73
+ percent: number;
74
+ ci: [number, number];
75
+ direction: CIDirection;
76
+ histogram?: HistogramBin[];
77
+ }
78
+
79
+ export interface HtmlReportOptions {
80
+ openBrowser: boolean;
81
+ outputPath?: string;
82
+ }
83
+
84
+ export interface HtmlReportResult {
85
+ reportDir: string;
86
+ server?: import("node:http").Server;
87
+ closeServer?: () => void;
88
+ }