@contrast/perf
Advanced tools
Comparing version 1.2.2 to 1.3.0
@@ -34,2 +34,6 @@ /* | ||
class Perf { | ||
/** | ||
* @param {string} prefix | ||
* @param {any} options | ||
*/ | ||
constructor(prefix, options = {}) { | ||
@@ -294,3 +298,3 @@ this.prefix = prefix || ''; | ||
// writeAllMap() is async, but we can't await because we're in setInterval | ||
// callback. the key here is that the interval need to be long enough | ||
// callback. the key here is that the interval needs to be long enough | ||
// that any async operations will complete before the next interval. | ||
@@ -379,4 +383,4 @@ // given that the interval is 60 seconds (and can't be user configured), | ||
* | ||
* @param {Object} pino - the pino instance to wrap | ||
* @returns {Object} - the wrapped pino instance | ||
* @param {import('pino').Logger} pino - the pino instance to wrap | ||
* @returns {import('pino').Logger} - the wrapped pino instance | ||
*/ | ||
@@ -406,2 +410,26 @@ wrapPinoInstance(pino) { | ||
/** | ||
* Wraps an emitter and records each message type as a separate tag. Only | ||
* wraps the on() method. | ||
*/ | ||
wrapEmitter(emitter, tag) { | ||
if (!Perf.isEnabled) { | ||
return emitter; | ||
} | ||
const perf = this; | ||
tag = `${this.prefix}:${tag}:on`; | ||
const originalOn = emitter.on; | ||
emitter.on = function(event, origListener) { | ||
originalOn.call(emitter, event, function(...args) { | ||
const start = hrtime.bigint(); | ||
const result = origListener.call(this, ...args); | ||
perf.record(`${tag}:${event}`, start); | ||
return result; | ||
}); | ||
}; | ||
return emitter; | ||
} | ||
/** | ||
* Wraps a function so that it is timed. This is used for functions like | ||
@@ -408,0 +436,0 @@ * String.prototype.replace, so that the primitive functions can be timed. |
'use strict'; | ||
const { EventEmitter } = require('node:events'); | ||
const Perf = require('.'); | ||
@@ -37,402 +38,511 @@ | ||
// }); | ||
}); | ||
describe('wrap functions', function () { | ||
let perf; | ||
beforeEach(function() { | ||
Perf.isEnabled = false; | ||
Perf.all.clear(); | ||
perf = new Perf('wrap-test'); | ||
}); | ||
describe('wrapInit functions', function () { | ||
let perf; | ||
beforeEach(function() { | ||
Perf.isEnabled = false; | ||
Perf.all.clear(); | ||
perf = new Perf('wrap-test'); | ||
}); | ||
it('wrapInit returns the original function when Perf is not enabled', function() { | ||
Perf.isEnabled = false; | ||
function init() { | ||
return { | ||
install() { | ||
return 'i-am-install'; | ||
} | ||
}; | ||
} | ||
const wrapped = perf.wrapInit(init, 'tag'); | ||
expect(wrapped).equal(init); | ||
}); | ||
it('wrapInit returns the original function when Perf is not enabled', function() { | ||
Perf.isEnabled = false; | ||
function init() { | ||
return { | ||
install() { | ||
return 'i-am-install'; | ||
} | ||
}; | ||
} | ||
const wrapped = perf.wrapInit(init, 'tag'); | ||
expect(wrapped).equal(init); | ||
}); | ||
it('wrapInit returns a wrapped function when Perf is enabled', async function() { | ||
Perf.isEnabled = true; | ||
it('wrapInit returns a wrapped function when Perf is enabled', async function() { | ||
Perf.isEnabled = true; | ||
async function install() { | ||
return 'i-am-install'; | ||
} | ||
function init() { | ||
return { | ||
install | ||
}; | ||
} | ||
async function install() { | ||
return 'i-am-install'; | ||
} | ||
function init() { | ||
return { | ||
install | ||
}; | ||
} | ||
// wrap init | ||
const wrapped = perf.wrapInit(init, 'tag'); | ||
expect(wrapped).not.equal(init); | ||
expect(wrapped).instanceOf(Function); | ||
// wrap init | ||
const wrapped = perf.wrapInit(init, 'tag'); | ||
expect(wrapped).not.equal(init); | ||
expect(wrapped).instanceOf(Function); | ||
// invoke the wrapped function | ||
const result = wrapped(); | ||
expect(result.install).not.equal(install); | ||
expect(result.install).instanceOf(Function); | ||
// invoke the wrapped function | ||
const result = wrapped(); | ||
expect(result.install).not.equal(install); | ||
expect(result.install).instanceOf(Function); | ||
// invoke install (an async function) | ||
let installResult = result.install(); | ||
expect(installResult).instanceOf(Promise); | ||
installResult = await installResult; | ||
expect(installResult).equal('i-am-install'); | ||
// invoke install (an async function) | ||
let installResult = result.install(); | ||
expect(installResult).instanceOf(Promise); | ||
installResult = await installResult; | ||
expect(installResult).equal('i-am-install'); | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(2); | ||
expect(perf.timings.get('wrap-test:tag:init')).instanceOf(Array) | ||
.length(2); | ||
expect(perf.timings.get('wrap-test:tag:install')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(2); | ||
expect(perf.timings.get('wrap-test:tag:init')).instanceOf(Array) | ||
.length(2); | ||
expect(perf.timings.get('wrap-test:tag:install')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
}); | ||
it('wrapReporter returns original class when Perf not enabled', function() { | ||
Perf.isEnabled = false; | ||
class Reporter { | ||
constructor() { } | ||
get() { } | ||
post() { } | ||
put() { } | ||
} | ||
describe('wrapReporter functions', function() { | ||
let perf; | ||
beforeEach(function() { | ||
Perf.isEnabled = false; | ||
Perf.all.clear(); | ||
perf = new Perf('wrap-test'); | ||
}); | ||
const wrapped = new Perf().wrapReporter(Reporter); | ||
expect(wrapped).equal(Reporter); | ||
}); | ||
it('wrapReporter returns original class when Perf not enabled', function() { | ||
Perf.isEnabled = false; | ||
class Reporter { | ||
constructor() { } | ||
get() { } | ||
post() { } | ||
put() { } | ||
} | ||
it('wrapReporter returns a subclass when Perf is enabled', async function() { | ||
Perf.isEnabled = true; | ||
class Reporter { | ||
constructor() { } | ||
const wrapped = new Perf().wrapReporter(Reporter); | ||
expect(wrapped).equal(Reporter); | ||
}); | ||
/* eslint-disable */ | ||
async get() { return 'i-am-get'; } | ||
async post() { return 'i-am-post'; } | ||
async put() { return 'i-am-put'; } | ||
/* eslint-enable */ | ||
} | ||
const wrapped = perf.wrapReporter(Reporter); | ||
expect(wrapped).not.equal(Reporter); | ||
expect(wrapped).instanceOf(Function); | ||
it('wrapReporter returns a subclass when Perf is enabled', async function() { | ||
Perf.isEnabled = true; | ||
class Reporter { | ||
constructor() { } | ||
const reporter = new wrapped(); | ||
expect(reporter).instanceOf(Reporter); | ||
for (const method of ['get', 'post', 'put']) { | ||
expect(reporter[method]).instanceOf(Function); | ||
let result = reporter[method](); | ||
expect(result).instanceOf(Promise); | ||
result = await result; | ||
expect(result).equal(`i-am-${method}`); | ||
} | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(3); | ||
expect(perf.timings.get('wrap-test:Reporter:get')).instanceOf(Array) | ||
.length(2); | ||
expect(perf.timings.get('wrap-test:Reporter:post')).instanceOf(Array) | ||
.length(2); | ||
expect(perf.timings.get('wrap-test:Reporter:put')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
it('wrapPinoInstance returns original when Perf not enabled', function() { | ||
Perf.isEnabled = false; | ||
const pino = { | ||
info() { }, | ||
warn() { }, | ||
error() { }, | ||
trace() { }, | ||
debug() { }, | ||
fatal() { }, | ||
}; | ||
const wrapped = perf.wrapPinoInstance(pino); | ||
expect(wrapped).equal(pino); | ||
}); | ||
it('wrapPinoInstance wraps instance when Perf is enabled', function() { | ||
Perf.isEnabled = true; | ||
/* eslint-disable */ | ||
const originals = { | ||
info() { return 'i-am-info' }, | ||
warn() { return 'i-am-warn' }, | ||
error() { return 'i-am-error' }, | ||
trace() { return 'i-am-trace' }, | ||
debug() { return 'i-am-debug' }, | ||
fatal() { return 'i-am-fatal' }, | ||
}; | ||
async get() { return 'i-am-get'; } | ||
async post() { return 'i-am-post'; } | ||
async put() { return 'i-am-put'; } | ||
/* eslint-enable */ | ||
} | ||
const wrapped = perf.wrapReporter(Reporter); | ||
expect(wrapped).not.equal(Reporter); | ||
expect(wrapped).instanceOf(Function); | ||
const pino = { | ||
info: originals.info, | ||
warn: originals.warn, | ||
error: originals.error, | ||
trace: originals.trace, | ||
debug: originals.debug, | ||
fatal: originals.fatal, | ||
}; | ||
const wrapped = perf.wrapPinoInstance(pino); | ||
expect(wrapped).instanceOf(Object); | ||
const reporter = new wrapped(); | ||
expect(reporter).instanceOf(Reporter); | ||
for (const method of ['info', 'warn', 'error', 'trace', 'debug', 'fatal']) { | ||
expect(wrapped[method]).instanceOf(Function); | ||
expect(wrapped[method]).not.equal(originals[method]); | ||
expect(wrapped[method]()).equal(`i-am-${method}`); | ||
const result = wrapped[method](); | ||
expect(result).equal(`i-am-${method}`); | ||
} | ||
for (const method of ['get', 'post', 'put']) { | ||
expect(reporter[method]).instanceOf(Function); | ||
let result = reporter[method](); | ||
expect(result).instanceOf(Promise); | ||
result = await result; | ||
expect(result).equal(`i-am-${method}`); | ||
} | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(6); | ||
for (const method of ['info', 'warn', 'error', 'trace', 'debug', 'fatal']) { | ||
expect(perf.timings.get(`pino:${method}`)).instanceOf(Array) | ||
.length(2); | ||
} | ||
}); | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(3); | ||
expect(perf.timings.get('wrap-test:Reporter:get')).instanceOf(Array) | ||
.length(2); | ||
expect(perf.timings.get('wrap-test:Reporter:post')).instanceOf(Array) | ||
.length(2); | ||
expect(perf.timings.get('wrap-test:Reporter:put')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
}); | ||
for (const enabled of [true, false]) { | ||
it(`wrapSync wraps original function when Perf is ${enabled ? 'enabled' : 'disabled'}`, function() { | ||
Perf.isEnabled = enabled; | ||
const original = function xyzzy() { | ||
return 'i-am-original'; | ||
}; | ||
const wrapped = perf.wrapSync(original, 'tag'); | ||
expect(wrapped).not.equal(original); | ||
describe('wrapPinoInstance functions', function() { | ||
let perf; | ||
beforeEach(function() { | ||
Perf.isEnabled = false; | ||
Perf.all.clear(); | ||
perf = new Perf('wrap-test'); | ||
}); | ||
it('wrapPinoInstance returns original when Perf not enabled', function() { | ||
Perf.isEnabled = false; | ||
const pino = { | ||
info() { }, | ||
warn() { }, | ||
error() { }, | ||
trace() { }, | ||
debug() { }, | ||
fatal() { }, | ||
}; | ||
const wrapped = perf.wrapPinoInstance(pino); | ||
expect(wrapped).equal(pino); | ||
}); | ||
const result = wrapped(); | ||
expect(result).equal('i-am-original'); | ||
it('wrapPinoInstance wraps instance when Perf is enabled', function() { | ||
Perf.isEnabled = true; | ||
/* eslint-disable */ | ||
const originals = { | ||
info() { return 'i-am-info' }, | ||
warn() { return 'i-am-warn' }, | ||
error() { return 'i-am-error' }, | ||
trace() { return 'i-am-trace' }, | ||
debug() { return 'i-am-debug' }, | ||
fatal() { return 'i-am-fatal' }, | ||
}; | ||
/* eslint-enable */ | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(1); | ||
expect(perf.timings.get('wrap-test:tag:xyzzy')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
const pino = { | ||
info: originals.info, | ||
warn: originals.warn, | ||
error: originals.error, | ||
trace: originals.trace, | ||
debug: originals.debug, | ||
fatal: originals.fatal, | ||
}; | ||
const wrapped = perf.wrapPinoInstance(pino); | ||
expect(wrapped).instanceOf(Object); | ||
for (const method of ['info', 'warn', 'error', 'trace', 'debug', 'fatal']) { | ||
expect(wrapped[method]).instanceOf(Function); | ||
expect(wrapped[method]).not.equal(originals[method]); | ||
expect(wrapped[method]()).equal(`i-am-${method}`); | ||
const result = wrapped[method](); | ||
expect(result).equal(`i-am-${method}`); | ||
} | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(6); | ||
for (const method of ['info', 'warn', 'error', 'trace', 'debug', 'fatal']) { | ||
expect(perf.timings.get(`pino:${method}`)).instanceOf(Array) | ||
.length(2); | ||
} | ||
}); | ||
}); | ||
describe('Perf map functions', function() { | ||
describe('wrapEmitter functions', function() { | ||
let perf; | ||
before(function() { | ||
let emitter; | ||
beforeEach(function() { | ||
Perf.isEnabled = false; | ||
Perf.all.clear(); | ||
perf = new Perf('wrap-emitter-test'); | ||
emitter = new EventEmitter(); | ||
}); | ||
beforeEach(function() { | ||
perf = new Perf('map-test'); | ||
it('wrapEmitter returns the original function when Perf is not enabled', function() { | ||
Perf.isEnabled = false; | ||
const wrapped = perf.wrapEmitter(emitter, 'messages'); | ||
expect(wrapped).equal(emitter); | ||
}); | ||
afterEach(function() { | ||
//console.log(Perf.all); | ||
}); | ||
// record, recordExcluding | ||
it('records a timing', function() { | ||
const start = process.hrtime.bigint(); | ||
perf.record('record', start); | ||
expect(Perf.all.get('map-test')).equal(perf.timings); | ||
expect(perf.timings.get('record')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
it('wrapEmitter returns a wrapped function when Perf is enabled', async function() { | ||
Perf.isEnabled = true; | ||
const perfName = 'emitter'; | ||
perf = new Perf(perfName); | ||
it('records a timing excluding a delta time', function() { | ||
const start = process.hrtime.bigint(); | ||
perf.recordExcluding('recordExcluding', start, 100_000n); | ||
expect(Perf.all.get('map-test')).equal(perf.timings); | ||
expect(perf.timings.get('recordExcluding')).instanceOf(Array) | ||
.length(2); | ||
// it's a bit gross to force it negative, but allows testing | ||
// that the supplied value was subtracted. | ||
const [_, delta] = perf.timings.get('recordExcluding'); | ||
//expect(Number(n)).equal(1); | ||
// .lt(0n) works but .gt(-100_000n) throws "expected -82267 to be a | ||
// number or date", so convert the bigint. | ||
expect(Number(delta)).lt(0).gt(-100_000); // eslint-disable-line | ||
//console.log(n, delta); | ||
const calls = []; | ||
function handler(event, ...args) { | ||
calls.push([event, ...args]); | ||
return 'i-am-handler'; | ||
} | ||
const originalEmitter = emitter; | ||
const originalOn = emitter.on; | ||
const tag = 'messages'; | ||
const wrapped = perf.wrapEmitter(emitter, tag); | ||
expect(wrapped.on).not.equal(originalOn); | ||
expect(wrapped).instanceOf(EventEmitter); | ||
expect(emitter).equal(originalEmitter); | ||
wrapped.on('event-one', handler); | ||
wrapped.on('event-two', handler); | ||
wrapped.emit('event-one', 1, 'one'); | ||
wrapped.emit('event-two', 2, 'two'); | ||
expect(calls).deep.equal([ | ||
[1, 'one'], | ||
[2, 'two'], | ||
]); | ||
/* eslint-disable newline-per-chained-call */ | ||
expect(Perf.all.get(perfName)).equal(perf.timings); | ||
expect(perf.timings.size).equal(2); | ||
const eventOneTimings = perf.timings.get(`${perfName}:${tag}:on:event-one`); | ||
expect(eventOneTimings).an('array').length(2); | ||
expect(eventOneTimings[0]).equal(1); // count | ||
expect(eventOneTimings[1]).a('bigint'); | ||
const eventTwoTimings = perf.timings.get(`${perfName}:${tag}:on:event-two`); | ||
expect(eventTwoTimings).an('array').length(2); | ||
expect(eventTwoTimings[0]).equal(1); // count | ||
expect(eventTwoTimings[1]).a('bigint'); | ||
}); | ||
}); | ||
describe('Collected stats', function() { | ||
before(function() { | ||
describe('wrap sync and async functions', function() { | ||
let perf; | ||
beforeEach(function() { | ||
Perf.isEnabled = false; | ||
Perf.all.clear(); | ||
perf = new Perf('wrap-test'); | ||
}); | ||
it('calculates stats when done', function() { | ||
let fn; | ||
const perf = new Perf('stats-test', { hrtime: () => fn() }); | ||
for (let i = 0; i < 10; i++) { | ||
fn = function() { | ||
return BigInt(i * 1_000_000); | ||
for (const enabled of [true, false]) { | ||
it(`wrapSync wraps original function when Perf is ${enabled ? 'enabled' : 'disabled'}`, function() { | ||
Perf.isEnabled = enabled; | ||
const original = function xyzzy() { | ||
return 'i-am-original'; | ||
}; | ||
perf.record('stats', 0n); | ||
} | ||
const stats = perf.getStats(perf.timings); | ||
const wrapped = perf.wrapSync(original, 'tag'); | ||
expect(wrapped).not.equal(original); | ||
expect(stats).instanceOf(Map); | ||
expect(stats.size).equal(1); | ||
expect(stats.get('stats')).deep.equal({ | ||
n: 10, | ||
totalMicros: 45_000, | ||
mean: 4500, | ||
const result = wrapped(); | ||
expect(result).equal('i-am-original'); | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(1); | ||
expect(perf.timings.get('wrap-test:tag:xyzzy')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
}); | ||
} | ||
it('calculates streaming stats', function() { | ||
let fn; | ||
const perf = new Perf('streaming-test', { streaming: true, hrtime: () => fn() }); | ||
for (let i = 0; i < 10; i++) { | ||
fn = function() { | ||
return BigInt(i * 1_000_000); | ||
for (const enabled of [true, false]) { | ||
it(`wrapAsync wraps original function when Perf is ${enabled ? 'enabled' : 'disabled'}`, async function() { | ||
Perf.isEnabled = enabled; | ||
const original = async function xyzzy() { | ||
return new Promise(resolve => setTimeout(() => resolve('i-am-original'), 10)); | ||
}; | ||
perf.record('streaming', 0n); | ||
} | ||
const stats = perf.getStats(); | ||
const wrapped = perf.wrapAsync(original, 'tag'); | ||
expect(wrapped).not.equal(original); | ||
expect(stats).instanceOf(Map); | ||
expect(stats.size).equal(1); | ||
const streamingStats = stats.get('streaming'); | ||
expect(streamingStats.n).equal(10); | ||
expect(streamingStats.totalMicros).equal(45_000n); | ||
expect(streamingStats.mean).equal(4500); | ||
expect(streamingStats.min).equal(0n); | ||
expect(streamingStats.max).equal(9000n); | ||
// verified with https://www.standarddeviationcalculator.io/ | ||
expect(streamingStats.popStddev).equal(2872.28); | ||
expect(streamingStats.sampleStddev).equal(3027.65); | ||
}); | ||
const result = await wrapped(); | ||
expect(result).equal('i-am-original'); | ||
expect(Perf.all.get('wrap-test')).equal(perf.timings); | ||
expect(perf.timings.size).equal(1); | ||
expect(perf.timings.get('wrap-test:tag:xyzzy')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
} | ||
}); | ||
}); | ||
describe('test prototype', function() { | ||
const instances = []; | ||
const values = { | ||
'instance-0': [10, 20, 30], | ||
'instance-1': [100, 200, 300], | ||
'instance-2': [1000, 2000, 3000], | ||
'streaming': [10000, 20000, 30000], | ||
}; | ||
let firstMark; | ||
before(async function() { | ||
Perf.all.clear(); | ||
const prefixes = Object.keys(values); | ||
for (let i = 0; i < 4; i++) { | ||
const prefix = prefixes[i]; | ||
const options = { | ||
streaming: prefix === 'streaming', | ||
hrtime: () => BigInt(values[prefix].shift()) | ||
}; | ||
instances.push(new Perf(prefix, options)); | ||
for (let j = 0; j < 3; j++) { | ||
instances[i].record('test', 0n); | ||
} | ||
} | ||
}); | ||
describe('Perf map functions', function() { | ||
let perf; | ||
it('dumps some stuff', function() { | ||
// make a bunch of perf instances | ||
const i0 = instances[0].timings; | ||
const i1 = instances[1].timings; | ||
const i2 = instances[2].timings; | ||
const i3 = instances[3].timings; | ||
before(function() { | ||
Perf.all.clear(); | ||
}); | ||
expect(i0).deep.equal(new Map([['test', [3, 60n]]])); | ||
expect(i1).deep.equal(new Map([['test', [3, 600n]]])); | ||
expect(i2).deep.equal(new Map([['test', [3, 6000n]]])); | ||
// maybe Perf should expose RunningStats? | ||
expect(i3).instanceOf(Map); | ||
expect(i3.size).equal(1); | ||
const i3stats = i3.get('test'); | ||
expect(i3stats).property('n', 3); | ||
expect(i3stats).property('totalMicros', 60n); // it's been converted to microseconds. | ||
beforeEach(function() { | ||
perf = new Perf('map-test'); | ||
}); | ||
afterEach(function() { | ||
//console.log(Perf.all); | ||
}); | ||
Perf.mark('listening'); | ||
// record, recordExcluding | ||
it('records a timing', function() { | ||
const start = process.hrtime.bigint(); | ||
perf.record('record', start); | ||
expect(Perf.all.get('map-test')).equal(perf.timings); | ||
expect(perf.timings.get('record')).instanceOf(Array) | ||
.length(2); | ||
}); | ||
expect(Perf.marks.get('listening')).instanceOf(Map); | ||
const mark0 = Perf.marks.get('listening').get('instance-0'); | ||
const mark1 = Perf.marks.get('listening').get('instance-1'); | ||
const mark2 = Perf.marks.get('listening').get('instance-2'); | ||
const mark3 = Perf.marks.get('listening').get('streaming'); | ||
it('records a timing excluding a delta time', function() { | ||
const start = process.hrtime.bigint(); | ||
perf.recordExcluding('recordExcluding', start, 100_000n); | ||
expect(Perf.all.get('map-test')).equal(perf.timings); | ||
expect(perf.timings.get('recordExcluding')).instanceOf(Array) | ||
.length(2); | ||
// it's a bit gross to force it negative, but allows testing | ||
// that the supplied value was subtracted. | ||
const [_, delta] = perf.timings.get('recordExcluding'); | ||
//expect(Number(n)).equal(1); | ||
// .lt(0n) works but .gt(-100_000n) throws "expected -82267 to be a | ||
// number or date", so convert the bigint. | ||
expect(Number(delta)).lt(0).gt(-100_000); // eslint-disable-line | ||
//console.log(n, delta); | ||
}); | ||
}); | ||
expect(mark0).deep.equal(new Map([['test', [3, 60n]]])); | ||
expect(mark1).deep.equal(new Map([['test', [3, 600n]]])); | ||
expect(mark2).deep.equal(new Map([['test', [3, 6000n]]])); | ||
describe('Collected stats', function() { | ||
before(function() { | ||
Perf.all.clear(); | ||
}); | ||
const s3 = mark3.get('test'); | ||
expect(s3).property('n', 3); | ||
expect(s3).property('totalMicros', 60n); | ||
it('calculates stats when done', function() { | ||
let fn; | ||
const perf = new Perf('stats-test', { hrtime: () => fn() }); | ||
/* eslint-disable */ | ||
expect(i0).instanceOf(Map).property('size', 0); | ||
expect(i1).instanceOf(Map).property('size', 0); | ||
expect(i2).instanceOf(Map).property('size', 0); | ||
expect(i3).instanceOf(Map).property('size', 0); | ||
/* eslint-enable */ | ||
for (let i = 0; i < 10; i++) { | ||
fn = function() { | ||
return BigInt(i * 1_000_000); | ||
}; | ||
perf.record('stats', 0n); | ||
} | ||
const stats = perf.getStats(perf.timings); | ||
expect(stats).instanceOf(Map); | ||
expect(stats.size).equal(1); | ||
expect(stats.get('stats')).deep.equal({ | ||
n: 10, | ||
totalMicros: 45_000, | ||
mean: 4500, | ||
}); | ||
}); | ||
it('gets the same answer for the streaming stats', async function() { | ||
// duplicate the existing listening stats | ||
firstMark = new Map(Perf.marks.get('listening')); | ||
const firstMarkMaps = {}; | ||
for (const [prefix, data] of firstMark) { | ||
firstMarkMaps[prefix] = new Map(data); | ||
} | ||
it('calculates streaming stats', function() { | ||
let fn; | ||
const perf = new Perf('streaming-test', { streaming: true, hrtime: () => fn() }); | ||
for (let i = 0; i < 10; i++) { | ||
fn = function() { | ||
return BigInt(i * 1_000_000); | ||
}; | ||
perf.record('streaming', 0n); | ||
} | ||
const stats = perf.getStats(); | ||
// add two more perf measurements | ||
const values = { | ||
'instance-0': [40, 50], | ||
'instance-1': [400, 500], | ||
'instance-2': [4000, 5000], | ||
'streaming': [40000, 50000], | ||
expect(stats).instanceOf(Map); | ||
expect(stats.size).equal(1); | ||
const streamingStats = stats.get('streaming'); | ||
expect(streamingStats.n).equal(10); | ||
expect(streamingStats.totalMicros).equal(45_000n); | ||
expect(streamingStats.mean).equal(4500); | ||
expect(streamingStats.min).equal(0n); | ||
expect(streamingStats.max).equal(9000n); | ||
// verified with https://www.standarddeviationcalculator.io/ | ||
expect(streamingStats.popStddev).equal(2872.28); | ||
expect(streamingStats.sampleStddev).equal(3027.65); | ||
}); | ||
}); | ||
describe('test prototype', function() { | ||
const instances = []; | ||
const values = { | ||
'instance-0': [10, 20, 30], | ||
'instance-1': [100, 200, 300], | ||
'instance-2': [1000, 2000, 3000], | ||
'streaming': [10000, 20000, 30000], | ||
}; | ||
let firstMark; | ||
before(async function() { | ||
Perf.all.clear(); | ||
const prefixes = Object.keys(values); | ||
for (let i = 0; i < 4; i++) { | ||
const prefix = prefixes[i]; | ||
const options = { | ||
streaming: prefix === 'streaming', | ||
hrtime: () => BigInt(values[prefix].shift()) | ||
}; | ||
const prefixes = Object.keys(values); | ||
for (let i = 0; i < 4; i++) { | ||
const prefix = prefixes[i]; | ||
instances[i].hrtime = { bigint: () => BigInt(values[prefix].shift()) }; | ||
for (let j = 0; j < 2; j++) { | ||
instances[i].record('test', 0n); | ||
} | ||
instances.push(new Perf(prefix, options)); | ||
for (let j = 0; j < 3; j++) { | ||
instances[i].record('test', 0n); | ||
} | ||
} | ||
}); | ||
expect(Perf.marks.get('listening')).instanceOf(Map); | ||
const all0 = Perf.all.get('instance-0'); | ||
const all1 = Perf.all.get('instance-1'); | ||
const all2 = Perf.all.get('instance-2'); | ||
const all3 = Perf.all.get('streaming'); | ||
it('dumps some stuff', function() { | ||
// make a bunch of perf instances | ||
const i0 = instances[0].timings; | ||
const i1 = instances[1].timings; | ||
const i2 = instances[2].timings; | ||
const i3 = instances[3].timings; | ||
expect(all0).deep.equal(new Map([['test', [2, 90n]]])); | ||
expect(all1).deep.equal(new Map([['test', [2, 900n]]])); | ||
expect(all2).deep.equal(new Map([['test', [2, 9000n]]])); | ||
expect(i0).deep.equal(new Map([['test', [3, 60n]]])); | ||
expect(i1).deep.equal(new Map([['test', [3, 600n]]])); | ||
expect(i2).deep.equal(new Map([['test', [3, 6000n]]])); | ||
// maybe Perf should expose RunningStats? | ||
expect(i3).instanceOf(Map); | ||
expect(i3.size).equal(1); | ||
const i3stats = i3.get('test'); | ||
expect(i3stats).property('n', 3); | ||
expect(i3stats).property('totalMicros', 60n); // it's been converted to microseconds. | ||
let s3 = all3.get('test'); | ||
expect(s3).property('n', 2); | ||
expect(s3).property('totalMicros', 90n); | ||
Perf.mark('listening'); | ||
// ask for another mark of the same prefix. it should merge non-streaming | ||
// and *not* replace streaming stats. | ||
Perf.mark('listening'); | ||
expect(Perf.marks.get('listening')).instanceOf(Map); | ||
const mark0 = Perf.marks.get('listening').get('instance-0'); | ||
const mark1 = Perf.marks.get('listening').get('instance-1'); | ||
const mark2 = Perf.marks.get('listening').get('instance-2'); | ||
const mark3 = Perf.marks.get('listening').get('streaming'); | ||
/* eslint-disable */ | ||
expect(Perf.marks.get('listening')).instanceOf(Map); | ||
const mark0 = Perf.marks.get('listening').get('instance-0'); | ||
const mark1 = Perf.marks.get('listening').get('instance-1'); | ||
const mark2 = Perf.marks.get('listening').get('instance-2'); | ||
const mark3 = Perf.marks.get('listening').get('streaming'); | ||
/* eslint-enable */ | ||
expect(mark0).deep.equal(new Map([['test', [3, 60n]]])); | ||
expect(mark1).deep.equal(new Map([['test', [3, 600n]]])); | ||
expect(mark2).deep.equal(new Map([['test', [3, 6000n]]])); | ||
// simple stats are aggregated | ||
expect(mark0).deep.equal(new Map([['test', [5, 150n]]])); | ||
expect(mark1).deep.equal(new Map([['test', [5, 1500n]]])); | ||
expect(mark2).deep.equal(new Map([['test', [5, 15000n]]])); | ||
// streaming stats don't change | ||
s3 = mark3.get('test'); | ||
expect(s3).property('n', 3); | ||
expect(s3).property('totalMicros', 60n); | ||
}); | ||
const s3 = mark3.get('test'); | ||
expect(s3).property('n', 3); | ||
expect(s3).property('totalMicros', 60n); | ||
/* eslint-disable */ | ||
expect(i0).instanceOf(Map).property('size', 0); | ||
expect(i1).instanceOf(Map).property('size', 0); | ||
expect(i2).instanceOf(Map).property('size', 0); | ||
expect(i3).instanceOf(Map).property('size', 0); | ||
/* eslint-enable */ | ||
}); | ||
it('gets the same answer for the streaming stats', async function() { | ||
// duplicate the existing listening stats | ||
firstMark = new Map(Perf.marks.get('listening')); | ||
const firstMarkMaps = {}; | ||
for (const [prefix, data] of firstMark) { | ||
firstMarkMaps[prefix] = new Map(data); | ||
} | ||
// add two more perf measurements | ||
const values = { | ||
'instance-0': [40, 50], | ||
'instance-1': [400, 500], | ||
'instance-2': [4000, 5000], | ||
'streaming': [40000, 50000], | ||
}; | ||
const prefixes = Object.keys(values); | ||
for (let i = 0; i < 4; i++) { | ||
const prefix = prefixes[i]; | ||
instances[i].hrtime = { bigint: () => BigInt(values[prefix].shift()) }; | ||
for (let j = 0; j < 2; j++) { | ||
instances[i].record('test', 0n); | ||
} | ||
} | ||
expect(Perf.marks.get('listening')).instanceOf(Map); | ||
const all0 = Perf.all.get('instance-0'); | ||
const all1 = Perf.all.get('instance-1'); | ||
const all2 = Perf.all.get('instance-2'); | ||
const all3 = Perf.all.get('streaming'); | ||
expect(all0).deep.equal(new Map([['test', [2, 90n]]])); | ||
expect(all1).deep.equal(new Map([['test', [2, 900n]]])); | ||
expect(all2).deep.equal(new Map([['test', [2, 9000n]]])); | ||
let s3 = all3.get('test'); | ||
expect(s3).property('n', 2); | ||
expect(s3).property('totalMicros', 90n); | ||
// ask for another mark of the same prefix. it should merge non-streaming | ||
// and *not* replace streaming stats. | ||
Perf.mark('listening'); | ||
/* eslint-disable */ | ||
expect(Perf.marks.get('listening')).instanceOf(Map); | ||
const mark0 = Perf.marks.get('listening').get('instance-0'); | ||
const mark1 = Perf.marks.get('listening').get('instance-1'); | ||
const mark2 = Perf.marks.get('listening').get('instance-2'); | ||
const mark3 = Perf.marks.get('listening').get('streaming'); | ||
/* eslint-enable */ | ||
// simple stats are aggregated | ||
expect(mark0).deep.equal(new Map([['test', [5, 150n]]])); | ||
expect(mark1).deep.equal(new Map([['test', [5, 1500n]]])); | ||
expect(mark2).deep.equal(new Map([['test', [5, 15000n]]])); | ||
// streaming stats don't change | ||
s3 = mark3.get('test'); | ||
expect(s3).property('n', 3); | ||
expect(s3).property('totalMicros', 60n); | ||
}); | ||
}); |
{ | ||
"extends": "@tsconfig/node16/tsconfig.json", | ||
"compilerOptions": { | ||
// Enable constraints that allow a TypeScript project to be used with project references. | ||
// See more: https://www.typescriptlang.org/tsconfig#composite | ||
// We need this in all of our modules. | ||
"composite": true, | ||
// Tells TypeScript to read JS files, as | ||
@@ -5,0 +9,0 @@ // normally they are ignored as source files |
{ | ||
"name": "@contrast/perf", | ||
"version": "1.2.2", | ||
"version": "1.3.0", | ||
"description": "Performance measurement", | ||
@@ -5,0 +5,0 @@ "license": "SEE LICENSE IN LICENSE", |
46516
1190