New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

bench-node

Package Overview
Dependencies
Maintainers
0
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bench-node - npm Package Compare versions

Comparing version 0.4.1 to 0.5.0

examples/html-report/node.js

3

examples/create-uint32array/node.js

@@ -9,2 +9,5 @@ const { Suite } = require('../../lib');

})
.add(`new Uint32Array(1024) with 10 repetitions`, {repeatSuite: 10}, function () {
return new Uint32Array(1024);
})
.add(`[Managed] new Uint32Array(1024)`, function (timer) {

@@ -11,0 +14,0 @@ const assert = require('node:assert');

21

lib/clock.js

@@ -197,19 +197,18 @@ const { debug, types } = require('node:util');

function createRunner(bench, recommendedCount) {
const isAsync = types.isAsyncFunction(bench.fn);
const hasArg = bench.fn.length >= 1;
function createFnString(bench) {
const { isAsync, hasArg } = bench;
if (bench.fn.length > 1) {
process.emitWarning(`The benchmark "${ bench.name }" function should not have more than 1 argument.`);
}
const compiledFnStringFactory = hasArg ? createRunManagedBenchmark : createRunUnmanagedBenchmark;
const compiledFnString = compiledFnStringFactory(bench, isAsync ? 'await ' : '');
return compiledFnString;
}
function createRunner(bench, recommendedCount) {
const { isAsync, hasArg } = bench;
const compiledFnString = bench.fnStr;
const createFnPrototype = isAsync ? AsyncFunction : SyncFunction;
const compiledFn = createFnPrototype('bench', 'timer', 'count', 'kUnmanagedTimerResult', compiledFnString);
const selectedTimer = hasArg ? new ManagedTimer(recommendedCount) : timer;
const runner = compiledFn.bind(globalThis, bench, selectedTimer, recommendedCount, kUnmanagedTimerResult);
debugBench(`Compiled Code: ${ compiledFnString }`);

@@ -228,2 +227,3 @@ debugBench(`Created compiled benchmark, hasArg=${ hasArg }, isAsync=${ isAsync }, recommendedCount=${ recommendedCount }`);

if (typeof p.onCompleteBenchmark === 'function') {
// TODO: this won't work when useWorkers=true
p.onCompleteBenchmark(result, bench);

@@ -239,2 +239,3 @@ }

clockBenchmark,
createFnString,
timer,

@@ -241,0 +242,0 @@ MIN_RESOLUTION,

@@ -1,4 +0,13 @@

const { textReport, chartReport, htmlReport } = require('./report');
const { Worker } = require('node:worker_threads');
const { types } = require('node:util');
const path = require('node:path');
const {
textReport,
chartReport,
htmlReport,
jsonReport,
} = require('./report');
const { getInitialIterations, runBenchmark, runWarmup } = require('./lifecycle');
const { debugBench, timer } = require('./clock');
const { debugBench, timer, createFnString } = require('./clock');
const {

@@ -18,2 +27,7 @@ validatePlugins,

const getFunctionBody = (string) => string.substring(
string.indexOf("{") + 1,
string.lastIndexOf("}")
);
class Benchmark {

@@ -25,4 +39,5 @@ name = 'Benchmark';

plugins;
repeatSuite;
constructor(name, fn, minTime, maxTime, plugins) {
constructor(name, fn, minTime, maxTime, plugins, repeatSuite) {
this.name = name;

@@ -33,3 +48,20 @@ this.fn = fn;

this.plugins = plugins;
this.repeatSuite = repeatSuite;
this.hasArg = this.fn.length >= 1;
if (this.fn.length > 1) {
process.emitWarning(`The benchmark "${ this.name }" function should not have more than 1 argument.`);
}
this.isAsync = types.isAsyncFunction(this.fn);
this.fnStr = createFnString(this);
}
serializeBenchmark() {
// Regular functions can't be passed to worker.postMessage
// So we pass the string and deserialize fnStr into a new Function
// on worker
this.fn = getFunctionBody(this.fn.toString());
}
}

@@ -42,2 +74,4 @@

maxTime: 0.5,
// Number of times the benchmark will be repeated
repeatSuite: 1,
};

@@ -55,2 +89,3 @@

#plugins;
#useWorkers;

@@ -69,2 +104,3 @@ constructor(options = {}) {

this.#useWorkers = options.useWorkers || false;
if (options?.plugins) {

@@ -90,2 +126,3 @@ validateArray(options.plugins, 'plugin');

validateNumber(options.maxTime, 'options.maxTime', options.minTime);
validateNumber(options.repeatSuite, 'options.repeatSuite', options.repeatSuite);
}

@@ -100,2 +137,3 @@ validateFunction(fn, 'fn');

this.#plugins,
options.repeatSuite,
);

@@ -110,8 +148,12 @@ this.#benchmarks.push(benchmark);

// This is required to avoid variance on first benchmark run
for (let i = 0; i < this.#benchmarks.length; ++i) {
const benchmark = this.#benchmarks[i];
debugBench(`Warmup ${ benchmark.name } with minTime=${ benchmark.minTime }, maxTime=${ benchmark.maxTime }`);
const initialIteration = await getInitialIterations(benchmark);
await runWarmup(benchmark, initialIteration, { minTime: 0.005, maxTime: 0.05 });
// It doesn't make sense to warmup a fresh new instance of Worker.
// TODO: support warmup directly in the Worker.
if (!this.#useWorkers) {
// This is required to avoid variance on first benchmark run
for (let i = 0; i < this.#benchmarks.length; ++i) {
const benchmark = this.#benchmarks[i];
debugBench(`Warmup ${ benchmark.name } with minTime=${ benchmark.minTime }, maxTime=${ benchmark.maxTime }`);
const initialIteration = await getInitialIterations(benchmark);
await runWarmup(benchmark, initialIteration, { minTime: 0.005, maxTime: 0.05 });
}
}

@@ -122,5 +164,11 @@

// Warmup is calculated to reduce noise/bias on the results
const initialIteration = await getInitialIterations(benchmark);
debugBench(`Starting ${ benchmark.name } with minTime=${ benchmark.minTime }, maxTime=${ benchmark.maxTime }`);
const result = await runBenchmark(benchmark, initialIteration);
const initialIterations = await getInitialIterations(benchmark);
debugBench(`Starting ${ benchmark.name } with minTime=${ benchmark.minTime }, maxTime=${ benchmark.maxTime }, repeatSuite=${ benchmark.repeatSuite }`);
let result;
if (this.#useWorkers) {
result = await this.runWorkerBenchmark(benchmark, initialIterations);
} else {
result = await runBenchmark(benchmark, initialIterations, benchmark.repeatSuite);
}
results[i] = result;

@@ -132,5 +180,33 @@ }

}
return results;
}
runWorkerBenchmark(benchmark, initialIterations) {
benchmark.serializeBenchmark();
const worker = new Worker(path.join(__dirname, './worker-runner.js'));
worker.postMessage({
benchmark,
initialIterations,
repeatSuite: benchmark.repeatSuite,
});
return new Promise((resolve, reject) => {
worker.on('message', (result) => {
resolve(result);
// TODO: await?
worker.terminate();
});
worker.on('error', (err) => {
reject(err);
worker.terminate();
});
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
}

@@ -146,2 +222,3 @@

htmlReport,
jsonReport,
};

@@ -67,11 +67,5 @@ const { clockBenchmark, debugBench, MIN_RESOLUTION, timer } = require('./clock');

async function runBenchmark(bench, initialIterations) {
const histogram = new StatisticalHistogram();
const maxDuration = bench.maxTime * timer.scale;
const minSamples = 10;
async function runBenchmarkOnce(bench, histogram, { initialIterations, maxDuration, minSamples }) {
let iterations = 0;
let timeSpent = 0;
while (timeSpent < maxDuration || histogram.samples.length <= minSamples) {

@@ -92,10 +86,37 @@ const { 0: duration, 1: realIterations } = await clockBenchmark(bench, initialIterations);

}
return { iterations, timeSpent };
}
async function runBenchmark(bench, initialIterations, repeatSuite) {
const histogram = new StatisticalHistogram();
const maxDuration = bench.maxTime * timer.scale;
const minSamples = 10;
let totalIterations = 0;
let totalTimeSpent = 0;
for (let i = 0; i < repeatSuite; ++i) {
const { iterations, timeSpent } = await runBenchmarkOnce(
bench,
histogram,
{ initialIterations, maxDuration, minSamples }
);
totalTimeSpent += timeSpent;
totalIterations += iterations;
}
histogram.finish()
const opsSec = iterations / (timeSpent / timer.scale);
const opsSec = totalIterations / (totalTimeSpent / timer.scale);
return {
opsSec,
iterations,
histogram,
iterations: totalIterations,
// StatisticalHistogram is not a serializable object
histogram: {
samples: histogram.samples.length,
min: histogram.min,
max: histogram.max,
},
name: bench.name,

@@ -102,0 +123,0 @@ plugins: parsePluginsResult(bench.plugins, bench.name),

const { textReport } = require('./reporter/text');
const { chartReport } = require('./reporter/chart');
const { htmlReport } = require('./reporter/html');
const { jsonReport } = require('./reporter/json');

@@ -9,2 +10,3 @@ module.exports = {

htmlReport,
jsonReport,
};

@@ -26,3 +26,3 @@ const util = require('node:util');

// process.stdout.write(result.histogram.stddev.toString());
process.stdout.write(` (${ result.histogram.samples.length } runs sampled) `);
process.stdout.write(` (${ result.histogram.samples } runs sampled) `);

@@ -29,0 +29,0 @@ for (const p of result.plugins) {

{
"name": "bench-node",
"version": "0.4.1",
"version": "0.5.0",
"description": "",

@@ -28,3 +28,6 @@ "main": "lib/index.js",

},
"homepage": "https://github.com/RafaelGSS/bench-node#readme"
"homepage": "https://github.com/RafaelGSS/bench-node#readme",
"dependencies": {
"piscina": "^4.8.0"
}
}

@@ -96,3 +96,4 @@ # `bench-node`

* `maxTime` {number} Maximum duration for the benchmark to run. **Default:** `0.5` seconds.
* `fn` {Function|AsyncFunction} The benchmark function. Can be synchronous or asynchronous.
* `repeatSuite` {number} Number of times to repeat benchmark to run. **Default:** `1` times.
* `fn` {Function|AsyncFunction} The benchmark function. Can be synchronous or asynchronous.
* Returns: {Suite}

@@ -240,2 +241,42 @@

### `jsonReport`
The `jsonReport` plugin provides benchmark results in **JSON format**.
It includes key performance metrics—such as `opsSec`, `runsSampled`, `min`
and `max` times, and any reporter data from your **plugins**—so you can easily
store, parse, or share the information.
Example output:
```json
[
{
"name": "single with matcher",
"opsSec": 180000,
"runsSampled": 50,
"min": "13.20μs",
"max": "82.57μs",
"plugins": []
},
{
"name": "Multiple replaces",
"opsSec": 170000,
"runsSampled": 50,
"min": "15.31μs",
"max": "77.49μs",
"plugins": []
}
]
```
**Usage:**
```cjs
const { Suite, jsonReport } = require('bench-node');
const suite = new Suite({
reporter: jsonReport,
});
```
### Custom Reporter

@@ -379,1 +420,15 @@

> results between versions of V8/Node.js.
### Worker Threads
> Stability: 1.0 (Experimental)
`bench-node` provides experimental support for **Worker Threads**. When you set `useWorkers: true`,
the library runs each benchmark in a separate worker thread, ensuring that one benchmark
does not affect another. Usage is straightforward:
```cjs
const suite = new Suite({
useWorkers: true,
});
```

@@ -120,2 +120,12 @@ const { Suite } = require('../lib/index');

});
it('repeatSuite should be a valid number', () => {
['ds', {}, () => {}].forEach((r) => {
assert.throws(() => {
bench.add('name', { repeatSuite: r }, noop);
}, {
code: 'ERR_INVALID_ARG_TYPE',
});
});
});
});

@@ -122,0 +132,0 @@ });

const { describe, it, before } = require('node:test');
const assert = require('node:assert');
const { Suite } = require('../lib');
const copyBench = require('./fixtures/copy');

@@ -72,1 +73,24 @@ const { managedBench, managedOptBench } = require('./fixtures/opt-managed');

});
describe('Workers should have parallel context', () => {
let results;
before(async () => {
const bench = new Suite({
reporter: () => {},
useWorkers: true,
});
bench
.add('Import with node: prefix', () => {
return import('node:fs');
})
.add('Import without node: prefix', () => {
return import('fs');
});
results = await bench.run();
});
it('should have a similar result as they will not share import.meta.cache', () => {
assertMaxBenchmarkDifference(results, { percentageLimit: 10, ciPercentageLimit: 30 });
});
});

