1313 * node render-benchmark-comment.mjs \
1414 * --status running|completed|failed \
1515 * [--results-dir <dir>] # dir with bench-results-*.json files
16+ * [--baseline-dir <dir>] # main-branch results to diff averages against
1617 * [--previous-body <file>] # previous comment body to carry history from
1718 * [--commit <sha>] [--run-url <url>] \
1819 * [--output <file>] # defaults to stdout
@@ -48,6 +49,7 @@ export function parseArgs(argv) {
4849 const args = {
4950 status : 'completed' ,
5051 resultsDir : undefined ,
52+ baselineDir : undefined ,
5153 previousBody : undefined ,
5254 commit : undefined ,
5355 runUrl : undefined ,
@@ -66,6 +68,9 @@ export function parseArgs(argv) {
6668 case '--results-dir' :
6769 args . resultsDir = next ( ) ;
6870 break ;
71+ case '--baseline-dir' :
72+ args . baselineDir = next ( ) ;
73+ break ;
6974 case '--previous-body' :
7075 args . previousBody = next ( ) ;
7176 break ;
@@ -149,6 +154,50 @@ function formatMs(value) {
149154 return `${ Math . abs ( value ) >= 100 ? Math . round ( value ) : value } ` ;
150155}
151156
157+ /**
158+ * Annotates each metric row with the matching baseline average (from the most
159+ * recent main-branch run), keyed by backend/app/metric/scenario. The
160+ * annotation is stored on the entry so history re-renders keep showing the
161+ * delta each run was originally compared against.
162+ */
163+ export function annotateWithBaseline ( results , baseline ) {
164+ if ( ! baseline || baseline . length === 0 ) return results ;
165+ const baselineAvgs = new Map ( ) ;
166+ for ( const result of baseline ) {
167+ for ( const row of result . metrics ?? [ ] ) {
168+ baselineAvgs . set (
169+ `${ result . backend } /${ result . app } /${ row . metric } /${ row . scenario } ` ,
170+ row . avg
171+ ) ;
172+ }
173+ }
174+ return results . map ( ( result ) => ( {
175+ ...result ,
176+ metrics : ( result . metrics ?? [ ] ) . map ( ( row ) => {
177+ const baselineAvg = baselineAvgs . get (
178+ `${ result . backend } /${ result . app } /${ row . metric } /${ row . scenario } `
179+ ) ;
180+ return typeof baselineAvg === 'number' ? { ...row , baselineAvg } : row ;
181+ } ) ,
182+ } ) ) ;
183+ }
184+
185+ /** Formats the avg-vs-main delta, e.g. " (+4.2%)"; empty without a baseline. */
186+ function formatDelta ( current , baselineAvg ) {
187+ if (
188+ typeof current !== 'number' ||
189+ typeof baselineAvg !== 'number' ||
190+ baselineAvg <= 0 ||
191+ ! Number . isFinite ( current / baselineAvg )
192+ ) {
193+ return '' ;
194+ }
195+ const pct = ( ( current - baselineAvg ) / baselineAvg ) * 100 ;
196+ if ( Math . abs ( pct ) < 0.5 ) return ' (±0%)' ;
197+ const digits = Math . abs ( pct ) >= 10 ? 0 : 1 ;
198+ return ` (${ pct > 0 ? '+' : '' } ${ pct . toFixed ( digits ) } %)` ;
199+ }
200+
152201/**
153202 * Formats a percentile cell, marking it 🟢/🔴 against its target when one is
154203 * defined for this row.
@@ -182,7 +231,7 @@ function renderResultTable(result) {
182231 const name = label ? `**${ label . name } **` : row . metric ;
183232 const targets = row . targets ?? { } ;
184233 lines . push (
185- `| ${ name } | ${ row . scenario } | ${ formatMs ( row . avg ) } | ${ formatCell ( row . p75 , targets . p75 ) } | ${ formatCell ( row . p90 , targets . p90 ) } | ${ formatCell ( row . p99 , targets . p99 ) } | ${ row . samples } |`
234+ `| ${ name } | ${ row . scenario } | ${ formatMs ( row . avg ) } ${ formatDelta ( row . avg , row . baselineAvg ) } | ${ formatCell ( row . p75 , targets . p75 ) } | ${ formatCell ( row . p90 , targets . p90 ) } | ${ formatCell ( row . p99 , targets . p99 ) } | ${ row . samples } |`
186235 ) ;
187236 }
188237 return lines . join ( '\n' ) ;
@@ -249,8 +298,17 @@ function renderFooter(entries) {
249298 ) . join ( ' · ' ) ;
250299 const scenarioLegend = buildScenarioLegend ( results ) ;
251300 const targetsLegend = buildTargetsLegend ( results ) ;
301+ const hasBaseline = results . some ( ( result ) =>
302+ ( result . metrics ?? [ ] ) . some ( ( row ) => typeof row . baselineAvg === 'number' )
303+ ) ;
252304
253305 return [
306+ ...( hasBaseline
307+ ? [
308+ '<sub>Avg deltas compare against the most recent benchmark run on `main` at the time of this run.</sub>' ,
309+ '' ,
310+ ]
311+ : [ ] ) ,
254312 `<sub>Metrics — ${ definitions } </sub>` ,
255313 ...( scenarioLegend ? [ '' , `<sub>Scenarios — ${ scenarioLegend } </sub>` ] : [ ] ) ,
256314 ...( targetsLegend
@@ -312,6 +370,7 @@ function renderHistorySection(shownPrevious) {
312370export function renderComment ( {
313371 status,
314372 results,
373+ baseline = [ ] ,
315374 history,
316375 commit,
317376 runUrl,
@@ -324,7 +383,7 @@ export function renderComment({
324383 commit,
325384 runUrl,
326385 generatedAt : now . toISOString ( ) ,
327- results,
386+ results : annotateWithBaseline ( results , baseline ) ,
328387 } ,
329388 ...entries ,
330389 ] . slice ( 0 , MAX_HISTORY_ENTRIES ) ;
@@ -367,6 +426,10 @@ function main() {
367426 : '' ;
368427 const history = extractHistory ( previousBody ) ;
369428 const results = loadResults ( args . resultsDir ) ;
429+ const baseline = loadResults ( args . baselineDir ) ;
430+ if ( args . baselineDir && baseline . length === 0 ) {
431+ console . error ( `No baseline results found in ${ args . baselineDir } ` ) ;
432+ }
370433
371434 if ( args . status === 'completed' && results . length === 0 ) {
372435 console . error ( 'No benchmark results found for status=completed' ) ;
@@ -376,6 +439,7 @@ function main() {
376439 const body = renderComment ( {
377440 status : args . status ,
378441 results,
442+ baseline,
379443 history,
380444 commit : args . commit ,
381445 runUrl : args . runUrl ,
0 commit comments