mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Most node:bench tests record a sample with `b.start();
process.hrtime.bigint(); b.end(1)`, counting on the extra clock read to
make end() see a later timestamp than start(). On a debian12-x64 CI
host the monotonic clock is coarse enough that all three reads returned
the same value, so end() threw ERR_INVALID_STATE ("insufficient clock
precision for benchmark sample") and test-bench-harness-errors failed;
every test using the idiom can fail the same way there.
Add test/common/bench.js with completeSample(), which spins until
process.hrtime.bigint() moves between start() and end(), and use it at
all of those sites, bench-runner fixtures included.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65780
Fixes: https://github.com/nodejs/node/issues/64226
Refs: https://github.com/nodejs/node/issues/59146
Refs: https://github.com/nodejs/node/issues/54534
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
107 lines
3.5 KiB
JavaScript
107 lines
3.5 KiB
JavaScript
// Flags: --no-warnings
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
const { completeSample } = require('../common/bench');
|
|
const assert = require('assert');
|
|
const { createRunner } = require('node:bench');
|
|
|
|
(async () => {
|
|
const runner = createRunner({ yieldBetweenSamples: false });
|
|
const invocations = [];
|
|
let closedContext;
|
|
|
|
const controlledCompletion = runner.bench('controlled', {
|
|
samples: 5,
|
|
warmup: 2,
|
|
}, common.mustCall((b) => {
|
|
invocations.push(`${b.phase}:${b.index}`);
|
|
const detail = { index: b.index, phase: b.phase };
|
|
const sample = completeSample(b, 2, { detail });
|
|
detail.index = -1;
|
|
|
|
assert.strictEqual(sample.operations, 2);
|
|
assert.strictEqual(typeof sample.duration_ns, 'bigint');
|
|
assert.strictEqual(sample.rate > 0, true);
|
|
assert.notStrictEqual(sample.detail, detail);
|
|
assert.notStrictEqual(sample.detail.index, -1);
|
|
sample.operations = 0;
|
|
sample.rate = NaN;
|
|
sample.detail.index = -2;
|
|
|
|
if (b.phase === 'measurement' && b.index === 1) {
|
|
b.done();
|
|
closedContext = b;
|
|
}
|
|
}, 4));
|
|
|
|
const recordedCompletion = runner.bench(
|
|
'recorded', { samples: 3 }, common.mustCall((b) => {
|
|
const detail = { source: 'worker', value: 1n };
|
|
const sample = b.record({
|
|
__proto__: null,
|
|
detail,
|
|
duration_ns: 20n,
|
|
operations: 5,
|
|
});
|
|
detail.source = 'changed';
|
|
assert.deepStrictEqual(sample, {
|
|
__proto__: null,
|
|
detail: { source: 'worker', value: 1n },
|
|
duration_ns: 20n,
|
|
operations: 5,
|
|
rate: 250_000_000,
|
|
});
|
|
sample.operations = 0;
|
|
sample.rate = NaN;
|
|
sample.detail.source = 'returned value changed';
|
|
b.done();
|
|
}));
|
|
|
|
const variableSamples = [
|
|
{ __proto__: null, duration_ns: 1_000_000_000n, operations: 1 },
|
|
{ __proto__: null, duration_ns: 100_000_000n, operations: 100 },
|
|
];
|
|
const variableCompletion = runner.bench('variable batch', {
|
|
samples: variableSamples.length,
|
|
}, common.mustCall((b) => {
|
|
b.record(variableSamples[b.index]);
|
|
}, variableSamples.length));
|
|
|
|
const records = await runner.run().toArray();
|
|
const [controlled, recorded, variable] = await Promise.all([
|
|
controlledCompletion,
|
|
recordedCompletion,
|
|
variableCompletion,
|
|
]);
|
|
|
|
assert.deepStrictEqual(invocations, [
|
|
'warmup:0',
|
|
'warmup:1',
|
|
'measurement:0',
|
|
'measurement:1',
|
|
]);
|
|
assert.strictEqual(controlled.samples.length, 2);
|
|
assert.deepStrictEqual(
|
|
controlled.samples.map(({ operations }) => operations), [2, 2]);
|
|
assert.deepStrictEqual(
|
|
controlled.samples.map(({ detail }) => detail.index), [0, 1]);
|
|
assert.strictEqual(controlled.samples.every(({ rate }) => rate > 0), true);
|
|
assert.strictEqual(recorded.samples.length, 1);
|
|
assert.deepStrictEqual(recorded.samples[0].detail,
|
|
{ source: 'worker', value: 1n });
|
|
assert.deepStrictEqual(variable.samples.map(({ rate }) => rate), [1, 1000]);
|
|
assert.strictEqual(variable.summary.mean, 500.5);
|
|
const pooledRate = 1_000_000_000 * 101 / 1_100_000_000;
|
|
assert.notStrictEqual(variable.summary.mean, pooledRate);
|
|
assert.strictEqual(
|
|
records.filter(({ type }) => type === 'bench:sample').length, 5);
|
|
assert.throws(() => closedContext.start(), { code: 'ERR_INVALID_STATE' });
|
|
assert.throws(() => closedContext.end(1), { code: 'ERR_INVALID_STATE' });
|
|
assert.throws(() => closedContext.record({
|
|
duration_ns: 1n,
|
|
operations: 1,
|
|
}), { code: 'ERR_INVALID_STATE' });
|
|
assert.throws(() => closedContext.done(), { code: 'ERR_INVALID_STATE' });
|
|
})().then(common.mustCall());
|