This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 47b0901645 minor refactor: change jmh-summary graph to use a line of
best fit
47b0901645 is described below
commit 47b090164509095b159eb562a544ee3c2d65ae2c
Author: Paul King <[email protected]>
AuthorDate: Wed May 20 20:15:15 2026 +1000
minor refactor: change jmh-summary graph to use a line of best fit
---
subprojects/performance/dashboard/jmh-summary.html | 64 ++++++++++++++++++++--
1 file changed, 58 insertions(+), 6 deletions(-)
diff --git a/subprojects/performance/dashboard/jmh-summary.html
b/subprojects/performance/dashboard/jmh-summary.html
index 936549f2c3..1d72c4642e 100644
--- a/subprojects/performance/dashboard/jmh-summary.html
+++ b/subprojects/performance/dashboard/jmh-summary.html
@@ -48,11 +48,14 @@
Benchmarks reported as <code>ops/ms</code> are kept as-is; benchmarks
reported
in time-per-op units (<code>ms/op</code>, <code>us/op</code>, …) are
inverted, so on both charts <strong>higher is always faster</strong> and
- <code>1.0</code> means "in line with the last 90 days". Each line is the
- <em>geometric</em> mean of those per-benchmark ratios across a suite. The
- <code>core</code> and <code>grails</code> suites each recombine their two
- CI-split halves (A–G/H–Z, A–D/E–Z); <code>indy</code>
- and <code>classic</code> are kept on separate charts.
+ <code>1.0</code> means "in line with the last 90 days". Each solid line is
the
+ <em>geometric</em> mean of those per-benchmark ratios across a suite; the
+ dashed overlay is a 7-run rolling median of those points, which smooths
+ through CI-runner noise so the trend stays readable when an individual
+ runner has a slow day. Click any solid-line point to open its commit on
+ GitHub. The <code>core</code> and <code>grails</code> suites each recombine
+ their two CI-split halves (A–G/H–Z, A–D/E–Z);
+ <code>indy</code> and <code>classic</code> are kept on separate charts.
</p>
<h2>Invokedynamic (<code>indy</code>)</h2>
@@ -79,6 +82,7 @@
(function () {
const DAY = 864e5;
const WINDOW = 90 * DAY; // trailing baseline window
+ const ROLLING_WINDOW = 7; // runs (≈ days, daily cron) for the
smoothed median overlay
// Logical suite -> the CI-split data.js files that make it up. The A-G/H-Z
// and A-D/E-Z splits exist only to cut CI wall-clock; they are recombined
here.
@@ -164,6 +168,22 @@
return points;
}
+ // Trailing rolling median of the per-run y values; robust to a single slow
+ // runner (the user's most common cause of an obvious-outlier point).
+ function rollingMedianSeries(points, window) {
+ const out = [];
+ for (let i = 0; i < points.length; i++) {
+ const lo = Math.max(0, i - window + 1);
+ const ys = [];
+ for (let j = lo; j <= i; j++) ys.push(points[j].y);
+ ys.sort((a, b) => a - b);
+ const mid = Math.floor(ys.length / 2);
+ const med = ys.length % 2 ? ys[mid] : (ys[mid - 1] + ys[mid]) / 2;
+ out.push({ x: points[i].x, y: med, n: ys.length });
+ }
+ return out;
+ }
+
const baselineLine = {
id: 'baselineLine',
afterDatasetsDraw(chart) {
@@ -191,6 +211,23 @@
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
+ onClick(evt, elements, chart) {
+ // Find the nearest raw-series point under the cursor (median-overlay
+ // points have no commitUrl and stay inert).
+ const hit = chart.getElementsAtEventForMode(evt, 'nearest', {
intersect: false }, true);
+ for (const el of hit) {
+ const p = chart.data.datasets[el.datasetIndex].data[el.index];
+ if (p && p.commitUrl) { window.open(p.commitUrl, '_blank',
'noopener'); return; }
+ }
+ },
+ onHover(evt, elements, chart) {
+ const hit = chart.getElementsAtEventForMode(evt, 'nearest', {
intersect: false }, true);
+ const clickable = hit.some(el => {
+ const p = chart.data.datasets[el.datasetIndex].data[el.index];
+ return p && p.commitUrl;
+ });
+ chart.canvas.style.cursor = clickable ? 'pointer' : 'default';
+ },
scales: {
x: {
type: 'time',
@@ -213,8 +250,11 @@
},
label(item) {
const r = item.raw;
+ const suffix = r.commit !== undefined
+ ? r.n + ' benchmarks'
+ : r.n + '-run window';
return item.dataset.label + ': ' + item.parsed.y.toFixed(3) +
- ' x (' + r.n + ' benchmarks)';
+ ' x (' + suffix + ')';
},
},
},
@@ -253,6 +293,18 @@
spanGaps: true,
data: points,
});
+ datasets.push({
+ label: suite.label + ' (' + ROLLING_WINDOW + '-run median)',
+ borderColor: suite.color,
+ backgroundColor: suite.color,
+ borderWidth: 2,
+ borderDash: [6, 4],
+ pointRadius: 0,
+ pointHoverRadius: 0,
+ tension: 0.15,
+ spanGaps: true,
+ data: rollingMedianSeries(points, ROLLING_WINDOW),
+ });
}
renderChart(mode.canvas, datasets);
}