@codspeed/tinybench-plugin
Version:
tinybench compatibility layer for CodSpeed
367 lines (350 loc) • 11.2 kB
JavaScript
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 /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