UNPKG

@codspeed/tinybench-plugin

Version:

tinybench compatibility layer for CodSpeed

367 lines (350 loc) 11.2 kB
import { setupCore, teardownCore, InstrumentHooks, optimizeFunction, mongoMeasurement, msToNs, writeWalltimeResults, calculateQuantiles, msToS, tryIntrospect, getCodspeedRunnerMode, getGitDir } from '@codspeed/core'; import path from 'path'; import { fileURLToPath } from 'url'; function get(belowFn) { const oldLimit = Error.stackTraceLimit; Error.stackTraceLimit = Infinity; const dummyObject = {}; const v8Handler = Error.prepareStackTrace; Error.prepareStackTrace = function(dummyObject, v8StackTrace) { return v8StackTrace; }; Error.captureStackTrace(dummyObject, belowFn || get); const v8StackTrace = dummyObject.stack; Error.prepareStackTrace = v8Handler; Error.stackTraceLimit = oldLimit; return v8StackTrace; } function CallSite(properties) { for (const property in properties) { this[property] = properties[property]; } } const strProperties = [ 'this', 'typeName', 'functionName', 'methodName', 'fileName', 'lineNumber', 'columnNumber', 'function', 'evalOrigin' ]; const boolProperties = [ 'topLevel', 'eval', 'native', 'constructor' ]; strProperties.forEach(function (property) { CallSite.prototype[property] = null; CallSite.prototype['get' + property[0].toUpperCase() + property.substr(1)] = function () { return this[property]; }; }); boolProperties.forEach(function (property) { CallSite.prototype[property] = false; CallSite.prototype['is' + property[0].toUpperCase() + property.substr(1)] = function () { return this[property]; }; }); const taskUriMap = /* @__PURE__ */ new WeakMap(); function getTaskUri(bench, taskName, rootCallingFile) { const uriMap = taskUriMap.get(bench); return uriMap?.get(taskName) || `${rootCallingFile}::${taskName}`; } function getOrCreateUriMap(bench) { let uriMap = taskUriMap.get(bench); if (!uriMap) { uriMap = /* @__PURE__ */ new Map(); taskUriMap.set(bench, uriMap); } return uriMap; } var __defProp$1 = Object.defineProperty; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$1 = (obj, key, value) => { __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class BaseBenchRunner { constructor(bench, rootCallingFile) { __publicField$1(this, "bench"); __publicField$1(this, "rootCallingFile"); this.bench = bench; this.rootCallingFile = rootCallingFile; } setupBenchRun() { setupCore(); this.logStart(); } logStart() { console.log( `[CodSpeed] running with @codspeed/tinybench v${"5.0.1"} (${this.getModeName()})` ); } getTaskUri(task) { return getTaskUri(this.bench, task.name, this.rootCallingFile); } logTaskCompletion(uri, status) { console.log(`[CodSpeed] ${status} ${uri}`); } finalizeBenchRun() { teardownCore(); console.log(`[CodSpeed] Done running ${this.bench.tasks.length} benches.`); return this.bench.tasks; } wrapWithInstrumentHooks(fn, uri) { InstrumentHooks.startBenchmark(); const result = fn(); InstrumentHooks.stopBenchmark(); InstrumentHooks.setExecutedBenchmark(process.pid, uri); return result; } async wrapWithInstrumentHooksAsync(fn, uri) { InstrumentHooks.startBenchmark(); const result = await fn(); InstrumentHooks.stopBenchmark(); InstrumentHooks.setExecutedBenchmark(process.pid, uri); return result; } setupBenchMethods() { this.bench.run = async () => { this.setupBenchRun(); for (const task of this.bench.tasks) { const uri = this.getTaskUri(task); await this.runTaskAsync(task, uri); } return this.finalizeAsyncRun(); }; this.bench.runSync = () => { this.setupBenchRun(); for (const task of this.bench.tasks) { const uri = this.getTaskUri(task); this.runTaskSync(task, uri); } return this.finalizeSyncRun(); }; } } function setupCodspeedInstrumentedBench(bench, rootCallingFile) { const runner = new InstrumentedBenchRunner(bench, rootCallingFile); runner.setupBenchMethods(); } class InstrumentedBenchRunner extends BaseBenchRunner { getModeName() { return "instrumented mode"; } taskCompletionMessage() { return InstrumentHooks.isInstrumented() ? "Measured" : "Checked"; } wrapFunctionWithFrame(fn, isAsync) { if (isAsync) { return async function __codspeed_root_frame__() { await fn(); }; } else { return function __codspeed_root_frame__() { fn(); }; } } async runTaskAsync(task, uri) { const { fnOpts, fn } = task; await fnOpts?.beforeAll?.call(task, "run"); await optimizeFunction(async () => { await fnOpts?.beforeEach?.call(task, "run"); await fn(); await fnOpts?.afterEach?.call(task, "run"); }); await fnOpts?.beforeEach?.call(task, "run"); await mongoMeasurement.start(uri); global.gc?.(); await this.wrapWithInstrumentHooksAsync( this.wrapFunctionWithFrame(fn, true), uri ); await mongoMeasurement.stop(uri); await fnOpts?.afterEach?.call(task, "run"); await fnOpts?.afterAll?.call(task, "run"); this.logTaskCompletion(uri, this.taskCompletionMessage()); } runTaskSync(task, uri) { const { fnOpts, fn } = task; fnOpts?.beforeAll?.call(task, "run"); fnOpts?.beforeEach?.call(task, "run"); this.wrapWithInstrumentHooks(this.wrapFunctionWithFrame(fn, false), uri); fnOpts?.afterEach?.call(task, "run"); fnOpts?.afterAll?.call(task, "run"); this.logTaskCompletion(uri, this.taskCompletionMessage()); } finalizeAsyncRun() { return this.finalizeBenchRun(); } finalizeSyncRun() { return this.finalizeBenchRun(); } } var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, key + "" , value); return value; }; function setupCodspeedWalltimeBench(bench, rootCallingFile) { const runner = new WalltimeBenchRunner(bench, rootCallingFile); runner.setupBenchMethods(); } class WalltimeBenchRunner extends BaseBenchRunner { constructor() { super(...arguments); __publicField(this, "codspeedBenchmarks", []); } getModeName() { return "walltime mode"; } async runTaskAsync(task, uri) { this.wrapTaskFunction(task, true); if (this.bench.opts.warmup) { await task.warmup(); } await mongoMeasurement.start(uri); await this.wrapWithInstrumentHooksAsync(() => task.run(), uri); await mongoMeasurement.stop(uri); this.registerCodspeedBenchmarkFromTask(task); } runTaskSync(task, uri) { this.wrapTaskFunction(task, false); if (this.bench.opts.warmup) { task.warmup(); } this.wrapWithInstrumentHooks(() => task.runSync(), uri); this.registerCodspeedBenchmarkFromTask(task); } finalizeAsyncRun() { return this.finalizeWalltimeRun(true); } finalizeSyncRun() { return this.finalizeWalltimeRun(false); } wrapTaskFunction(task, isAsync) { const { fn } = task; if (isAsync) { async function __codspeed_root_frame__2() { await fn(); } task.fn = __codspeed_root_frame__2; } else { let __codspeed_root_frame__2 = function() { fn(); }; task.fn = __codspeed_root_frame__2; } } registerCodspeedBenchmarkFromTask(task) { const uri = this.getTaskUri(task); if (!task.result) { console.warn(` \u26A0 No result data available for ${uri}`); return; } const warmupIterations = this.bench.opts.warmup ? this.bench.opts.warmupIterations ?? TINYBENCH_WARMUP_DEFAULT : 0; const stats = convertTinybenchResultToBenchmarkStats( task.result, warmupIterations ); this.codspeedBenchmarks.push({ name: task.name, uri, config: { max_rounds: this.bench.opts.iterations ?? null, max_time_ns: this.bench.opts.time ? msToNs(this.bench.opts.time) : null, min_round_time_ns: null, // tinybench does not have an option for this warmup_time_ns: this.bench.opts.warmup && this.bench.opts.warmupTime ? msToNs(this.bench.opts.warmupTime) : null }, stats }); this.logTaskCompletion(uri, "Collected walltime data for"); } finalizeWalltimeRun(isAsync) { if (this.codspeedBenchmarks.length > 0) { writeWalltimeResults(this.codspeedBenchmarks, isAsync); } console.log( `[CodSpeed] Done collecting walltime data for ${this.bench.tasks.length} benches.` ); return this.bench.tasks; } } const TINYBENCH_WARMUP_DEFAULT = 16; function convertTinybenchResultToBenchmarkStats(result, warmupIterations) { const { min, max, mean, sd, samples } = result.latency; const sortedTimesNs = samples.map(msToNs).sort((a, b) => a - b); const meanNs = msToNs(mean); const stdevNs = msToNs(sd); const { q1_ns, q3_ns, median_ns, iqr_outlier_rounds, stdev_outlier_rounds } = calculateQuantiles({ meanNs, stdevNs, sortedTimesNs }); return { min_ns: msToNs(min), max_ns: msToNs(max), mean_ns: meanNs, stdev_ns: stdevNs, q1_ns, median_ns, q3_ns, total_time: msToS(result.totalTime), iter_per_round: 1, // as there is only one round in tinybench, we define that there were n rounds of 1 iteration rounds: sortedTimesNs.length, iqr_outlier_rounds, stdev_outlier_rounds, warmup_iters: warmupIterations }; } tryIntrospect(); function withCodSpeed(bench) { const codspeedRunnerMode = getCodspeedRunnerMode(); if (codspeedRunnerMode === "disabled") { return bench; } const rootCallingFile = getCallingFile(); const uriMap = getOrCreateUriMap(bench); const rawAdd = bench.add; bench.add = (name, fn, opts) => { const callingFile = getCallingFile(); let uri = callingFile; if (bench.name !== void 0) { uri += `::${bench.name}`; } uri += `::${name}`; uriMap.set(name, uri); return rawAdd.bind(bench)(name, fn, opts); }; if (codspeedRunnerMode === "instrumented") { setupCodspeedInstrumentedBench(bench, rootCallingFile); } else if (codspeedRunnerMode === "walltime") { setupCodspeedWalltimeBench(bench, rootCallingFile); } return bench; } function getCallingFile() { const stack = get(); let callingFile = stack[2].getFileName(); const gitDir = getGitDir(callingFile); if (gitDir === void 0) { throw new Error("Could not find a git repository"); } if (callingFile.startsWith("file://")) { callingFile = fileURLToPath(callingFile); } return path.relative(gitDir, callingFile); } async function setupInstruments(body) { if (!InstrumentHooks.isInstrumented()) { console.warn("[CodSpeed] No instrumentation found, using default mongoUrl"); return { remoteAddr: body.mongoUrl }; } return await mongoMeasurement.setupInstruments(body); } export { setupInstruments, withCodSpeed }; //# sourceMappingURL=index.es5.js.map