const { noColor } = Deno;
interface BenchmarkClock { start: number; stop: number;}
export interface BenchmarkTimer { start: () => void; stop: () => void;}
export interface BenchmarkFunction { (b: BenchmarkTimer): void | Promise<void>; name: string;}
export interface BenchmarkDefinition { func: BenchmarkFunction; name: string; runs?: number;}
export interface BenchmarkRunOptions { only?: RegExp; skip?: RegExp; silent?: boolean;}
export interface BenchmarkResult { name: string; totalMs: number; runsCount?: number; runsAvgMs?: number; runsMs?: number[];}
export interface BenchmarkRunResult { measured: number; filtered: number; results: BenchmarkResult[];}
export class BenchmarkRunError extends Error { benchmarkName?: string; constructor(msg: string, benchmarkName?: string) { super(msg); this.name = "BenchmarkRunError"; this.benchmarkName = benchmarkName; }}
function red(text: string): string { return noColor ? text : `\x1b[31m${text}\x1b[0m`;}
function blue(text: string): string { return noColor ? text : `\x1b[34m${text}\x1b[0m`;}
function verifyOr1Run(runs?: number): number { return runs && runs >= 1 && runs !== Infinity ? Math.floor(runs) : 1;}
function assertTiming(clock: BenchmarkClock, benchmarkName: string): void { if (!clock.stop) { throw new BenchmarkRunError( `Running benchmarks FAILED during benchmark named [${benchmarkName}]. The benchmark timer's stop method must be called`, benchmarkName ); } else if (!clock.start) { throw new BenchmarkRunError( `Running benchmarks FAILED during benchmark named [${benchmarkName}]. The benchmark timer's start method must be called`, benchmarkName ); } else if (clock.start > clock.stop) { throw new BenchmarkRunError( `Running benchmarks FAILED during benchmark named [${benchmarkName}]. The benchmark timer's start method must be called before its stop method`, benchmarkName ); }}
function createBenchmarkTimer(clock: BenchmarkClock): BenchmarkTimer { return { start(): void { clock.start = performance.now(); }, stop(): void { clock.stop = performance.now(); }, };}
const candidates: BenchmarkDefinition[] = [];
export function bench( benchmark: BenchmarkDefinition | BenchmarkFunction): void { if (!benchmark.name) { throw new Error("The benchmark function must not be anonymous"); } if (typeof benchmark === "function") { candidates.push({ name: benchmark.name, runs: 1, func: benchmark }); } else { candidates.push({ name: benchmark.name, runs: verifyOr1Run(benchmark.runs), func: benchmark.func, }); }}
export async function runBenchmarks({ only = /[^\s]/, skip = /^\s*$/, silent,}: BenchmarkRunOptions = {}): Promise<BenchmarkRunResult> { const benchmarks: BenchmarkDefinition[] = candidates.filter( ({ name }): boolean => only.test(name) && !skip.test(name) ); const filtered = candidates.length - benchmarks.length; let measured = 0; let failError: Error | undefined = undefined; const clock: BenchmarkClock = { start: NaN, stop: NaN }; const b = createBenchmarkTimer(clock);
if (!silent) { console.log( "running", benchmarks.length, `benchmark${benchmarks.length === 1 ? " ..." : "s ..."}` ); }
const benchmarkResults: BenchmarkResult[] = []; for (const { name, runs = 0, func } of benchmarks) { if (!silent) { console.groupCollapsed(`benchmark ${name} ... `); }
let result = ""; try { if (runs === 1) { await func(b); assertTiming(clock, name); result = `${clock.stop - clock.start}ms`; benchmarkResults.push({ name, totalMs: clock.stop - clock.start }); } else if (runs > 1) { let pendingRuns = runs; let totalMs = 0; const runsMs = []; while (true) { await func(b); assertTiming(clock, name); totalMs += clock.stop - clock.start; runsMs.push(clock.stop - clock.start); clock.start = clock.stop = NaN; if (!--pendingRuns) { result = `${runs} runs avg: ${totalMs / runs}ms`; benchmarkResults.push({ name, totalMs, runsCount: runs, runsAvgMs: totalMs / runs, runsMs, }); break; } } } } catch (err) { failError = err;
if (!silent) { console.groupEnd(); console.error(red(err.stack)); }
break; }
if (!silent) { console.log(blue(result)); console.groupEnd(); }
measured++; clock.start = clock.stop = NaN; }
if (!silent) { console.log( `benchmark result: ${!!failError ? red("FAIL") : blue("DONE")}. ` + `${measured} measured; ${filtered} filtered` ); }
if (!!failError) { throw failError; }
const benchmarkRunResult = { measured, filtered, results: benchmarkResults, };
return benchmarkRunResult;}