@@ -70,3 +70,3 @@ // @ts-check

"isSupported()",
"onCompleteBenchmark([number, number, object], {fn, maxTime, minTime, name, plugins})",
"onCompleteBenchmark([number, number, object], {fn, fnStr, hasArg, isAsync, maxTime, minTime, name, plugins, repeatSuite})",
"toJSON(string)",

@@ -73,0 +73,0 @@ "toString()",

@@ -5,3 +5,8 @@ const { describe, it, before } = require('node:test');

const { Suite, chartReport, htmlReport } = require('../lib');
const {
Suite,
chartReport,
htmlReport,
jsonReport,
} = require('../lib');

@@ -111,1 +116,66 @@ describe('chartReport outputs benchmark results as a bar chart', async (t) => {

});
describe('jsonReport should produce valid JSON output', async () => {
let output = ''
before(async () => {
const originalStdoutWrite = process.stdout.write
process.stdout.write = function (data) {
output += data
}
// Create a new Suite with the JSON reporter
const suite = new Suite({
reporter: jsonReport
})
suite
.add('single with matcher', function () {
const pattern = /[123]/g
const replacements = { 1: 'a', 2: 'b', 3: 'c' }
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(pattern, (m) => replacements[m])
assert.ok(r)
})
.add('Multiple replaces', function () {
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c')
assert.ok(r)
})
// Run the suite
await suite.run()
// Restore stdout
process.stdout.write = originalStdoutWrite
});
it('should print valid JSON', () => {
// Verify if the output can be parsed as JSON
let data
try {
data = JSON.parse(output)
} catch (err) {
assert.fail(`Output is not valid JSON: ${err.message}`)
}
assert.ok(Array.isArray(data), 'Output should be an array of results')
});
it('should contain the required benchmark fields', () => {
const data = JSON.parse(output)
// We expect the two benchmarks we added: 'single with matcher' and 'Multiple replaces'
assert.strictEqual(data.length, 2, 'Should have results for 2 benchmarks')
for (const entry of data) {
// Ensure each entry has expected keys
assert.ok(typeof entry.name === 'string', 'name should be a string')
assert.ok(typeof entry.opsSec === 'number', 'opsSec should be a number')
assert.ok(typeof entry.runsSampled === 'number', 'runsSampled should be a number')
assert.ok(typeof entry.min === 'string', 'min should be a string (formatted time)')
assert.ok(typeof entry.max === 'string', 'max should be a string (formatted time)')
assert.ok(Array.isArray(entry.plugins), 'plugins should be an array')
}
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc