From 77ee24ddcebd32318b4b251bd742bcf53b32e595 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Thu, 7 Aug 2025 08:11:32 -0700 Subject: [PATCH] Print benchmark comparison results in a tabular format (#53097) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53097 # Changelog: [Internal] - This makes the benchmark results comparison printout, added in https://github.com/facebook/react-native/pull/52925, be done in a tabular form, so it's easier to read and copy/paste around. Reviewed By: lenaic Differential Revision: D79728695 fbshipit-source-id: 4dfc999ad4a9a8c2d67efdfce11aff75383cf645 --- .../runner/benchmarkUtils.js | 41 ++++++------- private/react-native-fantom/runner/utils.js | 61 +++++++++++++++++++ 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/private/react-native-fantom/runner/benchmarkUtils.js b/private/react-native-fantom/runner/benchmarkUtils.js index 45e2c587e8b..8155c956ffc 100644 --- a/private/react-native-fantom/runner/benchmarkUtils.js +++ b/private/react-native-fantom/runner/benchmarkUtils.js @@ -8,6 +8,8 @@ * @format */ +import {markdownTable} from './utils'; + type TestTaskTiming = { name: string, latency: { @@ -31,7 +33,7 @@ export const printBenchmarkResultsRanking = ( testArtifact: mixed, }>, ) => { - const testTaskTimings: {[string]: Array<[string, number]>} = {}; + const testTaskTimings: {[string]: {[string]: number}} = {}; let numTestVariants = 0; for (const testResult of testResults) { @@ -49,12 +51,9 @@ export const printBenchmarkResultsRanking = ( for (const taskTiming of testArtifact.timings) { const taskName = taskTiming.name; if (testTaskTimings[taskName] === undefined) { - testTaskTimings[taskName] = []; + testTaskTimings[taskName] = {}; } - testTaskTimings[taskName].push([ - testResult.title, - taskTiming.latency.p50, - ]); + testTaskTimings[taskName][testResult.title] = taskTiming.latency.p50; } } if (numTestVariants <= 1 || Object.keys(testTaskTimings).length === 0) { @@ -62,25 +61,23 @@ export const printBenchmarkResultsRanking = ( return; } - // Sort by each task's execution times + // Find relative execution times for tasks + const results: {[string]: {[string]: string}} = {}; for (const taskName in testTaskTimings) { - testTaskTimings[taskName].sort((a, b) => a[1] - b[1]); + const kv = Object.entries(testTaskTimings[taskName]); + kv.sort((a, b) => a[1] - b[1]); + const bestTiming = kv[0][1]; + results[taskName] = {}; + kv.forEach(([key, val]) => { + results[taskName][key] = + `${val.toFixed(3)}ms ${getTimingDelta(bestTiming, val)}`; + }); + results[taskName][kv[0][0]] = `🏆 ${bestTiming.toFixed(3)}ms`; } - // Print the rankings - console.log('### Benchmark Results Ranking ###'); - for (const taskName in testTaskTimings) { - console.log(`> ${taskName}:`); - let lastTiming; - for (const [i, [testVariationName, latency]] of testTaskTimings[ - taskName - ].entries()) { - console.log( - ` ${i + 1}. ${testVariationName}: ${latency.toFixed(2)}ms ${getTimingDelta(lastTiming, latency)}`, - ); - lastTiming = latency; - } - } + console.log('### Benchmark Times Comparison (p50): ###'); + console.log(markdownTable(results, 'Task name')); + console.log(''); }; function getTimingDelta(lastTiming: ?number, currentTiming: ?number): string { diff --git a/private/react-native-fantom/runner/utils.js b/private/react-native-fantom/runner/utils.js index 4dcad8fa8dd..eefc4d3cb99 100644 --- a/private/react-native-fantom/runner/utils.js +++ b/private/react-native-fantom/runner/utils.js @@ -371,3 +371,64 @@ export function printConsoleLog(log: ConsoleLogMessage): void { break; } } + +// Returns a markdown table corresponding to the given data, adopted from the RN console.table polyfill implementation +export function markdownTable( + data: {[string]: {[string]: string}}, + indexColumnName?: string = '', +): string { + const repeat = (element: string, n: number) => + Array.apply(null, Array(n)).map(() => element); + + const rows = Object.keys(data).map((key: string) => ({ + [indexColumnName]: key, + ...data[key], + })); + + if (rows.length === 0) { + return ''; + } + + const columns = Array.from( + rows.reduce((columnSet: Set, row) => { + Object.keys(row).forEach(key => columnSet.add(key)); + return columnSet; + }, new Set()), + ); + const stringRows: Array> = []; + const columnWidths = []; + + // Figure out max cell width for each column + columns.forEach((k, i) => { + columnWidths[i] = k.length; + for (let j = 0; j < rows.length; j++) { + const cellStr = rows[j][k]; + stringRows[j] = stringRows[j] || []; + stringRows[j][i] = cellStr; + columnWidths[i] = Math.max(columnWidths[i], cellStr.length); + } + }); + + // Join all elements in the row into a single string with | separators + // (appends extra spaces to each cell to make separators | aligned) + const joinRow = (row: Array, space?: string = ' ') => { + const cells = row.map((cell: string, i) => { + const extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); + return cell + extraSpaces; + }); + return '| ' + cells.join(space + '|' + space) + ' |'; + }; + + const separators = columnWidths.map(columnWidth => + repeat('-', columnWidth).join(''), + ); + const separatorRow = joinRow(separators); + const header = joinRow(columns); + const table = [header, separatorRow]; + + for (let i = 0; i < rows.length; i++) { + table.push(joinRow(stringRows[i])); + } + + return '\n' + table.join('\n'); +}