/*** * @module node-opcua-benchmarker */ // tslint:disable:object-literal-shorthand /// import { EventEmitter } from "events"; import { isFunction } from "util"; import { assert } from "node-opcua-assert"; import hrtime = require("browser-process-hrtime"); export interface IPerformanceData { message: string; ops: number; count: number; total_time: number; } export interface IRunOptions { /** * max execution time in sec : default to 0.5 seconds. */ max_time?: number; /** * specify the minimum number of time the benchmark-ed test cycle should be repeated */ min_count?: number; } export type TestFunctionSync = () => void; export type TestFunctionAsync = () => Promise; export type TestFunction = TestFunctionSync | TestFunctionAsync; export interface ITestRun { name: string; functor: TestFunction; result?: IPerformanceData; } async function measure_cycle(func: TestFunction): Promise { const start = hrtime(); // tuple [second, nanosecond] await func(); const elapsed = hrtime(start); return elapsed[0] + elapsed[1] / 1000000000; } function minimum(arr: T[], predicate: (t:T) => number) : T { return arr.reduce( (prev: T , current: T) => predicate(prev) < predicate(current) ? prev : current, arr[0]); } function maximum(arr: T[], predicate: (t:T) => number) : T { return arr.reduce( (prev: T , current: T) => predicate(prev) > predicate(current) ? prev : current, arr[0]); } export interface IBenchmarkerEvent { on(event: "completed", listener: (this: Benchmarker, fastest: ITestRun, speedUp: number) => void): this; on(event: "cycle", listener: (message: string) => void): this; } export class Benchmarker extends EventEmitter implements IBenchmarkerEvent { /** * access the fastest test run */ public fastest?: ITestRun; /** * access the slowest test run */ public slowest?: ITestRun; /** * the speed up factor indicates how much faster is the faster ITestRun compare to the slowest. */ public speedUp = 0; /*** * @internal */ private readonly suites: { [id: string]: ITestRun }; constructor() { super(); this.suites = {}; } /** * Add a new test to the suite * @param name name of the tests * @param func the code that need to be stress */ public add(name: string, func: TestFunction): Benchmarker { assert(isFunction(func)); this.suites[name] = { functor: func, name: name, } as ITestRun; return this; } /** * run the benchmark * @param options */ public async run(options?: IRunOptions) { options = options || {}; options.max_time = !options.max_time ? 0.5 : options.max_time; options.min_count = options.min_count || 5; for (const test of Object.values(this.suites)) { test.result = await this.measure_perf(test.name, test.functor, options!); } const pred = (bench: ITestRun)=> bench.result!.ops; // find fastest this.fastest = maximum(Object.values(this.suites), pred); this.slowest = minimum(Object.values(this.suites), pred); this.speedUp = Math.floor(this.fastest!.result!.ops / this.slowest!.result!.ops); this.emit("complete", this.fastest, this.speedUp); return this; } /** * * @param name * @param func * @param options * @internal */ private async measure_perf(name: string, func: TestFunction, options: IRunOptions): Promise { let totalTime = 0; let count = 0; const maxTime = !options.max_time ? 0.5 : options.max_time; const minCount = options.min_count || 5; while (totalTime < maxTime || count < minCount) { totalTime += await measure_cycle(func); count += 1; } const ops = count / totalTime; const message = " CYCLE " + name + " op/s " + ((count / totalTime).toPrecision(7) + " count = " + count); this.emit("cycle", message); return { count: count, message: message, ops: ops, total_time: totalTime, } as IPerformanceData; } }