UNPKG

tzientist

Version:

Scientist-like library for Node.js in TypeScript

154 lines (153 loc) 5.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.experiment = experiment; exports.experimentAsync = experimentAsync; function hrtimeToMs(hrtime) { const MS_PER_SEC = 1000; const NS_PER_MS = 1e6; const [seconds, nanoseconds] = hrtime; return seconds * MS_PER_SEC + nanoseconds / NS_PER_MS; } function defaultPublish(results) { if (results.candidateResult !== results.controlResult || (results.candidateError && !results.controlError) || (!results.candidateError && results.controlError)) { console.warn(`Experiment ${results.experimentName}: difference found`); } } const defaultOptionsSync = { publish: defaultPublish }; /** * A factory that creates an experiment function. * * @param name - The name of the experiment, typically for use in publish. * @param control - The legacy function you are trying to replace. * @param candidate - The new function intended to replace the control. * @param [options] - Options for the experiment. You will usually want to specify a publish function. * @returns A function that acts like the control while also running the candidate and publishing results. */ function experiment({ name, control, candidate, options = defaultOptionsSync }) { const publish = options.publish || defaultPublish; return (...args) => { let controlResult; let candidateResult; let controlError; let candidateError; let controlTimeMs; let candidateTimeMs; const isEnabled = !options.enabled || options.enabled(...args); function publishResults() { if (isEnabled) { publish({ experimentName: name, experimentArguments: args, controlResult, candidateResult, controlError, candidateError, controlTimeMs, candidateTimeMs }); } } if (isEnabled) { try { // Not using bigint version of hrtime for Node 8 compatibility const candidateStartTime = process.hrtime(); candidateResult = candidate(...args); candidateTimeMs = hrtimeToMs(process.hrtime(candidateStartTime)); } catch (e) { candidateError = e; } } try { const controlStartTime = process.hrtime(); controlResult = control(...args); controlTimeMs = hrtimeToMs(process.hrtime(controlStartTime)); } catch (e) { controlError = e; publishResults(); throw e; } publishResults(); return controlResult; }; } async function executeAndTime(controlOrCandidate, args) { // Not using bigint version of hrtime for Node 8 compatibility const startTime = process.hrtime(); const result = await controlOrCandidate(...args); const timeMs = hrtimeToMs(process.hrtime(startTime)); return [result, timeMs]; } const defaultOptionsAsync = Object.assign(Object.assign({}, defaultOptionsSync), { inParallel: true }); /** * A factory that creates an asynchronous experiment function. * * @param name - The name of the experiment, typically for use in publish. * @param control - The legacy async function you are trying to replace. * @param candidate - The new async function intended to replace the control. * @param [options] - Options for the experiment. You will usually want to specify a publish function. * @returns An async function that acts like the control while also running the candidate and publishing results. */ function experimentAsync({ name, control, candidate, options = defaultOptionsAsync }) { var _a, _b; const publish = (_a = options.publish) !== null && _a !== void 0 ? _a : defaultOptionsAsync.publish; const inParallel = (_b = options.inParallel) !== null && _b !== void 0 ? _b : defaultOptionsAsync.inParallel; return async (...args) => { let controlResult; let candidateResult; let controlError; let candidateError; let controlTimeMs; let candidateTimeMs; const isEnabled = !options.enabled || options.enabled(...args); function publishResults() { if (isEnabled) { publish({ experimentName: name, experimentArguments: args, controlResult, candidateResult, controlError, candidateError, controlTimeMs, candidateTimeMs }); } } if (isEnabled) { const runFunctions = getRunFunctions(inParallel); [[candidateResult, candidateTimeMs], [controlResult, controlTimeMs]] = await runFunctions(() => executeAndTime(candidate, args).catch((e) => { candidateError = e; return [undefined, undefined]; }), () => executeAndTime(control, args).catch((e) => { controlError = e; return [undefined, undefined]; })); } else { controlResult = await control(...args).catch((e) => { controlError = e; return undefined; }); } publishResults(); if (controlError) { throw controlError; } return controlResult; }; } function getRunFunctions(inParallel) { return async (function1, function2) => { if (inParallel) { return Promise.all([function1(), function2()]); } return [await function1(), await function2()]; }; }