import { pairedGreenThreshold, pairedYellowThreshold } from './config.ts';
import type { BenchmarkResult, PairedComparison } from './types.ts';
type ColorFn = (value: number | string) => string;
export function printBenchmarkResults(
results: ReadonlyArray<BenchmarkResult>,
includesMemory: boolean,
): void {
const nameMaxLen = maxBy(results, ({ name }) => name.length);
const opsTop = maxBy(results, ({ ops }) => ops);
const opsMaxLen = maxBy(results, ({ ops }) => beautifyNumber(ops).length);
const memPerOpMaxLen = maxBy(
results,
({ memPerOp }) => beautifyBytes(memPerOp).length,
);
for (const result of results) {
printBench(result);
}
function printBench(bench: BenchmarkResult): void {
const { name, memPerOp, ops, deviation, numSamples } = bench;
console.log(
' ' +
nameStr() +
grey(' x ') +
opsStr() +
' ops/sec ' +
grey('\xb1') +
deviationStr() +
cyan('%') +
(includesMemory ? grey(' x ') + memPerOpStr() + '/op' : '') +
grey(' (' + numSamples + ' runs sampled)'),
);
function nameStr(): string {
const nameFmt = name.padEnd(nameMaxLen);
return ops === opsTop ? green(nameFmt) : nameFmt;
}
function opsStr(): string {
const percent = ops / opsTop;
const colorFn = percent > 0.95 ? green : percent > 0.8 ? yellow : red;
return colorFn(beautifyNumber(ops).padStart(opsMaxLen));
}
function deviationStr(): string {
const colorFn = deviation > 5 ? red : deviation > 2 ? yellow : green;
return colorFn(deviation.toFixed(2));
}
function memPerOpStr(): string {
return beautifyBytes(memPerOp).padStart(memPerOpMaxLen);
}
}
}
export function printPairedComparisons(
pairedComparisons: ReadonlyArray<PairedComparison>,
): void {
if (pairedComparisons.length === 0) {
return;
}
console.log(' ' + grey('paired round-by-round ops/sec changes:'));
const leftMaxLen = maxBy(
pairedComparisons,
({ baselineRevision }) => baselineRevision.length,
);
const rightMaxLen = maxBy(
pairedComparisons,
({ revision }) => revision.length,
);
const speedupMaxLen = maxBy(
pairedComparisons,
({ speedupPercent }) => formatSignedPercent(speedupPercent).length,
);
const ciMaxLen = maxBy(
pairedComparisons,
(comparison) => formatConfidenceInterval(comparison).length,
);
for (const paired of pairedComparisons) {
const speedupColorFn = pairedSpeedupColorFn(paired);
console.log(
' ' +
paired.baselineRevision.padEnd(leftMaxLen) +
' -> ' +
paired.revision.padEnd(rightMaxLen) +
grey(' x ') +
speedupColorFn(
formatSignedPercent(paired.speedupPercent).padStart(speedupMaxLen),
) +
' ops/sec change ' +
grey(
'(95% CI ' +
formatConfidenceInterval(paired).padStart(ciMaxLen) +
', ' +
paired.numPairs +
' paired runs)',
),
);
}
}
function beautifyBytes(bytes: number): string {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log2(bytes) / 10);
return beautifyNumber(bytes / 2 ** (i * 10)) + ' ' + sizes[i];
}
function beautifyNumber(num: number): string {
return Number(num.toFixed(num > 100 ? 0 : 2)).toLocaleString();
}
function formatSignedPercent(num: number): string {
const rounded = Number(num.toFixed(2));
const sign = rounded > 0 ? '+' : '';
return sign + rounded.toFixed(2) + '%';
}
function formatConfidenceInterval({
ciLowPercent,
ciHighPercent,
}: PairedComparison): string {
return (
formatSignedPercent(ciLowPercent) +
' to ' +
formatSignedPercent(ciHighPercent)
);
}
function maxBy<T>(array: ReadonlyArray<T>, fn: (obj: T) => number): number {
return Math.max(...array.map(fn));
}
function pairedSpeedupColorFn({
speedupPercent,
ciLowPercent,
ciHighPercent,
}: PairedComparison): ColorFn {
if (ciLowPercent <= 0 && ciHighPercent >= 0) {
return grey;
}
const relativeOps = 1 + speedupPercent / 100;
if (speedupPercent < 0) {
if (relativeOps > pairedGreenThreshold) {
return grey;
}
if (relativeOps > pairedYellowThreshold) {
return yellow;
}
return red;
}
return green;
}
export function bold(str: number | string): string {
return '\u001b[1m' + str + '\u001b[0m';
}
export function red(str: number | string): string {
return '\u001b[31m' + str + '\u001b[0m';
}
export function green(str: number | string): string {
return '\u001b[32m' + str + '\u001b[0m';
}
export function yellow(str: number | string): string {
return '\u001b[33m' + str + '\u001b[0m';
}
export function cyan(str: number | string): string {
return '\u001b[36m' + str + '\u001b[0m';
}
export function grey(str: number | string): string {
return '\u001b[90m' + str + '\u001b[0m';
}