blob: 768e114611d5c80bf68cdf1ffd96ae2a9a4187ef [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Lightweight class to benchmark the execution times of operations
* and print the time to the console. It expands on the console.time()
* functionality slightly by treating the timestamps as a sequence.
*/
export class Bechmark {
private names: string[] = [];
private ts: number[] = [];
/**
* Creates a new Benchmark instance and create the first log.
*
* @param name The first log name.
*/
constructor(name?: string) {
name = this.defaultName(name);
this.log(name);
}
/**
* Log the time of an event.
*
* @param name The log entry's name
*/
log(name?: string) {
name = this.defaultName(name);
this.ts.push(performance.now());
this.names.push(name);
}
/**
* Prints the Benchmark's history showing the event and time deltas.
*
* @param name The log entry's name
*/
print(name?: string) {
name = this.defaultName(name);
this.log(name);
const firstName = this.names[0];
let lines: string[] = [];
lines.push(`Bechmark : ${firstName}`);
lines.push(this.deltaTS(0, this.names.length - 1));
for (let i = 1; i < this.names.length; i++) {
lines.push(this.deltaTS(i - 1, i));
}
console.log(lines.join('\n'));
}
/**
* [deltaTS description]
* @param start [description]
* @param stop [description]
* @return [description]
*/
private deltaTS(start: number, stop: number): string {
const deltaText = String(Math.round(this.ts[stop] - this.ts[start]));
const deltaPadding = ' '.repeat(Math.max(0, 8 - deltaText.length));
const startName = this.names[start];
const stopName = this.names[stop];
return `${deltaText}${deltaPadding} : ${startName} → ${stopName}`;
}
/**
* Sets a useful default name if it is missing. Attempts to use the
* stack trace to find the line number of the caller, if that fails uses
* the number of calls.
*
* @param name Optional name.
* @returns Log name
*/
private defaultName(name?: string) {
if (name !== undefined) {
return name;
}
const stack = this.getCallerFromStack();
if (stack !== undefined) {
return stack;
}
return String(this.names.length);
}
/**
* Extracts the line number of the caller function using the V8 JS API's
* non-standard method to get a stack trace.
*
* @returns Returns the line of the caller function if possible.
*/
private getCallerFromStack(): string|undefined {
// https://v8.dev/docs/stack-trace-api
const fullTrace = new Error().stack;
if (!fullTrace) {
return undefined;
}
const lines = fullTrace.split('\n').map((x) => x.match(/\([^()]+\)/));
const match = lines.map((x) => x ? x[0] : null).filter((x) => x);
// Magic number here gets us out of the Benchmark class's functions and
// points at the line which called the public interface.
const result = match[3];
if (typeof result === 'string') {
return result;
}
return undefined;
}
}