benchforge 0.1.9 → 0.1.11
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/README.md +40 -6
- package/dist/{BenchRunner-CSKN9zPy.d.mts → BenchRunner-BzyUfiyB.d.mts} +32 -8
- package/dist/{BrowserHeapSampler-DCeL42RE.mjs → BrowserHeapSampler-B6asLKWQ.mjs} +57 -57
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs.map +1 -0
- package/dist/{GcStats-ByEovUi1.mjs → GcStats-wX7Xyblu.mjs} +15 -15
- package/dist/GcStats-wX7Xyblu.mjs.map +1 -0
- package/dist/HeapSampler-B8dtKHn1.mjs.map +1 -1
- package/dist/{TimingUtils-ClclVQ7E.mjs → TimingUtils-DwOwkc8G.mjs} +225 -225
- package/dist/TimingUtils-DwOwkc8G.mjs.map +1 -0
- package/dist/bin/benchforge.mjs +1 -1
- package/dist/browser/index.js +210 -210
- package/dist/index.d.mts +102 -46
- package/dist/index.mjs +3 -3
- package/dist/runners/WorkerScript.d.mts +1 -1
- package/dist/runners/WorkerScript.mjs +66 -66
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/{src-Cf_LXwlp.mjs → src-B-DDaCa9.mjs} +1225 -990
- package/dist/src-B-DDaCa9.mjs.map +1 -0
- package/package.json +2 -1
- package/src/BenchMatrix.ts +125 -125
- package/src/BenchmarkReport.ts +50 -45
- package/src/HtmlDataPrep.ts +21 -21
- package/src/PermutationTest.ts +24 -24
- package/src/StandardSections.ts +45 -45
- package/src/StatisticalUtils.ts +60 -61
- package/src/browser/BrowserGcStats.ts +5 -5
- package/src/browser/BrowserHeapSampler.ts +63 -63
- package/src/cli/CliArgs.ts +6 -3
- package/src/cli/FilterBenchmarks.ts +5 -5
- package/src/cli/RunBenchCLI.ts +526 -498
- package/src/export/JsonExport.ts +10 -10
- package/src/export/PerfettoExport.ts +74 -74
- package/src/export/SpeedscopeExport.ts +202 -0
- package/src/heap-sample/HeapSampleReport.ts +143 -70
- package/src/heap-sample/HeapSampler.ts +55 -12
- package/src/heap-sample/ResolvedProfile.ts +89 -0
- package/src/html/HtmlReport.ts +33 -33
- package/src/html/HtmlTemplate.ts +67 -67
- package/src/html/browser/CIPlot.ts +50 -50
- package/src/html/browser/HistogramKde.ts +13 -13
- package/src/html/browser/LegendUtils.ts +48 -48
- package/src/html/browser/RenderPlots.ts +98 -98
- package/src/html/browser/SampleTimeSeries.ts +79 -79
- package/src/index.ts +6 -0
- package/src/matrix/MatrixFilter.ts +6 -6
- package/src/matrix/MatrixReport.ts +96 -96
- package/src/matrix/VariantLoader.ts +5 -5
- package/src/runners/AdaptiveWrapper.ts +151 -151
- package/src/runners/BasicRunner.ts +175 -175
- package/src/runners/BenchRunner.ts +8 -8
- package/src/runners/GcStats.ts +22 -22
- package/src/runners/RunnerOrchestrator.ts +168 -168
- package/src/runners/WorkerScript.ts +96 -96
- package/src/table-util/Formatters.ts +41 -36
- package/src/table-util/TableReport.ts +122 -122
- package/src/table-util/test/TableValueExtractor.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +7 -39
- package/src/test/HeapAttribution.test.ts +51 -0
- package/src/test/RunBenchCLI.test.ts +18 -18
- package/src/test/TestUtils.ts +24 -24
- package/src/tests/BenchMatrix.test.ts +12 -12
- package/src/tests/MatrixFilter.test.ts +15 -15
- package/dist/BrowserHeapSampler-DCeL42RE.mjs.map +0 -1
- package/dist/GcStats-ByEovUi1.mjs.map +0 -1
- package/dist/TimingUtils-ClclVQ7E.mjs.map +0 -1
- package/dist/src-Cf_LXwlp.mjs.map +0 -1
package/dist/browser/index.js
CHANGED
|
@@ -2,6 +2,12 @@ import * as Plot from "@observablehq/plot";
|
|
|
2
2
|
import * as d3 from "d3";
|
|
3
3
|
|
|
4
4
|
//#region src/html/browser/CIPlot.ts
|
|
5
|
+
const defaultMargin = {
|
|
6
|
+
top: 22,
|
|
7
|
+
right: 12,
|
|
8
|
+
bottom: 22,
|
|
9
|
+
left: 12
|
|
10
|
+
};
|
|
5
11
|
const defaultOpts = {
|
|
6
12
|
width: 260,
|
|
7
13
|
height: 85,
|
|
@@ -23,12 +29,7 @@ const colors = {
|
|
|
23
29
|
stroke: "#3b82f6"
|
|
24
30
|
}
|
|
25
31
|
};
|
|
26
|
-
const
|
|
27
|
-
top: 22,
|
|
28
|
-
right: 12,
|
|
29
|
-
bottom: 22,
|
|
30
|
-
left: 12
|
|
31
|
-
};
|
|
32
|
+
const formatPct$1 = (v) => (v >= 0 ? "+" : "") + v.toFixed(0) + "%";
|
|
32
33
|
/** Create a small distribution plot showing histogram with CI shading */
|
|
33
34
|
function createDistributionPlot(histogram, ci, pointEstimate, options = {}) {
|
|
34
35
|
const opts = {
|
|
@@ -68,6 +69,13 @@ function buildLayout(width, height) {
|
|
|
68
69
|
}
|
|
69
70
|
};
|
|
70
71
|
}
|
|
72
|
+
function createSvg(w, h) {
|
|
73
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
74
|
+
svg.setAttribute("width", String(w));
|
|
75
|
+
svg.setAttribute("height", String(h));
|
|
76
|
+
if (w && h) svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
77
|
+
return svg;
|
|
78
|
+
}
|
|
71
79
|
/** Compute x/y scale functions mapping data values to SVG coordinates */
|
|
72
80
|
function buildScales(histogram, ci, layout) {
|
|
73
81
|
const { margin, plot } = layout;
|
|
@@ -140,6 +148,26 @@ function drawCILabels(svg, ci, scales, height) {
|
|
|
140
148
|
svg.appendChild(text(scales.x(ci[0]), height - 4, formatPct$1(ci[0]), "middle", "12"));
|
|
141
149
|
svg.appendChild(text(scales.x(ci[1]), height - 4, formatPct$1(ci[1]), "middle", "12"));
|
|
142
150
|
}
|
|
151
|
+
function text(x, y, content, anchor = "start", size = "9", fill = "#666", weight = "400") {
|
|
152
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
153
|
+
el.setAttribute("x", String(x));
|
|
154
|
+
el.setAttribute("y", String(y));
|
|
155
|
+
el.setAttribute("text-anchor", anchor);
|
|
156
|
+
el.setAttribute("font-size", size);
|
|
157
|
+
el.setAttribute("font-weight", weight);
|
|
158
|
+
el.setAttribute("fill", fill);
|
|
159
|
+
el.textContent = content;
|
|
160
|
+
return el;
|
|
161
|
+
}
|
|
162
|
+
function rect(x, y, w, h, attrs) {
|
|
163
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
164
|
+
el.setAttribute("x", String(x));
|
|
165
|
+
el.setAttribute("y", String(y));
|
|
166
|
+
el.setAttribute("width", String(w));
|
|
167
|
+
el.setAttribute("height", String(h));
|
|
168
|
+
setAttrs(el, attrs);
|
|
169
|
+
return el;
|
|
170
|
+
}
|
|
143
171
|
/** Apply gaussian kernel smoothing to histogram bins */
|
|
144
172
|
function gaussianSmooth(bins, sigma) {
|
|
145
173
|
return bins.map((bin, i) => {
|
|
@@ -156,23 +184,6 @@ function gaussianSmooth(bins, sigma) {
|
|
|
156
184
|
};
|
|
157
185
|
});
|
|
158
186
|
}
|
|
159
|
-
const formatPct$1 = (v) => (v >= 0 ? "+" : "") + v.toFixed(0) + "%";
|
|
160
|
-
function createSvg(w, h) {
|
|
161
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
162
|
-
svg.setAttribute("width", String(w));
|
|
163
|
-
svg.setAttribute("height", String(h));
|
|
164
|
-
if (w && h) svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
165
|
-
return svg;
|
|
166
|
-
}
|
|
167
|
-
function rect(x, y, w, h, attrs) {
|
|
168
|
-
const el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
169
|
-
el.setAttribute("x", String(x));
|
|
170
|
-
el.setAttribute("y", String(y));
|
|
171
|
-
el.setAttribute("width", String(w));
|
|
172
|
-
el.setAttribute("height", String(h));
|
|
173
|
-
setAttrs(el, attrs);
|
|
174
|
-
return el;
|
|
175
|
-
}
|
|
176
187
|
function path(d, attrs) {
|
|
177
188
|
const el = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
178
189
|
el.setAttribute("d", d);
|
|
@@ -188,17 +199,6 @@ function line(x1, y1, x2, y2, attrs) {
|
|
|
188
199
|
setAttrs(el, attrs);
|
|
189
200
|
return el;
|
|
190
201
|
}
|
|
191
|
-
function text(x, y, content, anchor = "start", size = "9", fill = "#666", weight = "400") {
|
|
192
|
-
const el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
193
|
-
el.setAttribute("x", String(x));
|
|
194
|
-
el.setAttribute("y", String(y));
|
|
195
|
-
el.setAttribute("text-anchor", anchor);
|
|
196
|
-
el.setAttribute("font-size", size);
|
|
197
|
-
el.setAttribute("font-weight", weight);
|
|
198
|
-
el.setAttribute("fill", fill);
|
|
199
|
-
el.textContent = content;
|
|
200
|
-
return el;
|
|
201
|
-
}
|
|
202
202
|
/** Set SVG attributes, converting camelCase keys to kebab-case */
|
|
203
203
|
function setAttrs(el, attrs) {
|
|
204
204
|
for (const [k, v] of Object.entries(attrs)) el.setAttribute(k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()), v);
|
|
@@ -206,6 +206,25 @@ function setAttrs(el, attrs) {
|
|
|
206
206
|
|
|
207
207
|
//#endregion
|
|
208
208
|
//#region src/html/browser/LegendUtils.ts
|
|
209
|
+
/** Build complete legend marks array */
|
|
210
|
+
function buildLegend(bounds, items) {
|
|
211
|
+
const xRange = bounds.xMax - bounds.xMin;
|
|
212
|
+
const legendX = bounds.xMin + xRange * .68;
|
|
213
|
+
const textX = legendX + xRange * .04;
|
|
214
|
+
const getY = (i) => bounds.yMax * .98 - i * (bounds.yMax * .08);
|
|
215
|
+
const marks = [legendBackground(bounds)];
|
|
216
|
+
for (let i = 0; i < items.length; i++) {
|
|
217
|
+
const pos = {
|
|
218
|
+
legendX,
|
|
219
|
+
y: getY(i),
|
|
220
|
+
textX,
|
|
221
|
+
xRange,
|
|
222
|
+
yMax: bounds.yMax
|
|
223
|
+
};
|
|
224
|
+
marks.push(symbolMark(pos, items[i]), textMark(pos, items[i].label));
|
|
225
|
+
}
|
|
226
|
+
return marks;
|
|
227
|
+
}
|
|
209
228
|
/** Draw a semi-transparent white background behind the legend area */
|
|
210
229
|
function legendBackground(bounds) {
|
|
211
230
|
const xRange = bounds.xMax - bounds.xMin;
|
|
@@ -226,6 +245,30 @@ function legendBackground(bounds) {
|
|
|
226
245
|
strokeWidth: 1
|
|
227
246
|
});
|
|
228
247
|
}
|
|
248
|
+
function symbolMark(pos, item) {
|
|
249
|
+
switch (item.style) {
|
|
250
|
+
case "filled-dot": return dotMark(pos.legendX, pos.y, item.color, true);
|
|
251
|
+
case "hollow-dot": return dotMark(pos.legendX, pos.y, item.color, false);
|
|
252
|
+
case "vertical-bar": return verticalBarMark(pos, item.color);
|
|
253
|
+
case "vertical-line": return verticalLineMark(pos, item.color, item.strokeDash);
|
|
254
|
+
case "rect": return rectMark(pos, item.color);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function textMark(pos, label) {
|
|
258
|
+
const data = [{
|
|
259
|
+
x: pos.textX,
|
|
260
|
+
y: pos.y,
|
|
261
|
+
text: label
|
|
262
|
+
}];
|
|
263
|
+
return Plot.text(data, {
|
|
264
|
+
x: "x",
|
|
265
|
+
y: "y",
|
|
266
|
+
text: "text",
|
|
267
|
+
fontSize: 11,
|
|
268
|
+
textAnchor: "start",
|
|
269
|
+
fill: "#333"
|
|
270
|
+
});
|
|
271
|
+
}
|
|
229
272
|
function dotMark(x, y, color, filled) {
|
|
230
273
|
return Plot.dot([{
|
|
231
274
|
x,
|
|
@@ -293,49 +336,6 @@ function rectMark(pos, color) {
|
|
|
293
336
|
};
|
|
294
337
|
return Plot.rect(data, opts);
|
|
295
338
|
}
|
|
296
|
-
function symbolMark(pos, item) {
|
|
297
|
-
switch (item.style) {
|
|
298
|
-
case "filled-dot": return dotMark(pos.legendX, pos.y, item.color, true);
|
|
299
|
-
case "hollow-dot": return dotMark(pos.legendX, pos.y, item.color, false);
|
|
300
|
-
case "vertical-bar": return verticalBarMark(pos, item.color);
|
|
301
|
-
case "vertical-line": return verticalLineMark(pos, item.color, item.strokeDash);
|
|
302
|
-
case "rect": return rectMark(pos, item.color);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function textMark(pos, label) {
|
|
306
|
-
const data = [{
|
|
307
|
-
x: pos.textX,
|
|
308
|
-
y: pos.y,
|
|
309
|
-
text: label
|
|
310
|
-
}];
|
|
311
|
-
return Plot.text(data, {
|
|
312
|
-
x: "x",
|
|
313
|
-
y: "y",
|
|
314
|
-
text: "text",
|
|
315
|
-
fontSize: 11,
|
|
316
|
-
textAnchor: "start",
|
|
317
|
-
fill: "#333"
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
/** Build complete legend marks array */
|
|
321
|
-
function buildLegend(bounds, items) {
|
|
322
|
-
const xRange = bounds.xMax - bounds.xMin;
|
|
323
|
-
const legendX = bounds.xMin + xRange * .68;
|
|
324
|
-
const textX = legendX + xRange * .04;
|
|
325
|
-
const getY = (i) => bounds.yMax * .98 - i * (bounds.yMax * .08);
|
|
326
|
-
const marks = [legendBackground(bounds)];
|
|
327
|
-
for (let i = 0; i < items.length; i++) {
|
|
328
|
-
const pos = {
|
|
329
|
-
legendX,
|
|
330
|
-
y: getY(i),
|
|
331
|
-
textX,
|
|
332
|
-
xRange,
|
|
333
|
-
yMax: bounds.yMax
|
|
334
|
-
};
|
|
335
|
-
marks.push(symbolMark(pos, items[i]), textMark(pos, items[i].label));
|
|
336
|
-
}
|
|
337
|
-
return marks;
|
|
338
|
-
}
|
|
339
339
|
|
|
340
340
|
//#endregion
|
|
341
341
|
//#region src/html/browser/HistogramKde.ts
|
|
@@ -386,17 +386,6 @@ function createHistogramKde(allSamples, benchmarkNames) {
|
|
|
386
386
|
]
|
|
387
387
|
});
|
|
388
388
|
}
|
|
389
|
-
function buildColorData(benchmarkNames) {
|
|
390
|
-
const scheme = d3.schemeObservable10;
|
|
391
|
-
return {
|
|
392
|
-
colorMap: new Map(benchmarkNames.map((name, i) => [name, scheme[i % 10]])),
|
|
393
|
-
legendItems: benchmarkNames.map((name, i) => ({
|
|
394
|
-
color: scheme[i % 10],
|
|
395
|
-
label: name,
|
|
396
|
-
style: "vertical-bar"
|
|
397
|
-
}))
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
389
|
/** Bin samples into grouped histogram bars for each benchmark */
|
|
401
390
|
function buildBarData(allSamples, benchmarkNames) {
|
|
402
391
|
const sorted = allSamples.map((d) => d.value).sort((a, b) => a - b);
|
|
@@ -436,6 +425,17 @@ function buildBarData(allSamples, benchmarkNames) {
|
|
|
436
425
|
yMax: (d3.max(barData, (d) => d.count) || 1) * 1.15
|
|
437
426
|
};
|
|
438
427
|
}
|
|
428
|
+
function buildColorData(benchmarkNames) {
|
|
429
|
+
const scheme = d3.schemeObservable10;
|
|
430
|
+
return {
|
|
431
|
+
colorMap: new Map(benchmarkNames.map((name, i) => [name, scheme[i % 10]])),
|
|
432
|
+
legendItems: benchmarkNames.map((name, i) => ({
|
|
433
|
+
color: scheme[i % 10],
|
|
434
|
+
label: name,
|
|
435
|
+
style: "vertical-bar"
|
|
436
|
+
}))
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
439
|
|
|
440
440
|
//#endregion
|
|
441
441
|
//#region src/html/browser/SampleTimeSeries.ts
|
|
@@ -531,59 +531,6 @@ function buildPlotContext(timeSeries) {
|
|
|
531
531
|
benchmarks
|
|
532
532
|
};
|
|
533
533
|
}
|
|
534
|
-
function buildSampleData(timeSeries, benchmarks) {
|
|
535
|
-
const result = [];
|
|
536
|
-
for (const benchmark of benchmarks) {
|
|
537
|
-
const isBaseline = benchmark.includes("(baseline)");
|
|
538
|
-
for (const d of timeSeries.filter((t) => t.benchmark === benchmark)) {
|
|
539
|
-
const optTier = d.optStatus !== void 0 ? OPT_STATUS_NAMES[d.optStatus] || "unknown" : null;
|
|
540
|
-
result.push({
|
|
541
|
-
benchmark,
|
|
542
|
-
sample: d.iteration,
|
|
543
|
-
value: d.value,
|
|
544
|
-
isBaseline,
|
|
545
|
-
isWarmup: d.isWarmup || false,
|
|
546
|
-
optTier
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return result;
|
|
551
|
-
}
|
|
552
|
-
/** Pick display unit (ns/us/ms) based on average value magnitude */
|
|
553
|
-
function getTimeUnit(values) {
|
|
554
|
-
const avg = d3.mean(values);
|
|
555
|
-
const fmt0 = (d) => d3.format(",.0f")(d);
|
|
556
|
-
const fmt1 = (d) => d3.format(",.1f")(d);
|
|
557
|
-
if (avg < .001) return {
|
|
558
|
-
unitSuffix: "ns",
|
|
559
|
-
convertValue: (ms) => ms * 1e6,
|
|
560
|
-
formatValue: fmt0
|
|
561
|
-
};
|
|
562
|
-
if (avg < 1) return {
|
|
563
|
-
unitSuffix: "μs",
|
|
564
|
-
convertValue: (ms) => ms * 1e3,
|
|
565
|
-
formatValue: fmt1
|
|
566
|
-
};
|
|
567
|
-
return {
|
|
568
|
-
unitSuffix: "ms",
|
|
569
|
-
convertValue: (ms) => ms,
|
|
570
|
-
formatValue: fmt1
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
/** Compute Y axis range with padding, snapping yMin to a round number */
|
|
574
|
-
function computeYRange(values) {
|
|
575
|
-
const dataMin = d3.min(values);
|
|
576
|
-
const dataMax = d3.max(values);
|
|
577
|
-
const dataRange = dataMax - dataMin;
|
|
578
|
-
let yMin = dataMin - dataRange * .15;
|
|
579
|
-
const magnitude = 10 ** Math.floor(Math.log10(Math.abs(yMin)));
|
|
580
|
-
yMin = Math.floor(yMin / magnitude) * magnitude;
|
|
581
|
-
if (dataMin > 0 && yMin < 0) yMin = 0;
|
|
582
|
-
return {
|
|
583
|
-
yMin,
|
|
584
|
-
yMax: dataMax + dataRange * .05
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
534
|
/** Scale heap byte values into the plot's Y coordinate range */
|
|
588
535
|
function prepareHeapData(heapSeries, yMin, yMax) {
|
|
589
536
|
if (heapSeries.length === 0) return [];
|
|
@@ -721,6 +668,59 @@ function buildLegendItems(hasWarmup, gcCount, pauseCount, hasHeap, optTiers, ben
|
|
|
721
668
|
}
|
|
722
669
|
return items;
|
|
723
670
|
}
|
|
671
|
+
function buildSampleData(timeSeries, benchmarks) {
|
|
672
|
+
const result = [];
|
|
673
|
+
for (const benchmark of benchmarks) {
|
|
674
|
+
const isBaseline = benchmark.includes("(baseline)");
|
|
675
|
+
for (const d of timeSeries.filter((t) => t.benchmark === benchmark)) {
|
|
676
|
+
const optTier = d.optStatus !== void 0 ? OPT_STATUS_NAMES[d.optStatus] || "unknown" : null;
|
|
677
|
+
result.push({
|
|
678
|
+
benchmark,
|
|
679
|
+
sample: d.iteration,
|
|
680
|
+
value: d.value,
|
|
681
|
+
isBaseline,
|
|
682
|
+
isWarmup: d.isWarmup || false,
|
|
683
|
+
optTier
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
/** Pick display unit (ns/us/ms) based on average value magnitude */
|
|
690
|
+
function getTimeUnit(values) {
|
|
691
|
+
const avg = d3.mean(values);
|
|
692
|
+
const fmt0 = (d) => d3.format(",.0f")(d);
|
|
693
|
+
const fmt1 = (d) => d3.format(",.1f")(d);
|
|
694
|
+
if (avg < .001) return {
|
|
695
|
+
unitSuffix: "ns",
|
|
696
|
+
convertValue: (ms) => ms * 1e6,
|
|
697
|
+
formatValue: fmt0
|
|
698
|
+
};
|
|
699
|
+
if (avg < 1) return {
|
|
700
|
+
unitSuffix: "μs",
|
|
701
|
+
convertValue: (ms) => ms * 1e3,
|
|
702
|
+
formatValue: fmt1
|
|
703
|
+
};
|
|
704
|
+
return {
|
|
705
|
+
unitSuffix: "ms",
|
|
706
|
+
convertValue: (ms) => ms,
|
|
707
|
+
formatValue: fmt1
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
/** Compute Y axis range with padding, snapping yMin to a round number */
|
|
711
|
+
function computeYRange(values) {
|
|
712
|
+
const dataMin = d3.min(values);
|
|
713
|
+
const dataMax = d3.max(values);
|
|
714
|
+
const dataRange = dataMax - dataMin;
|
|
715
|
+
let yMin = dataMin - dataRange * .15;
|
|
716
|
+
const magnitude = 10 ** Math.floor(Math.log10(Math.abs(yMin)));
|
|
717
|
+
yMin = Math.floor(yMin / magnitude) * magnitude;
|
|
718
|
+
if (dataMin > 0 && yMin < 0) yMin = 0;
|
|
719
|
+
return {
|
|
720
|
+
yMin,
|
|
721
|
+
yMax: dataMax + dataRange * .05
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
724
|
|
|
725
725
|
//#endregion
|
|
726
726
|
//#region src/html/browser/RenderPlots.ts
|
|
@@ -752,12 +752,9 @@ function renderGroup(group, groupIndex, gcEnabled) {
|
|
|
752
752
|
const statsContainer = document.querySelector(`#stats-${groupIndex}`);
|
|
753
753
|
if (statsContainer) statsContainer.innerHTML = benchmarks.map((b) => generateStatsHtml(b, gcEnabled)).join("");
|
|
754
754
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (!container || !condition) return;
|
|
759
|
-
container.innerHTML = "";
|
|
760
|
-
container.appendChild(create());
|
|
755
|
+
function showError(groupIndex, message) {
|
|
756
|
+
const container = document.querySelector(`#group-${groupIndex}`);
|
|
757
|
+
if (container) container.innerHTML = `<div class="error">${message}</div>`;
|
|
761
758
|
}
|
|
762
759
|
/** Combine baseline and benchmarks into a single list with display names */
|
|
763
760
|
function prepareBenchmarks(group) {
|
|
@@ -787,6 +784,62 @@ function flattenSamples(benchmarks) {
|
|
|
787
784
|
for (const b of benchmarks) if (b.samples?.length) flattenBenchmark(b, result);
|
|
788
785
|
return result;
|
|
789
786
|
}
|
|
787
|
+
/** Clear a container element and append a freshly created plot */
|
|
788
|
+
function renderToContainer(selector, condition, create) {
|
|
789
|
+
const container = document.querySelector(selector);
|
|
790
|
+
if (!container || !condition) return;
|
|
791
|
+
container.innerHTML = "";
|
|
792
|
+
container.appendChild(create());
|
|
793
|
+
}
|
|
794
|
+
function generateStatsHtml(b, gcEnabled) {
|
|
795
|
+
const ciHtml = generateCIHtml(b.comparisonCI);
|
|
796
|
+
if (b.sectionStats?.length) {
|
|
797
|
+
const statsHtml = (gcEnabled ? b.sectionStats : b.sectionStats.filter((s) => s.groupTitle !== "gc")).map((stat) => `
|
|
798
|
+
<div class="stat-item">
|
|
799
|
+
<div class="stat-label">${stat.groupTitle ? stat.groupTitle + " " : ""}${stat.label}</div>
|
|
800
|
+
<div class="stat-value">${stat.value}</div>
|
|
801
|
+
</div>
|
|
802
|
+
`).join("");
|
|
803
|
+
return `
|
|
804
|
+
<div class="summary-stats">
|
|
805
|
+
<h3 style="margin-bottom: 10px; color: #333;">${b.name}</h3>
|
|
806
|
+
<div class="stats-grid">${ciHtml}${statsHtml}</div>
|
|
807
|
+
</div>
|
|
808
|
+
`;
|
|
809
|
+
}
|
|
810
|
+
return `
|
|
811
|
+
<div class="summary-stats">
|
|
812
|
+
<h3 style="margin-bottom: 10px; color: #333;">${b.name}</h3>
|
|
813
|
+
<div class="stats-grid">
|
|
814
|
+
${ciHtml}
|
|
815
|
+
<div class="stat-item">
|
|
816
|
+
<div class="stat-label">Min</div>
|
|
817
|
+
<div class="stat-value">${b.stats.min.toFixed(3)}ms</div>
|
|
818
|
+
</div>
|
|
819
|
+
<div class="stat-item">
|
|
820
|
+
<div class="stat-label">Median</div>
|
|
821
|
+
<div class="stat-value">${b.stats.p50.toFixed(3)}ms</div>
|
|
822
|
+
</div>
|
|
823
|
+
<div class="stat-item">
|
|
824
|
+
<div class="stat-label">Mean</div>
|
|
825
|
+
<div class="stat-value">${b.stats.avg.toFixed(3)}ms</div>
|
|
826
|
+
</div>
|
|
827
|
+
<div class="stat-item">
|
|
828
|
+
<div class="stat-label">Max</div>
|
|
829
|
+
<div class="stat-value">${b.stats.max.toFixed(3)}ms</div>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="stat-item">
|
|
832
|
+
<div class="stat-label">P75</div>
|
|
833
|
+
<div class="stat-value">${b.stats.p75.toFixed(3)}ms</div>
|
|
834
|
+
</div>
|
|
835
|
+
<div class="stat-item">
|
|
836
|
+
<div class="stat-label">P99</div>
|
|
837
|
+
<div class="stat-value">${b.stats.p99.toFixed(3)}ms</div>
|
|
838
|
+
</div>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
`;
|
|
842
|
+
}
|
|
790
843
|
/** Extract time series, heap, GC, and pause data from one benchmark */
|
|
791
844
|
function flattenBenchmark(b, out) {
|
|
792
845
|
const warmupCount = b.warmupSamples?.length || 0;
|
|
@@ -834,22 +887,6 @@ function flattenBenchmark(b, out) {
|
|
|
834
887
|
});
|
|
835
888
|
});
|
|
836
889
|
}
|
|
837
|
-
function cumulativeSum(arr) {
|
|
838
|
-
const result = [];
|
|
839
|
-
let sum = 0;
|
|
840
|
-
for (const v of arr) {
|
|
841
|
-
sum += v;
|
|
842
|
-
result.push(sum);
|
|
843
|
-
}
|
|
844
|
-
return result;
|
|
845
|
-
}
|
|
846
|
-
function showError(groupIndex, message) {
|
|
847
|
-
const container = document.querySelector(`#group-${groupIndex}`);
|
|
848
|
-
if (container) container.innerHTML = `<div class="error">${message}</div>`;
|
|
849
|
-
}
|
|
850
|
-
function formatPct(v) {
|
|
851
|
-
return (v >= 0 ? "+" : "") + v.toFixed(1) + "%";
|
|
852
|
-
}
|
|
853
890
|
function generateCIHtml(ci) {
|
|
854
891
|
if (!ci) return "";
|
|
855
892
|
const text = `${formatPct(ci.percent)} [${formatPct(ci.ci[0])}, ${formatPct(ci.ci[1])}]`;
|
|
@@ -860,54 +897,17 @@ function generateCIHtml(ci) {
|
|
|
860
897
|
</div>
|
|
861
898
|
`;
|
|
862
899
|
}
|
|
863
|
-
function
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
<div class="stat-value">${stat.value}</div>
|
|
870
|
-
</div>
|
|
871
|
-
`).join("");
|
|
872
|
-
return `
|
|
873
|
-
<div class="summary-stats">
|
|
874
|
-
<h3 style="margin-bottom: 10px; color: #333;">${b.name}</h3>
|
|
875
|
-
<div class="stats-grid">${ciHtml}${statsHtml}</div>
|
|
876
|
-
</div>
|
|
877
|
-
`;
|
|
900
|
+
function cumulativeSum(arr) {
|
|
901
|
+
const result = [];
|
|
902
|
+
let sum = 0;
|
|
903
|
+
for (const v of arr) {
|
|
904
|
+
sum += v;
|
|
905
|
+
result.push(sum);
|
|
878
906
|
}
|
|
879
|
-
return
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
${ciHtml}
|
|
884
|
-
<div class="stat-item">
|
|
885
|
-
<div class="stat-label">Min</div>
|
|
886
|
-
<div class="stat-value">${b.stats.min.toFixed(3)}ms</div>
|
|
887
|
-
</div>
|
|
888
|
-
<div class="stat-item">
|
|
889
|
-
<div class="stat-label">Median</div>
|
|
890
|
-
<div class="stat-value">${b.stats.p50.toFixed(3)}ms</div>
|
|
891
|
-
</div>
|
|
892
|
-
<div class="stat-item">
|
|
893
|
-
<div class="stat-label">Mean</div>
|
|
894
|
-
<div class="stat-value">${b.stats.avg.toFixed(3)}ms</div>
|
|
895
|
-
</div>
|
|
896
|
-
<div class="stat-item">
|
|
897
|
-
<div class="stat-label">Max</div>
|
|
898
|
-
<div class="stat-value">${b.stats.max.toFixed(3)}ms</div>
|
|
899
|
-
</div>
|
|
900
|
-
<div class="stat-item">
|
|
901
|
-
<div class="stat-label">P75</div>
|
|
902
|
-
<div class="stat-value">${b.stats.p75.toFixed(3)}ms</div>
|
|
903
|
-
</div>
|
|
904
|
-
<div class="stat-item">
|
|
905
|
-
<div class="stat-label">P99</div>
|
|
906
|
-
<div class="stat-value">${b.stats.p99.toFixed(3)}ms</div>
|
|
907
|
-
</div>
|
|
908
|
-
</div>
|
|
909
|
-
</div>
|
|
910
|
-
`;
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
function formatPct(v) {
|
|
910
|
+
return (v >= 0 ? "+" : "") + v.toFixed(1) + "%";
|
|
911
911
|
}
|
|
912
912
|
|
|
913
913
|
//#endregion
|