@@ -121,7 +121,9 @@ jobs:
121121
122122 const threshold = parseFloat(process.env.ALERT_THRESHOLD_RATIO);
123123 const thresholdPct = ((threshold - 1) * 100).toFixed(0);
124- const rows = [];
124+ // Each entry: { name, isHasher, prCell, stdCell, baseCell, deltaCell, flag }
125+ // where flag is 'regression' | 'improvement' | null.
126+ const entries = [];
125127 let regressions = 0;
126128 let improvements = 0;
127129
@@ -131,6 +133,7 @@ jobs:
131133 const prStdDev = b.Statistics.StandardDeviation;
132134 const baseStats = baseMap.get(name);
133135 let deltaCell = '🆕 new';
136+ let flag = null;
134137 if (baseStats) {
135138 const baseMean = baseStats.Mean;
136139 const baseStdDev = baseStats.StandardDeviation;
@@ -143,14 +146,26 @@ jobs:
143146 const beyondNoise = Math.abs(prMean - baseMean) > (prStdDev + baseStdDev);
144147 if (ratio >= threshold && beyondNoise) {
145148 deltaCell += ' ⚠️';
149+ flag = 'regression';
146150 regressions++;
147151 } else if (ratio <= (1 / threshold) && beyondNoise) {
148152 deltaCell += ' ✅';
153+ flag = 'improvement';
149154 improvements++;
150155 }
151156 }
152- const baseMeanCell = baseStats ? formatNs(baseStats.Mean) : 'n/a';
153- rows.push(`| \`${name}\` | ${formatNs(prMean)} | ${formatNs(prStdDev)} | ${baseMeanCell} | ${deltaCell} |`);
157+ entries.push({
158+ name,
159+ // Hasher throughput benchmarks (StringHasherBenchmark,
160+ // IntegerHasherBenchmark) get their own section; everything else
161+ // is a collection benchmark.
162+ isHasher: /Hasher/.test(name),
163+ prCell: formatNs(prMean),
164+ stdCell: formatNs(prStdDev),
165+ baseCell: baseStats ? formatNs(baseStats.Mean) : 'n/a',
166+ deltaCell,
167+ flag,
168+ });
154169 }
155170
156171 let subtitle;
@@ -167,17 +182,48 @@ jobs:
167182 const baseSha = (process.env.BASE_SHA || '').slice(0, 7);
168183 const footer = `<sub>Same-runner A/B: main (\`${baseSha}\`) and this PR were built and benchmarked back-to-back on the same runner, so hardware variance cancels out. ⚠️ = PR mean ≥ +${thresholdPct}% slower than main and beyond combined std-dev; ✅ = correspondingly faster.</sub>`;
169184
185+ const header = ['| Benchmark | This PR | StdDev | main | Δ |', '|---|---:|---:|---:|---:|'];
186+ const toRow = (e) => `| \`${e.name}\` | ${e.prCell} | ${e.stdCell} | ${e.baseCell} | ${e.deltaCell} |`;
187+
188+ // A collapsible section per benchmark family, collapsed by default so
189+ // the (often large) tables stay below the fold.
190+ const section = (title, list) => {
191+ if (list.length === 0) return [];
192+ return [
193+ '<details>',
194+ `<summary><b>${title}</b> (${list.length})</summary>`,
195+ '',
196+ ...header,
197+ ...list.map(toRow),
198+ '</details>',
199+ '',
200+ ];
201+ };
202+
203+ // Highlights: only the flagged rows stay above the fold so reviewers
204+ // see what actually moved without expanding either table.
205+ const flagged = entries.filter(e => e.flag);
206+ const highlights = flagged.length === 0 ? [] : [
207+ '**Highlights**',
208+ '',
209+ ...header,
210+ ...flagged.map(toRow),
211+ '',
212+ ];
213+
214+ const collections = entries.filter(e => !e.isHasher);
215+ const hashers = entries.filter(e => e.isHasher);
216+
170217 const marker = '<!-- celerity-benchmarks-comment -->';
171218 const body = [
172219 marker,
173220 '## Benchmarks',
174221 '',
175222 subtitle,
176223 '',
177- '| Benchmark | This PR | StdDev | main | Δ |',
178- '|---|---:|---:|---:|---:|',
179- ...rows,
180- '',
224+ ...highlights,
225+ ...section('Collections', collections),
226+ ...section('Hashers', hashers),
181227 footer,
182228 ].join('\n');
183229
0 commit comments