cronometro
Advanced tools
Comparing version 1.0.3 to 1.0.4
@@ -0,1 +1,5 @@ | ||
### 2022-03-07 / 1.0.4 | ||
- chore: Updated build system. | ||
### 2022-01-26 / 1.0.3 | ||
@@ -2,0 +6,0 @@ |
@@ -1,17 +0,17 @@ | ||
'use strict'; | ||
import { isMainThread, Worker, workerData } from 'worker_threads'; | ||
import { defaultOptions, runnerPath } from "./models.js"; | ||
import { printResults } from "./print.js"; | ||
export * from "./models.js"; | ||
import { isMainThread, Worker, workerData } from 'node:worker_threads'; | ||
import { defaultOptions, runnerPath } from './models.js'; | ||
import { printResults } from './print.js'; | ||
export * from './models.js'; | ||
function scheduleNextTest(context) { | ||
// We still have work to do | ||
if (context.current < context.tests.length) { | ||
return process.nextTick(() => run(context)); | ||
return process.nextTick(()=>run(context) | ||
); | ||
} | ||
if (context.print) { | ||
const { colors, compare, compareMode } = { | ||
const { colors , compare , compareMode } = { | ||
colors: true, | ||
compare: false, | ||
compareMode: 'base', | ||
...(context.print === true ? {} : context.print) | ||
...context.print === true ? {} : context.print | ||
}; | ||
@@ -33,3 +33,3 @@ printResults(context.results, colors, compare, compareMode); | ||
}); | ||
worker.on('error', (error) => { | ||
worker.on('error', (error)=>{ | ||
context.results[name] = { | ||
@@ -49,3 +49,3 @@ success: false, | ||
}); | ||
worker.on('message', (result) => { | ||
worker.on('message', (result)=>{ | ||
context.results[name] = result; | ||
@@ -56,5 +56,3 @@ context.current++; | ||
} | ||
export function cronometro(tests, options, cb | ||
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type | ||
) { | ||
export function cronometro(tests, options, cb) { | ||
if (!isMainThread) { | ||
@@ -73,7 +71,7 @@ workerData.tests = Object.entries(tests); | ||
if (!callback) { | ||
promise = new Promise((resolve, reject) => { | ||
promise = new Promise((resolve, reject)=>{ | ||
promiseResolve = resolve; | ||
promiseReject = reject; | ||
}); | ||
callback = function (err, results) { | ||
callback = function(err, results) { | ||
if (err) { | ||
@@ -86,3 +84,6 @@ return promiseReject(err); | ||
// Parse and validate options | ||
const { iterations, errorThreshold, print, warmup } = { ...defaultOptions, ...options }; | ||
const { iterations , errorThreshold , print , warmup } = { | ||
...defaultOptions, | ||
...options | ||
}; | ||
if (typeof iterations !== 'number' || iterations < 1) { | ||
@@ -107,5 +108,6 @@ callback(new Error('The iterations option must be a positive number.')); | ||
}; | ||
process.nextTick(() => run(context)); | ||
process.nextTick(()=>run(context) | ||
); | ||
return promise; | ||
} | ||
export default cronometro; |
@@ -1,4 +0,4 @@ | ||
import { resolve } from 'path'; | ||
import { resolve } from 'node:path'; | ||
export const defaultOptions = { | ||
iterations: 1e4, | ||
iterations: 10000, | ||
warmup: true, | ||
@@ -8,3 +8,19 @@ errorThreshold: 1, | ||
}; | ||
export const percentiles = [0.001, 0.01, 0.1, 1, 2.5, 10, 25, 50, 75, 90, 97.5, 99, 99.9, 99.99, 99.999]; | ||
export const percentiles = [ | ||
0.001, | ||
0.01, | ||
0.1, | ||
1, | ||
2.5, | ||
10, | ||
25, | ||
50, | ||
75, | ||
90, | ||
97.5, | ||
99, | ||
99.9, | ||
99.99, | ||
99.999 | ||
]; | ||
export const runnerPath = resolve(import.meta.url.replace('file://', '').replace(/models.(js|ts)/, ''), './runner.js'); |
import { clean, colorize } from 'acquerello'; | ||
import { table } from 'table'; | ||
const styles = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray']; | ||
const styles = [ | ||
'red', | ||
'green', | ||
'yellow', | ||
'blue', | ||
'magenta', | ||
'cyan', | ||
'white', | ||
'gray' | ||
]; | ||
let currentLogger = console.log; | ||
@@ -14,5 +23,4 @@ export function setLogger(logger) { | ||
let standardErrorPadding = 0; | ||
const entries = Object.entries(results) | ||
.sort((a, b) => (!a[1].success ? -1 : b[1].mean - a[1].mean)) | ||
.map(([name, result]) => { | ||
const entries = Object.entries(results).sort((a, b)=>!a[1].success ? -1 : b[1].mean - a[1].mean | ||
).map(([name, result])=>{ | ||
if (!result.success) { | ||
@@ -29,3 +37,3 @@ return { | ||
} | ||
const { size, mean, standardError } = result; | ||
const { size , mean , standardError } = result; | ||
const relative = last !== 0 ? (last / mean - 1) * 100 : 0; | ||
@@ -37,8 +45,7 @@ if (mode === 'base') { | ||
} | ||
} | ||
else { | ||
} else { | ||
last = mean; | ||
compared = name; | ||
} | ||
const standardErrorString = ((standardError / mean) * 100).toFixed(2); | ||
const standardErrorString = (standardError / mean * 100).toFixed(2); | ||
standardErrorPadding = Math.max(standardErrorPadding, standardErrorString.length); | ||
@@ -49,3 +56,3 @@ return { | ||
error: null, | ||
throughput: (1e9 / mean).toFixed(2), | ||
throughput: (1000000000 / mean).toFixed(2), | ||
standardError: standardErrorString, | ||
@@ -57,3 +64,3 @@ relative: relative.toFixed(2), | ||
let currentColor = 0; | ||
const rows = entries.map((entry) => { | ||
const rows = entries.map((entry)=>{ | ||
if (entry.error) { | ||
@@ -71,4 +78,4 @@ const row = [ | ||
} | ||
const { name, size, throughput, standardError, relative } = entry; | ||
const color = styles[currentColor++ % styles.length]; | ||
const { name , size , throughput , standardError , relative } = entry; | ||
const color = styles[(currentColor++) % styles.length]; | ||
const row = [ | ||
@@ -81,6 +88,5 @@ styler(`{{${color}}}${name}{{-}}`), | ||
if (compare) { | ||
if (relative.match(/^[0.\s]+$/)) { | ||
if (/^[\s.0]+$/.test(relative)) { | ||
row.push(''); | ||
} | ||
else { | ||
} else { | ||
row.push(styler(`{{${color}}}+ ${relative} %{{-}}`)); | ||
@@ -98,3 +104,3 @@ } | ||
]); | ||
rows.splice(rows.length - 1, 0, [ | ||
rows.splice(-1, 0, [ | ||
styler('{{bold white}}Fastest test{{-}}'), | ||
@@ -107,3 +113,3 @@ styler('{{bold white}}Samples{{-}}'), | ||
rows[0].push(styler(`{{bold white}}${compareHeader}{{-}}`)); | ||
rows[rows.length - 2].push(styler(`{{bold white}}${compareHeader}{{-}}`)); | ||
rows.at(-2).push(styler(`{{bold white}}${compareHeader}{{-}}`)); | ||
} | ||
@@ -125,3 +131,3 @@ currentLogger(table(rows, { | ||
}, | ||
drawHorizontalLine(index, size) { | ||
drawHorizontalLine (index, size) { | ||
return index < 2 || index > size - 3; | ||
@@ -128,0 +134,0 @@ } |
@@ -1,3 +0,3 @@ | ||
import { isMainThread, parentPort, workerData } from 'worker_threads'; | ||
import { runWorker } from "./worker.js"; | ||
import { isMainThread, parentPort, workerData } from 'node:worker_threads'; | ||
import { runWorker } from './worker.js'; | ||
if (isMainThread) { | ||
@@ -8,8 +8,9 @@ throw new Error('Do not run this file as main script.'); | ||
let chain = Promise.resolve(); | ||
/* c8 ignore start */ | ||
if (workerData.path.endsWith('.ts')) { | ||
/* c8 ignore start */ if (workerData.path.endsWith('.ts')) { | ||
const instance = Symbol.for('ts-node.register.instance'); | ||
if (!(instance in process)) { | ||
chain = import('ts-node').then(({ register }) => { | ||
register({ project: process.env.TS_NODE_PROJECT }); | ||
chain = import('ts-node').then(({ register })=>{ | ||
register({ | ||
project: process.env.TS_NODE_PROJECT | ||
}); | ||
}); | ||
@@ -19,23 +20,18 @@ } | ||
// Require the script to set tests | ||
chain | ||
.then(() => { | ||
chain.then(()=>{ | ||
return import(workerData.path); | ||
}) | ||
.then((module) => { | ||
}).then((module)=>{ | ||
if (typeof module === 'function') { | ||
return module(); | ||
} | ||
else if (typeof module.default === 'function') { | ||
} else if (typeof module.default === 'function') { | ||
return module.default(); | ||
} | ||
}) | ||
.then(() => { | ||
}).then(()=>{ | ||
// Run the worker | ||
runWorker(workerData, (value) => parentPort.postMessage(value), process.exit); | ||
}) | ||
.catch((e) => { | ||
process.nextTick(() => { | ||
throw e; | ||
runWorker(workerData, (value)=>parentPort.postMessage(value) | ||
, process.exit); | ||
}).catch((error)=>{ | ||
process.nextTick(()=>{ | ||
throw error; | ||
}); | ||
}); | ||
/* c8 ignore stop */ | ||
}) /* c8 ignore stop */ ; |
import { build as buildHistogram } from 'hdr-histogram-js'; | ||
import { percentiles } from "./models.js"; | ||
import { percentiles } from './models.js'; | ||
function noOp() { | ||
// No-op | ||
// No-op | ||
} | ||
@@ -27,3 +27,3 @@ function noSetup(cb) { | ||
// Get some parameters | ||
const { histogram, total, errorThreshold } = context; | ||
const { histogram , total , errorThreshold } = context; | ||
// Track results | ||
@@ -36,3 +36,3 @@ histogram.recordValue(duration); | ||
if (errorThreshold > 0) { | ||
const completedPercentage = Math.floor((executed / total) * 10000); | ||
const completedPercentage = Math.floor(executed / total * 10000); | ||
// Check if abort the test earlier. It is checked every 5% after 10% of the iterations | ||
@@ -56,6 +56,7 @@ if (completedPercentage >= 1000 && completedPercentage % 500 === 0) { | ||
stddev: stdDev, | ||
percentiles: percentiles.reduce((accu, percentile) => { | ||
accu[percentile] = histogram.getValueAtPercentile(percentile); | ||
return accu; | ||
}, {}), | ||
percentiles: Object.fromEntries(percentiles.map((percentile)=>[ | ||
percentile, | ||
histogram.getValueAtPercentile(percentile) | ||
] | ||
)), | ||
standardError: stdDev / Math.sqrt(executed) | ||
@@ -65,3 +66,4 @@ }); | ||
// Schedule next iteration | ||
process.nextTick(() => runTestIteration(context)); | ||
process.nextTick(()=>runTestIteration(context) | ||
); | ||
} | ||
@@ -75,15 +77,14 @@ function runTestIteration(context) { | ||
if (callResult && typeof callResult.then === 'function') { | ||
callResult.then(() => context.handler(null), context.handler); | ||
} | ||
else if (context.test.length === 0) { | ||
callResult.then(()=>context.handler(null) | ||
, context.handler); | ||
} else if (context.test.length === 0) { | ||
// The function is not a promise and has no arguments, so it's sync | ||
context.handler(null); | ||
} | ||
} | ||
catch (e) { | ||
} catch (error) { | ||
// If a error was thrown, only handle if the original function length is 0, which means it's a sync error, otherwise propagate | ||
if (context.test.length === 0) { | ||
return context.handler(e); | ||
return context.handler(error); | ||
} | ||
throw e; | ||
throw error; | ||
} | ||
@@ -106,3 +107,4 @@ } | ||
// Schedule the first run | ||
return process.nextTick(() => runTestIteration(testContext)); | ||
return process.nextTick(()=>runTestIteration(testContext) | ||
); | ||
} | ||
@@ -124,4 +126,3 @@ function afterCallback(result, notifier, cb, err) { | ||
notifierCode = 1; | ||
} | ||
else { | ||
} else { | ||
notifier(result); | ||
@@ -132,3 +133,3 @@ } | ||
export function runWorker(context, notifier, cb) { | ||
const { warmup, tests, index, iterations, errorThreshold } = context; | ||
const { warmup , tests , index , iterations , errorThreshold } = context; | ||
// Require the original file to build tests | ||
@@ -142,4 +143,3 @@ const [name, testDefinition] = tests[index]; | ||
test = testDefinition; | ||
} | ||
else { | ||
} else { | ||
if (typeof testDefinition.test === 'function') { | ||
@@ -164,3 +164,3 @@ test = testDefinition.test; | ||
lowestDiscernibleValue: 1, | ||
highestTrackableValue: 1e9, | ||
highestTrackableValue: 1000000000, | ||
numberOfSignificantValueDigits: 5 | ||
@@ -171,3 +171,3 @@ }), | ||
notifier, | ||
callback(result) { | ||
callback (result) { | ||
if (warmup) { | ||
@@ -187,7 +187,7 @@ context.warmup = false; | ||
// Run the test setup, then start the test | ||
const callback = beforeCallback.bind(null, testContext); | ||
const beforeResponse = before(callback); | ||
const callback1 = beforeCallback.bind(null, testContext); | ||
const beforeResponse = before(callback1); | ||
if (beforeResponse && typeof beforeResponse.then === 'function') { | ||
beforeResponse.then(callback, callback); | ||
beforeResponse.then(callback1, callback1); | ||
} | ||
} |
{ | ||
"name": "cronometro", | ||
"version": "1.0.3", | ||
"version": "1.0.4", | ||
"description": "Simple benchmarking suite powered by HDR histograms.", | ||
@@ -29,9 +29,11 @@ "homepage": "https://sw.cowtech.it/cronometro", | ||
"scripts": { | ||
"prebuild": "rm -rf dist types && npm run lint", | ||
"build": "tsc -p . && esm-pkg-add-imports-extensions dist", | ||
"dev": "swc -w -d dist src", | ||
"prebuild": "rm -rf dist types && npm run typecheck && npm run lint", | ||
"build": "swc --delete-dir-on-start -d dist src", | ||
"format": "prettier -w src test", | ||
"typecheck": "tsc -p . --emitDeclarationOnly", | ||
"lint": "eslint src test", | ||
"test": "c8 --reporter=text --reporter=html esm-ts-tap -t 120 --reporter=spec --no-coverage test/*.test.ts", | ||
"test:ci": "c8 --reporter=text --reporter=json --check-coverage --branches 90 --functions 90 --lines 90 --statements 90 esm-ts-tap -t 120 --no-color --no-coverage test/*.test.ts", | ||
"ci": "npm run lint && npm run test:ci", | ||
"test": "c8 -c test/config/c8-local.json tap --rcfile=test/config/tap.yml test/*.test.ts", | ||
"test:ci": "c8 -c test/config/c8-ci.json tap --rcfile=test/config/tap.yml --no-color test/*.test.ts", | ||
"ci": "npm run build && npm run test:ci", | ||
"prepublishOnly": "npm run ci", | ||
@@ -41,19 +43,21 @@ "postpublish": "git push origin && git push origin -f --tags" | ||
"dependencies": { | ||
"acquerello": "^1.0.0", | ||
"acquerello": "^1.0.6", | ||
"hdr-histogram-js": "^3.0.0", | ||
"table": "^6.7.1" | ||
"table": "^6.8.0" | ||
}, | ||
"devDependencies": { | ||
"@cowtech/eslint-config": "^8.0.1", | ||
"@cowtech/esm-package-utils": "^0.9.3", | ||
"@types/node": "^17.0.2", | ||
"@types/sinon": "^10.0.2", | ||
"@types/tap": "^15.0.5", | ||
"c8": "^7.8.0", | ||
"prettier": "^2.3.2", | ||
"@cowtech/eslint-config": "^8.4.0", | ||
"@swc/cli": "^0.1.55", | ||
"@swc/core": "^1.2.150", | ||
"@types/node": "^17.0.21", | ||
"@types/sinon": "^10.0.11", | ||
"@types/tap": "^15.0.6", | ||
"c8": "^7.11.0", | ||
"chokidar": "^3.5.3", | ||
"prettier": "^2.5.1", | ||
"proxyquire": "^2.1.3", | ||
"sinon": "^12.0.1", | ||
"tap": "^15.0.9", | ||
"ts-node": "^10.2.0", | ||
"typescript": "^4.3.5" | ||
"tap": "^15.2.3", | ||
"ts-node": "^10.7.0", | ||
"typescript": "^4.6.2" | ||
}, | ||
@@ -60,0 +64,0 @@ "engines": { |
@@ -1,3 +0,3 @@ | ||
import { Callback, Options, Results, Tests } from './models'; | ||
export * from './models'; | ||
import { Callback, Options, Results, Tests } from './models.js'; | ||
export * from './models.js'; | ||
export declare function cronometro(tests: Tests): Promise<Results> | void; | ||
@@ -4,0 +4,0 @@ export declare function cronometro(tests: Tests, options: Partial<Options>): Promise<Results>; |
@@ -26,3 +26,3 @@ import { Histogram } from 'hdr-histogram-js'; | ||
} | ||
export declare type Callback = ((err: Error | null) => void) | ((err: null, results: Results) => any); | ||
export declare type Callback = (err: Error | null, results: Results) => any; | ||
export interface Percentiles { | ||
@@ -29,0 +29,0 @@ [key: string]: number; |
@@ -1,3 +0,3 @@ | ||
import { Results } from './models'; | ||
import { Results } from './models.js'; | ||
export declare function setLogger(logger: (message: string, ...params: Array<any>) => void): void; | ||
export declare function printResults(results: Results, colors: boolean, compare: boolean, mode: 'base' | 'previous'): void; |
@@ -1,2 +0,2 @@ | ||
import { WorkerContext } from './models'; | ||
import { WorkerContext } from './models.js'; | ||
export declare function runWorker(context: WorkerContext, notifier: (value: any) => void, cb: (code: number) => void): void; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30916
571
14
Updatedacquerello@^1.0.6
Updatedtable@^6.8.0