From df810d69ff816ce4767c14ffa43681932d17ffc7 Mon Sep 17 00:00:00 2001 From: zombieleet Date: Wed, 12 Oct 2022 01:38:39 +0100 Subject: [PATCH 1/7] test: move benchmark.js from common/ folder remove benchmark.js from test/common to test/benchmark change import part in all test-benchmark-* file --- test/{common => benchmark}/benchmark.js | 2 ++ test/benchmark/test-benchmark-assert.js | 2 +- test/benchmark/test-benchmark-async-hooks.js | 2 +- test/benchmark/test-benchmark-buffer.js | 2 +- test/benchmark/test-benchmark-child-process.js | 2 +- test/benchmark/test-benchmark-cluster.js | 2 +- test/benchmark/test-benchmark-crypto.js | 2 +- test/benchmark/test-benchmark-dgram.js | 2 +- test/benchmark/test-benchmark-dns.js | 2 +- test/benchmark/test-benchmark-domain.js | 2 +- test/benchmark/test-benchmark-es.js | 2 +- test/benchmark/test-benchmark-esm.js | 2 +- test/benchmark/test-benchmark-events.js | 2 +- test/benchmark/test-benchmark-fs.js | 2 +- test/benchmark/test-benchmark-http.js | 2 +- test/benchmark/test-benchmark-http2.js | 2 +- test/benchmark/test-benchmark-misc.js | 2 +- test/benchmark/test-benchmark-module.js | 2 +- test/benchmark/test-benchmark-napi.js | 2 +- test/benchmark/test-benchmark-net.js | 2 +- test/benchmark/test-benchmark-os.js | 2 +- test/benchmark/test-benchmark-path.js | 2 +- test/benchmark/test-benchmark-policy.js | 2 +- test/benchmark/test-benchmark-process.js | 2 +- test/benchmark/test-benchmark-querystring.js | 2 +- test/benchmark/test-benchmark-streams.js | 2 +- test/benchmark/test-benchmark-string_decoder.js | 2 +- test/benchmark/test-benchmark-timers.js | 2 +- test/benchmark/test-benchmark-tls.js | 2 +- test/benchmark/test-benchmark-url.js | 2 +- test/benchmark/test-benchmark-util.js | 2 +- test/benchmark/test-benchmark-v8.js | 2 +- test/benchmark/test-benchmark-vm.js | 2 +- test/benchmark/test-benchmark-worker.js | 2 +- test/benchmark/test-benchmark-zlib.js | 2 +- 35 files changed, 36 insertions(+), 34 deletions(-) rename test/{common => benchmark}/benchmark.js (98%) diff --git a/test/common/benchmark.js b/test/benchmark/benchmark.js similarity index 98% rename from test/common/benchmark.js rename to test/benchmark/benchmark.js index 88c918766fd7..995919a47d36 100644 --- a/test/common/benchmark.js +++ b/test/benchmark/benchmark.js @@ -1,5 +1,7 @@ 'use strict'; +require('../common'); + const assert = require('assert'); const fork = require('child_process').fork; const path = require('path'); diff --git a/test/benchmark/test-benchmark-assert.js b/test/benchmark/test-benchmark-assert.js index 5ec2319c28a1..10a8292c5b1a 100644 --- a/test/benchmark/test-benchmark-assert.js +++ b/test/benchmark/test-benchmark-assert.js @@ -5,6 +5,6 @@ require('../common'); // Minimal test for assert benchmarks. This makes sure the benchmarks aren't // completely broken but nothing more than that. -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('assert'); diff --git a/test/benchmark/test-benchmark-async-hooks.js b/test/benchmark/test-benchmark-async-hooks.js index c9ea2c1e86db..72e328408db4 100644 --- a/test/benchmark/test-benchmark-async-hooks.js +++ b/test/benchmark/test-benchmark-async-hooks.js @@ -8,6 +8,6 @@ if (!common.hasCrypto) if (!common.enoughTestMem) common.skip('Insufficient memory for async_hooks benchmark test'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('async_hooks'); diff --git a/test/benchmark/test-benchmark-buffer.js b/test/benchmark/test-benchmark-buffer.js index af93842b0b92..1c486cd492f8 100644 --- a/test/benchmark/test-benchmark-buffer.js +++ b/test/benchmark/test-benchmark-buffer.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('buffers', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-child-process.js b/test/benchmark/test-benchmark-child-process.js index 043620de12f1..0d8d87b6dcc9 100644 --- a/test/benchmark/test-benchmark-child-process.js +++ b/test/benchmark/test-benchmark-child-process.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('child_process', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-cluster.js b/test/benchmark/test-benchmark-cluster.js index b24aced5d58d..7463a0a3ef12 100644 --- a/test/benchmark/test-benchmark-cluster.js +++ b/test/benchmark/test-benchmark-cluster.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('cluster'); diff --git a/test/benchmark/test-benchmark-crypto.js b/test/benchmark/test-benchmark-crypto.js index 7f6988acf234..bd87cc060113 100644 --- a/test/benchmark/test-benchmark-crypto.js +++ b/test/benchmark/test-benchmark-crypto.js @@ -8,6 +8,6 @@ if (!common.hasCrypto) if (common.hasFipsCrypto) common.skip('some benchmarks are FIPS-incompatible'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('crypto', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-dgram.js b/test/benchmark/test-benchmark-dgram.js index ceafdd77a2aa..de76dde5ae62 100644 --- a/test/benchmark/test-benchmark-dgram.js +++ b/test/benchmark/test-benchmark-dgram.js @@ -2,7 +2,7 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); // Dgram benchmarks use hardcoded ports. Thus, this test can not be run in // parallel with tests that choose random ports. diff --git a/test/benchmark/test-benchmark-dns.js b/test/benchmark/test-benchmark-dns.js index 331a4c8ff0d8..ac56719be6b4 100644 --- a/test/benchmark/test-benchmark-dns.js +++ b/test/benchmark/test-benchmark-dns.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('dns', { ...process.env, NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-domain.js b/test/benchmark/test-benchmark-domain.js index 5ebbfc5ea3b4..d423e192622c 100644 --- a/test/benchmark/test-benchmark-domain.js +++ b/test/benchmark/test-benchmark-domain.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('domain'); diff --git a/test/benchmark/test-benchmark-es.js b/test/benchmark/test-benchmark-es.js index 6886b3ce9257..faf62eaacc02 100644 --- a/test/benchmark/test-benchmark-es.js +++ b/test/benchmark/test-benchmark-es.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('es', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-esm.js b/test/benchmark/test-benchmark-esm.js index cd10ff9cb160..c45f0e93882d 100644 --- a/test/benchmark/test-benchmark-esm.js +++ b/test/benchmark/test-benchmark-esm.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('esm', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-events.js b/test/benchmark/test-benchmark-events.js index 53de4897dd75..27ca9bbe8822 100644 --- a/test/benchmark/test-benchmark-events.js +++ b/test/benchmark/test-benchmark-events.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('events', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-fs.js b/test/benchmark/test-benchmark-fs.js index 3ef6be2b7eba..14a3e64079be 100644 --- a/test/benchmark/test-benchmark-fs.js +++ b/test/benchmark/test-benchmark-fs.js @@ -1,7 +1,7 @@ 'use strict'; require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); diff --git a/test/benchmark/test-benchmark-http.js b/test/benchmark/test-benchmark-http.js index a3d92c7e987f..e13254759c12 100644 --- a/test/benchmark/test-benchmark-http.js +++ b/test/benchmark/test-benchmark-http.js @@ -9,6 +9,6 @@ if (!common.enoughTestMem) // rather than parallel to make sure it does not conflict with tests that choose // random available ports. -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('http', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-http2.js b/test/benchmark/test-benchmark-http2.js index 25dd771076e7..fc271a9129b9 100644 --- a/test/benchmark/test-benchmark-http2.js +++ b/test/benchmark/test-benchmark-http2.js @@ -11,6 +11,6 @@ if (!common.enoughTestMem) // rather than parallel to make sure it does not conflict with tests that choose // random available ports. -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('http2', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-misc.js b/test/benchmark/test-benchmark-misc.js index 30707bfaf736..ad221c5bda0d 100644 --- a/test/benchmark/test-benchmark-misc.js +++ b/test/benchmark/test-benchmark-misc.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('misc', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-module.js b/test/benchmark/test-benchmark-module.js index da8e108d53e1..b5a1458e8e47 100644 --- a/test/benchmark/test-benchmark-module.js +++ b/test/benchmark/test-benchmark-module.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('module'); diff --git a/test/benchmark/test-benchmark-napi.js b/test/benchmark/test-benchmark-napi.js index 5c6a8aa01187..6d6a924abaa3 100644 --- a/test/benchmark/test-benchmark-napi.js +++ b/test/benchmark/test-benchmark-napi.js @@ -13,6 +13,6 @@ if (!common.isMainThread) { if (process.features.debug) { common.skip('benchmark does not work with debug build yet'); } -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('napi', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-net.js b/test/benchmark/test-benchmark-net.js index df8ea8011693..a03ff65aa6e0 100644 --- a/test/benchmark/test-benchmark-net.js +++ b/test/benchmark/test-benchmark-net.js @@ -6,6 +6,6 @@ require('../common'); // rather than parallel to make sure it does not conflict with tests that choose // random available ports. -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('net', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-os.js b/test/benchmark/test-benchmark-os.js index dbedd7f582d9..c1cf4f60281a 100644 --- a/test/benchmark/test-benchmark-os.js +++ b/test/benchmark/test-benchmark-os.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('os'); diff --git a/test/benchmark/test-benchmark-path.js b/test/benchmark/test-benchmark-path.js index 3bca4f2a11bd..accea13d823f 100644 --- a/test/benchmark/test-benchmark-path.js +++ b/test/benchmark/test-benchmark-path.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('path', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-policy.js b/test/benchmark/test-benchmark-policy.js index 7eb0992b1f1e..af34d019230a 100644 --- a/test/benchmark/test-benchmark-policy.js +++ b/test/benchmark/test-benchmark-policy.js @@ -2,7 +2,7 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('policy', [ 'n=1', diff --git a/test/benchmark/test-benchmark-process.js b/test/benchmark/test-benchmark-process.js index c6687f302341..2083c3705f3e 100644 --- a/test/benchmark/test-benchmark-process.js +++ b/test/benchmark/test-benchmark-process.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('process', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-querystring.js b/test/benchmark/test-benchmark-querystring.js index 6fee9bb39143..c7c5bcc1485a 100644 --- a/test/benchmark/test-benchmark-querystring.js +++ b/test/benchmark/test-benchmark-querystring.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('querystring', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-streams.js b/test/benchmark/test-benchmark-streams.js index 68c8478a7398..41497cc8450d 100644 --- a/test/benchmark/test-benchmark-streams.js +++ b/test/benchmark/test-benchmark-streams.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('streams', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-string_decoder.js b/test/benchmark/test-benchmark-string_decoder.js index 721529e5ae64..537c97c23d9a 100644 --- a/test/benchmark/test-benchmark-string_decoder.js +++ b/test/benchmark/test-benchmark-string_decoder.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('string_decoder'); diff --git a/test/benchmark/test-benchmark-timers.js b/test/benchmark/test-benchmark-timers.js index db4927ab32ea..0ab0319b4666 100644 --- a/test/benchmark/test-benchmark-timers.js +++ b/test/benchmark/test-benchmark-timers.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('timers', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-tls.js b/test/benchmark/test-benchmark-tls.js index c9a87c15770d..22269caecf46 100644 --- a/test/benchmark/test-benchmark-tls.js +++ b/test/benchmark/test-benchmark-tls.js @@ -12,6 +12,6 @@ if (!common.enoughTestMem) // rather than parallel to make sure it does not conflict with tests that choose // random available ports. -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('tls', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-url.js b/test/benchmark/test-benchmark-url.js index 664e7c4d8dc8..a767733b92f9 100644 --- a/test/benchmark/test-benchmark-url.js +++ b/test/benchmark/test-benchmark-url.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('url', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-util.js b/test/benchmark/test-benchmark-util.js index d0c16c623268..488ff795ed4c 100644 --- a/test/benchmark/test-benchmark-util.js +++ b/test/benchmark/test-benchmark-util.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('util', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-v8.js b/test/benchmark/test-benchmark-v8.js index efeaac8328c7..2193e9e51e3f 100644 --- a/test/benchmark/test-benchmark-v8.js +++ b/test/benchmark/test-benchmark-v8.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('v8', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-vm.js b/test/benchmark/test-benchmark-vm.js index e9c4e3f1389f..853ebcaa1ef2 100644 --- a/test/benchmark/test-benchmark-vm.js +++ b/test/benchmark/test-benchmark-vm.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('vm', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-worker.js b/test/benchmark/test-benchmark-worker.js index a4319d4face6..1804a8248fc0 100644 --- a/test/benchmark/test-benchmark-worker.js +++ b/test/benchmark/test-benchmark-worker.js @@ -9,6 +9,6 @@ if (!common.enoughTestMem) // this should be in sequential rather than parallel to make sure // it does not conflict with tests that choose random available ports. -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('worker', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/benchmark/test-benchmark-zlib.js b/test/benchmark/test-benchmark-zlib.js index e3c4723aa972..87e7b5535503 100644 --- a/test/benchmark/test-benchmark-zlib.js +++ b/test/benchmark/test-benchmark-zlib.js @@ -2,6 +2,6 @@ require('../common'); -const runBenchmark = require('../common/benchmark'); +const runBenchmark = require('./benchmark'); runBenchmark('zlib', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); From 9ae8b1f42544ad0290d25be3b4f4176597093953 Mon Sep 17 00:00:00 2001 From: zombieleet Date: Wed, 12 Oct 2022 01:56:11 +0100 Subject: [PATCH 2/7] lib/*.js (benchmark): expose internal benchmark module as a core module Exposes the internal benchmark utility module as a core module --- benchmark/common.js | 313 +---------------- {benchmark => lib}/_benchmark_progress.js | 46 ++- {benchmark => lib}/_http-benchmarkers.js | 186 +++++----- lib/benchmark.js | 398 ++++++++++++++++++++++ tsconfig.json | 1 + 5 files changed, 539 insertions(+), 405 deletions(-) rename {benchmark => lib}/_benchmark_progress.js (68%) rename {benchmark => lib}/_http-benchmarkers.js (51%) create mode 100644 lib/benchmark.js diff --git a/benchmark/common.js b/benchmark/common.js index 6ed230ffde42..34c4c0f951b7 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -1,308 +1,5 @@ 'use strict'; - -const child_process = require('child_process'); -const http_benchmarkers = require('./_http-benchmarkers.js'); - -class Benchmark { - constructor(fn, configs, options = {}) { - // Used to make sure a benchmark only start a timer once - this._started = false; - - // Indicate that the benchmark ended - this._ended = false; - - // Holds process.hrtime value - this._time = 0n; - - // Use the file name as the name of the benchmark - this.name = require.main.filename.slice(__dirname.length + 1); - - // Execution arguments i.e. flags used to run the jobs - this.flags = process.env.NODE_BENCHMARK_FLAGS ? - process.env.NODE_BENCHMARK_FLAGS.split(/\s+/) : - []; - - // Parse job-specific configuration from the command line arguments - const argv = process.argv.slice(2); - const parsed_args = this._parseArgs(argv, configs, options); - this.options = parsed_args.cli; - this.extra_options = parsed_args.extra; - if (options.flags) { - this.flags = this.flags.concat(options.flags); - } - - // The configuration list as a queue of jobs - this.queue = this._queue(this.options); - - // The configuration of the current job, head of the queue - this.config = this.queue[0]; - - process.nextTick(() => { - if (Object.hasOwn(process.env, 'NODE_RUN_BENCHMARK_FN')) { - fn(this.config); - } else { - // _run will use fork() to create a new process for each configuration - // combination. - this._run(); - } - }); - } - - _parseArgs(argv, configs, options) { - const cliOptions = {}; - - // Check for the test mode first. - const testIndex = argv.indexOf('--test'); - if (testIndex !== -1) { - for (const [key, rawValue] of Object.entries(configs)) { - let value = Array.isArray(rawValue) ? rawValue[0] : rawValue; - // Set numbers to one by default to reduce the runtime. - if (typeof value === 'number') { - if (key === 'dur' || key === 'duration') { - value = 0.05; - } else if (value > 1) { - value = 1; - } - } - cliOptions[key] = [value]; - } - // Override specific test options. - if (options.test) { - for (const [key, value] of Object.entries(options.test)) { - cliOptions[key] = Array.isArray(value) ? value : [value]; - } - } - argv.splice(testIndex, 1); - } else { - // Accept single values instead of arrays. - for (const [key, value] of Object.entries(configs)) { - if (!Array.isArray(value)) - configs[key] = [value]; - } - } - - const extraOptions = {}; - const validArgRE = /^(.+?)=([\s\S]*)$/; - // Parse configuration arguments - for (const arg of argv) { - const match = arg.match(validArgRE); - if (!match) { - console.error(`bad argument: ${arg}`); - process.exit(1); - } - const [, key, value] = match; - if (Object.hasOwn(configs, key)) { - if (!cliOptions[key]) - cliOptions[key] = []; - cliOptions[key].push( - // Infer the type from the config object and parse accordingly - typeof configs[key][0] === 'number' ? +value : value - ); - } else { - extraOptions[key] = value; - } - } - return { cli: { ...configs, ...cliOptions }, extra: extraOptions }; - } - - _queue(options) { - const queue = []; - const keys = Object.keys(options); - - // Perform a depth-first walk through all options to generate a - // configuration list that contains all combinations. - function recursive(keyIndex, prevConfig) { - const key = keys[keyIndex]; - const values = options[key]; - - for (const value of values) { - if (typeof value !== 'number' && typeof value !== 'string') { - throw new TypeError( - `configuration "${key}" had type ${typeof value}`); - } - if (typeof value !== typeof values[0]) { - // This is a requirement for being able to consistently and - // predictably parse CLI provided configuration values. - throw new TypeError(`configuration "${key}" has mixed types`); - } - - const currConfig = { [key]: value, ...prevConfig }; - - if (keyIndex + 1 < keys.length) { - recursive(keyIndex + 1, currConfig); - } else { - queue.push(currConfig); - } - } - } - - if (keys.length > 0) { - recursive(0, {}); - } else { - queue.push({}); - } - - return queue; - } - - http(options, cb) { - const http_options = { ...options }; - http_options.benchmarker = http_options.benchmarker || - this.config.benchmarker || - this.extra_options.benchmarker || - http_benchmarkers.default_http_benchmarker; - http_benchmarkers.run( - http_options, (error, code, used_benchmarker, result, elapsed) => { - if (cb) { - cb(code); - } - if (error) { - console.error(error); - process.exit(code || 1); - } - this.config.benchmarker = used_benchmarker; - this.report(result, elapsed); - } - ); - } - - _run() { - // If forked, report to the parent. - if (process.send) { - process.send({ - type: 'config', - name: this.name, - queueLength: this.queue.length, - }); - } - - const recursive = (queueIndex) => { - const config = this.queue[queueIndex]; - - // Set NODE_RUN_BENCHMARK_FN to indicate that the child shouldn't - // construct a configuration queue, but just execute the benchmark - // function. - const childEnv = { ...process.env }; - childEnv.NODE_RUN_BENCHMARK_FN = ''; - - // Create configuration arguments - const childArgs = []; - for (const [key, value] of Object.entries(config)) { - childArgs.push(`${key}=${value}`); - } - for (const [key, value] of Object.entries(this.extra_options)) { - childArgs.push(`${key}=${value}`); - } - - const child = child_process.fork(require.main.filename, childArgs, { - env: childEnv, - execArgv: this.flags.concat(process.execArgv), - }); - child.on('message', sendResult); - child.on('close', (code) => { - if (code) { - process.exit(code); - } - - if (queueIndex + 1 < this.queue.length) { - recursive(queueIndex + 1); - } - }); - }; - - recursive(0); - } - - start() { - if (this._started) { - throw new Error('Called start more than once in a single benchmark'); - } - this._started = true; - this._time = process.hrtime.bigint(); - } - - end(operations) { - // Get elapsed time now and do error checking later for accuracy. - const time = process.hrtime.bigint(); - - if (!this._started) { - throw new Error('called end without start'); - } - if (this._ended) { - throw new Error('called end multiple times'); - } - if (typeof operations !== 'number') { - throw new Error('called end() without specifying operation count'); - } - if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED && operations <= 0) { - throw new Error('called end() with operation count <= 0'); - } - - this._ended = true; - - if (time === this._time) { - if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED) - throw new Error('insufficient clock precision for short benchmark'); - // Avoid dividing by zero - this.report(operations && Number.MAX_VALUE, 0n); - return; - } - - const elapsed = time - this._time; - const rate = operations / (Number(elapsed) / 1e9); - this.report(rate, elapsed); - } - - report(rate, elapsed) { - sendResult({ - name: this.name, - conf: this.config, - rate, - time: nanoSecondsToString(elapsed), - type: 'report', - }); - } -} - -function nanoSecondsToString(bigint) { - const str = bigint.toString(); - const decimalPointIndex = str.length - 9; - if (decimalPointIndex <= 0) { - return `0.${'0'.repeat(-decimalPointIndex)}${str}`; - } - return `${str.slice(0, decimalPointIndex)}.${str.slice(decimalPointIndex)}`; -} - -function formatResult(data) { - // Construct configuration string, " A=a, B=b, ..." - let conf = ''; - for (const key of Object.keys(data.conf)) { - conf += ` ${key}=${JSON.stringify(data.conf[key])}`; - } - - let rate = data.rate.toString().split('.'); - rate[0] = rate[0].replace(/(\d)(?=(?:\d\d\d)+(?!\d))/g, '$1,'); - rate = (rate[1] ? rate.join('.') : rate[0]); - return `${data.name}${conf}: ${rate}\n`; -} - -function sendResult(data) { - if (process.send) { - // If forked, report by process send - process.send(data, () => { - if (Object.hasOwn(process.env, 'NODE_RUN_BENCHMARK_FN')) { - // If, for any reason, the process is unable to self close within - // a second after completing, forcefully close it. - setTimeout(() => { - process.exit(0); - }, 5000).unref(); - } - }); - } else { - // Otherwise report by stdout - process.stdout.write(formatResult(data)); - } -} +const { Benchmark, createBenchmark } = require('benchmark'); const urls = { long: 'http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' + @@ -393,7 +90,7 @@ function bakeUrlData(type, e = 0, withBase = false, asUrl = false) { module.exports = { Benchmark, - PORT: http_benchmarkers.PORT, + PORT: Benchmark.HTTP_BENCHMARK_PORT, bakeUrlData, binding(bindingName) { try { @@ -405,10 +102,8 @@ module.exports = { } }, buildType: process.features.debug ? 'Debug' : 'Release', - createBenchmark(fn, configs, options) { - return new Benchmark(fn, configs, options); - }, - sendResult, + createBenchmark, + sendResult: Benchmark.SendResult, searchParams, urlDataTypes: Object.keys(urls).concat(['wpt']), urls, diff --git a/benchmark/_benchmark_progress.js b/lib/_benchmark_progress.js similarity index 68% rename from benchmark/_benchmark_progress.js rename to lib/_benchmark_progress.js index 6c925f34e682..ca177ba76042 100644 --- a/benchmark/_benchmark_progress.js +++ b/lib/_benchmark_progress.js @@ -1,10 +1,44 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS + 'use strict'; +const { + String, + StringPrototypeRepeat, + MathMax, + MathCeil, + MathFloor +} = primordials; + +const { + setInterval, + clearInterval +} = require('timers'); + const readline = require('readline'); function pad(input, minLength, fill) { const result = String(input); - const padding = fill.repeat(Math.max(0, minLength - result.length)); + const padding = StringPrototypeRepeat(fill, MathMax(0, minLength - result.length)); return `${padding}${result}`; } @@ -15,9 +49,9 @@ function fraction(numerator, denominator) { } function getTime(diff) { - const time = Math.ceil(diff[0] + diff[1] / 1e9); - const hours = pad(Math.floor(time / 3600), 2, '0'); - const minutes = pad(Math.floor((time % 3600) / 60), 2, '0'); + const time = MathCeil(diff[0] + diff[1] / 1e9); + const hours = pad(MathFloor(time / 3600), 2, '0'); + const minutes = pad(MathFloor((time % 3600) / 60), 2, '0'); const seconds = pad((time % 3600) % 60, 2, '0'); return `${hours}:${minutes}:${seconds}`; } @@ -83,7 +117,7 @@ class BenchmarkProgress { // Calculate numbers for fractions. const runsPerFile = this.runsPerFile; - const completedFiles = Math.floor(completedRuns / runsPerFile); + const completedFiles = MathFloor(completedRuns / runsPerFile); const scheduledFiles = this.benchmarks.length; const completedRunsForFile = finished ? runsPerFile : completedRuns % runsPerFile; @@ -96,7 +130,7 @@ class BenchmarkProgress { runRate = completedConfig / scheduledConfig; } const completedRate = ((completedRuns + runRate) / scheduledRuns); - const percent = pad(Math.floor(completedRate * 100), 3, ' '); + const percent = pad(MathFloor(completedRate * 100), 3, ' '); const caption = finished ? 'Done\n' : this.currentFile; return `[${getTime(diff)}|% ${percent}| ` + diff --git a/benchmark/_http-benchmarkers.js b/lib/_http-benchmarkers.js similarity index 51% rename from benchmark/_http-benchmarkers.js rename to lib/_http-benchmarkers.js index ec0e80a9e7ed..05725e0c2f94 100644 --- a/benchmark/_http-benchmarkers.js +++ b/lib/_http-benchmarkers.js @@ -1,8 +1,47 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS + 'use strict'; const child_process = require('child_process'); -const path = require('path'); -const fs = require('fs'); + +const { + codes: { + ERR_BENCHMARK_HTTP_COULD_NOT_LOCATE_BENCHMARKER, + ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_INSTALLED, + ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_SUPPROTED, + ERR_BENCHMARK_HTTP_STRANGE_OUTPUT, + ERR_BENCHMARK_HTTP_UNKNOWN_OUTPUT, + } +} = require('internal/errors'); + +const { + Number, + JSONParse, + MathMax, + MathMin, + ArrayPrototypePush, + NumberIsFinite, + RegExpPrototypeExec +} = primordials; const requirementsURL = 'https://github.com/nodejs/node/blob/HEAD/benchmark/writing-and-running-benchmarks.md#http-benchmark-requirements'; @@ -10,14 +49,20 @@ const requirementsURL = // The port used by servers and wrk exports.PORT = Number(process.env.PORT) || 12346; -class AutocannonBenchmarker { - constructor() { - this.name = 'autocannon'; - this.executable = - process.platform === 'win32' ? 'autocannon.cmd' : 'autocannon'; +class LoadBalancerBenchmarker { + constructor(name, executable, type) { + this.name = name; + this.executable = executable; + this.type = type; const result = child_process.spawnSync(this.executable, ['-h']); this.present = !(result.error && result.error.code === 'ENOENT'); } +} + +class AutocannonBenchmarker extends LoadBalancerBenchmarker { + constructor() { + super('autocannon', process.platform === 'win32' ? 'autocannon.cmd' : 'autocannon'); + } create(options) { const args = [ @@ -27,10 +72,10 @@ class AutocannonBenchmarker { '-n', ]; for (const field in options.headers) { - args.push('-H', `${field}=${options.headers[field]}`); + ArrayPrototypePush(args, '-H', `${field}=${options.headers[field]}`); } const scheme = options.scheme || 'http'; - args.push(`${scheme}://127.0.0.1:${options.port}${options.path}`); + ArrayPrototypePush(args, `${scheme}://127.0.0.1:${options.port}${options.path}`); const child = child_process.spawn(this.executable, args); return child; } @@ -38,7 +83,7 @@ class AutocannonBenchmarker { processResults(output) { let result; try { - result = JSON.parse(output); + result = JSONParse(output); } catch { return undefined; } @@ -49,27 +94,24 @@ class AutocannonBenchmarker { } } -class WrkBenchmarker { +class WrkBenchmarker extends LoadBalancerBenchmarker { constructor() { - this.name = 'wrk'; - this.executable = 'wrk'; - const result = child_process.spawnSync(this.executable, ['-h']); - this.present = !(result.error && result.error.code === 'ENOENT'); + super('wrk', 'wrk'); } create(options) { const duration = typeof options.duration === 'number' ? - Math.max(options.duration, 1) : + MathMax(options.duration, 1) : options.duration; const scheme = options.scheme || 'http'; const args = [ '-d', duration, '-c', options.connections, - '-t', Math.min(options.connections, require('os').cpus().length || 8), + '-t', MathMin(options.connections, require('os').cpus().length || 8), `${scheme}://127.0.0.1:${options.port}${options.path}`, ]; for (const field in options.headers) { - args.push('-H', `${field}: ${options.headers[field]}`); + ArrayPrototypePush(args, '-H', `${field}: ${options.headers[field]}`); } const child = child_process.spawn(this.executable, args); return child; @@ -79,111 +121,65 @@ class WrkBenchmarker { const throughputRe = /Requests\/sec:[ \t]+([0-9.]+)/; const match = output.match(throughputRe); const throughput = match && +match[1]; - if (!isFinite(throughput)) { + if (!NumberIsFinite(throughput)) { return undefined; } return throughput; } } -/** - * Simple, single-threaded benchmarker for testing if the benchmark - * works - */ -class TestDoubleBenchmarker { - constructor(type) { - // `type` is the type of benchmarker. Possible values are 'http', 'https', - // and 'http2'. - this.name = `test-double-${type}`; - this.executable = path.resolve(__dirname, '_test-double-benchmarker.js'); - this.present = fs.existsSync(this.executable); - this.type = type; - } - - create(options) { - process.env.duration = process.env.duration || options.duration || 5; - - const scheme = options.scheme || 'http'; - const env = { - test_url: `${scheme}://127.0.0.1:${options.port}${options.path}`, - ...process.env - }; - - const child = child_process.fork(this.executable, - [this.type], - { silent: true, env }); - return child; - } - - processResults(output) { - let result; - try { - result = JSON.parse(output); - } catch { - return undefined; - } - return result.throughput; - } -} - /** * HTTP/2 Benchmarker */ -class H2LoadBenchmarker { +class H2LoadBenchmarker extends LoadBalancerBenchmarker { constructor() { - this.name = 'h2load'; - this.executable = 'h2load'; - const result = child_process.spawnSync(this.executable, ['-h']); - this.present = !(result.error && result.error.code === 'ENOENT'); + super('h2load', 'h2load'); } create(options) { const args = []; + if (typeof options.requests === 'number') - args.push('-n', options.requests); + ArrayPrototypePush(args, '-n', options.requests); if (typeof options.clients === 'number') - args.push('-c', options.clients); + ArrayPrototypePush(args, '-c', options.clients); if (typeof options.threads === 'number') - args.push('-t', options.threads); + ArrayPrototypePush(args, '-t', options.threads); if (typeof options.maxConcurrentStreams === 'number') - args.push('-m', options.maxConcurrentStreams); + ArrayPrototypePush(args, '-m', options.maxConcurrentStreams); if (typeof options.initialWindowSize === 'number') - args.push('-w', options.initialWindowSize); + ArrayPrototypePush(args, '-w', options.initialWindowSize); if (typeof options.sessionInitialWindowSize === 'number') - args.push('-W', options.sessionInitialWindowSize); + ArrayPrototypePush(args, '-W', options.sessionInitialWindowSize); if (typeof options.rate === 'number') - args.push('-r', options.rate); + ArrayPrototypePush(args, '-r', options.rate); if (typeof options.ratePeriod === 'number') - args.push(`--rate-period=${options.ratePeriod}`); + ArrayPrototypePush(args, `--rate-period=${options.ratePeriod}`); if (typeof options.duration === 'number') - args.push('-T', options.duration); + ArrayPrototypePush(args, '-T', options.duration); if (typeof options.timeout === 'number') - args.push('-N', options.timeout); + ArrayPrototypePush(args, '-N', options.timeout); if (typeof options.headerTableSize === 'number') - args.push(`--header-table-size=${options.headerTableSize}`); + ArrayPrototypePush(args, `--header-table-size=${options.headerTableSize}`); if (typeof options.encoderHeaderTableSize === 'number') { - args.push( - `--encoder-header-table-size=${options.encoderHeaderTableSize}`); + ArrayPrototypePush(args, `--encoder-header-table-size=${options.encoderHeaderTableSize}`); } const scheme = options.scheme || 'http'; const host = options.host || '127.0.0.1'; - args.push(`${scheme}://${host}:${options.port}${options.path}`); + ArrayPrototypePush(args, `${scheme}://${host}:${options.port}${options.path}`); const child = child_process.spawn(this.executable, args); return child; } processResults(output) { const rex = /(\d+\.\d+) req\/s/; - return rex.exec(output)[1]; + return RegExpPrototypeExec(rex, output)[1]; } } const http_benchmarkers = [ new WrkBenchmarker(), new AutocannonBenchmarker(), - new TestDoubleBenchmarker('http'), - new TestDoubleBenchmarker('https'), - new TestDoubleBenchmarker('http2'), new H2LoadBenchmarker(), ]; @@ -205,20 +201,30 @@ exports.run = function(options, callback) { benchmarker: exports.default_http_benchmarker, ...options }; + let err; if (!options.benchmarker) { - callback(new Error('Could not locate required http benchmarker. See ' + - `${requirementsURL} for further instructions.`)); + err = new ERR_BENCHMARK_HTTP_COULD_NOT_LOCATE_BENCHMARKER( + 'Could not locate required http benchmarker. See ' + + `${requirementsURL} for further instructions.` + ); + callback(err); return; } const benchmarker = benchmarkers[options.benchmarker]; if (!benchmarker) { - callback(new Error(`Requested benchmarker '${options.benchmarker}' ` + - 'is not supported')); + err = new ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_SUPPROTED( + `Requested benchmarker '${options.benchmarker}' ` + + 'is not supported' + ); + callback(err); return; } if (!benchmarker.present) { - callback(new Error(`Requested benchmarker '${options.benchmarker}' ` + - 'is not installed')); + err = new ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_INSTALLED( + `Requested benchmarker '${options.benchmarker}' ` + + 'is not installed' + ); + callback(err); return; } @@ -239,13 +245,13 @@ exports.run = function(options, callback) { if (stdout !== '') { error_message += ` Output: ${stdout}`; } - callback(new Error(error_message), code); + callback(new ERR_BENCHMARK_HTTP_UNKNOWN_OUTPUT(error_message), code); return; } const result = benchmarker.processResults(stdout); if (result === undefined) { - callback(new Error( + callback(new ERR_BENCHMARK_HTTP_STRANGE_OUTPUT( `${options.benchmarker} produced strange output: ${stdout}`), code); return; } diff --git a/lib/benchmark.js b/lib/benchmark.js new file mode 100644 index 000000000000..372cb0303d5a --- /dev/null +++ b/lib/benchmark.js @@ -0,0 +1,398 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS + +'use strict'; + +const child_process = require('child_process'); +const { setTimeout } = require('timers'); +const { + ObjectKeys, + StringPrototypeSlice, + StringPrototypeRepeat, + ArrayPrototypePush, + ArrayPrototypeIndexOf, + ArrayPrototypeSplice, + ArrayPrototypeSlice, + ArrayIsArray, + ArrayPrototypeConcat, + BigIntPrototypeToString, + ObjectPrototypeHasOwnProperty: ObjectHasOwn, + Number, + NumberMAX_VALUE, + NumberPrototypeToString, + JSONStringify +} = primordials; + +const http_benchmarkers = require('_http-benchmarkers'); + +const { + codes: { + ERR_BENCHMARK_START_CALLED_MORE_THAN_ONCE, + ERR_BENCHMARK_END_CALLED_MORE_THAN_ONCE, + ERR_BENCHMARK_NO_OPERATION_COUNT, + ERR_BENCHMARK_INVALID_OPERATION_COUNT, + ERR_BENCHMARK_START_NOT_CALLED, + ERR_BENCHMARK_CLOCK_PRECISION, + ERR_INVALID_ARG_TYPE + } +} = require('internal/errors'); + +const { + validateFunction, + validateString, + validateNumber +} = require('internal/validators'); + +const { console } = require('internal/console/global'); + +class Benchmark { + + constructor(fn, benchmarkConfigs, options = {}) { + + validateFunction(fn); + // TODO: validate config and options + + // Used to make sure a benchmark only start a timer once + this._started = false; + + // Indicate that the benchmark ended + this._ended = false; + + // Holds process.hrtime value + this._time = 0n; + + this.options = options; + + this.name = StringPrototypeSlice(process.mainModule.filename, process.mainModule.path.length + 1); + + // Execution arguments i.e. flags used to run the jobs + this.flags = process.env.NODE_BENCHMARK_FLAGS ? + process.env.NODE_BENCHMARK_FLAGS.split(/\s+/) : + []; + + // Parse job-specific configuration from the command line arguments + const argv = ArrayPrototypeSlice(process.argv, 2); + + const parsed_args = this._parseArgs(argv, benchmarkConfigs, options); + + this.options = parsed_args.cli; + this.extra_options = parsed_args.extra; + + if (options.flags) { + this.flags = ArrayPrototypeConcat(this.flags, options.flags); + } + + // The configuration list as a queue of jobs + this.queue = this._queue(this.options); + + // The configuration of the current job, head of the queue + this.config = this.queue[0]; + + process.nextTick(() => { + if (ObjectHasOwn(process.env, 'NODE_RUN_BENCHMARK_FN')) { + fn(this.config); + } else { + // _run will use fork() to create a new process for each configuration + // combination. + this._run(); + } + }); + } + + http(options, cb) { + validateFunction(cb); + + // TODO: validate options + + const http_options = { ...options }; + http_options.benchmarker = http_options.benchmarker || + this.config.benchmarker || + this.extra_options.benchmarker || + http_benchmarkers.default_http_benchmarker; + http_benchmarkers.run( + http_options, (error, code, used_benchmarker, result, elapsed) => { + if (cb) { + cb(code); + } + if (error) { + console.error(error); + process.exit(code || 1); + } + this.config.benchmarker = used_benchmarker; + this.report(result, elapsed); + } + ); + } + + _run() { + + if (process.send) { + process.send({ + type: 'config', + name: this.name, + queueLength: this.queue.length, + }); + } + + const recursive = (queueIndex) => { + const config = this.queue[queueIndex]; + + // Set NODE_RUN_BENCHMARK_FN to indicate that the child shouldn't + // construct a configuration queue, but just execute the benchmark + // function. + const childEnv = { + ...process.env, + NODE_RUN_BENCHMARK_FN: '' + }; + + const childArgs = []; + + for (const key of ObjectKeys(config)) { + ArrayPrototypePush(childArgs, `${key}=${config[key]}`); + } + + for (const key of ObjectKeys(this.extra_options)) { + ArrayPrototypePush(childArgs, `${key}=${this.extra_options[key]}`); + } + + const child = child_process.fork(process.mainModule.filename, childArgs, { + env: childEnv, + execArgv: ArrayPrototypeConcat(this.flags, process.execArgv) + }); + + child.on('message', Benchmark.SendResult); + child.on('close', (code) => { + if (code) { + process.exit(code); + } + + if (queueIndex + 1 < this.queue.length) { + recursive(queueIndex + 1); + } + + }); + }; + + recursive(0); + } + + _parseArgs(argv, config, options) { + const cliOptions = {}; + // Check for the test mode first. + const testIndex = ArrayPrototypeIndexOf(argv, '--test'); + if (testIndex !== -1) { + for (const key of ObjectKeys(config)) { + const rawValue = config[key]; + let value = ArrayIsArray(rawValue) ? rawValue[0] : rawValue; + // Set numbers to one by default to reduce the runtime. + if (typeof value === 'number') { + if (key === 'dur' || key === 'duration') { + value = 0.05; + } else if (value > 1) { + value = 1; + } + } + cliOptions[key] = [value]; + } + // Override specific test options. + if (options.test) { + for (const key of ObjectKeys(options.test)) { + const value = options.test[key]; + cliOptions[key] = ArrayIsArray(value) ? value : [value]; + } + } + ArrayPrototypeSplice(argv, testIndex, 1); + } else { + // Accept single values instead of arrays. + for (const key of ObjectKeys(config)) { + const value = config[key]; + if (!ArrayIsArray(value)) + config[key] = [value]; + } + } + + const extraOptions = {}; + const validArgRE = /^(.+?)=([\s\S]*)$/; + // Parse configuration arguments + for (const arg of argv) { + + const match = arg.match(validArgRE); + + if (!match) { + console.error(`bad argument: ${arg}`); + process.exit(1); + } + + const key = match[1]; + const value = match[2]; + + if (ObjectHasOwn(config, key)) { + if (!cliOptions[key]) + cliOptions[key] = []; + ArrayPrototypePush( + cliOptions[key], + // Infer the type from the config object and parse accordingly + typeof config[key][0] === 'number' ? +value : value + ); + } else { + extraOptions[key] = value; + } + } + return { cli: { ...config, ...cliOptions }, extra: extraOptions }; + } + + _queue(options) { + const queue = []; + const keys = ObjectKeys(options); + + function recursive(keyIndex, prevConfig) { + const key = keys[keyIndex]; + const values = options[key]; + for (const value of values) { + try { + validateString(value, 'value'); + } catch (error) { + if (error.code !== 'ERR_INVALID_ARG_TYPE') { + throw error; + } + validateNumber(value, 'value'); + } + + if (typeof value !== typeof values[0]) { + // This is a requirement for being able to consistently and + // predictably parse CLI provided configuration values. + throw new ERR_INVALID_ARG_TYPE(`configuration "${key}" has mixed types`); + } + + const currConfig = { [key]: value, ...prevConfig }; + + if (keyIndex + 1 < keys.length) { + recursive(keyIndex + 1, currConfig); + } else { + ArrayPrototypePush(queue, currConfig); + } + } + } + + if (keys.length > 0) { + recursive(0, {}); + } else { + ArrayPrototypePush(queue, {}); + } + return queue; + } + + start() { + if (this._started) { + throw new ERR_BENCHMARK_START_CALLED_MORE_THAN_ONCE(); + } + this._started = true; + this._time = process.hrtime.bigint(); + } + + end(operations) { + // Get elapsed time now and do error checking later for accuracy. + const time = process.hrtime.bigint(); + + if (!this._started) { + throw new ERR_BENCHMARK_START_NOT_CALLED(); + } + if (this._ended) { + throw new ERR_BENCHMARK_END_CALLED_MORE_THAN_ONCE(); + } + if (typeof operations !== 'number') { + throw new ERR_BENCHMARK_NO_OPERATION_COUNT(); + } + if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED && operations <= 0) { + throw new ERR_BENCHMARK_INVALID_OPERATION_COUNT(); + } + + this._ended = true; + + if (time === this._time) { + if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED) + throw new ERR_BENCHMARK_CLOCK_PRECISION(); + // Avoid dividing by zero + this.report(operations && NumberMAX_VALUE, 0n); + return; + } + + const elapsed = time - this._time; + const rate = operations / (Number(elapsed) / 1e9); + this.report(rate, elapsed); + } + + report(rate, elapsed) { + Benchmark.SendResult({ + name: this.name, + conf: this.config, + rate, + time: this._nanoSecondsToString(elapsed), + type: 'report', + }); + } + + _nanoSecondsToString(bigint) { + const str = BigIntPrototypeToString(bigint); + const decimalPointIndex = str.length - 9; + if (decimalPointIndex <= 0) { + return `0.${StringPrototypeRepeat('0', -decimalPointIndex)}${str}`; + } + return `${StringPrototypeSlice(str, 0, decimalPointIndex)}.${StringPrototypeSlice(str, decimalPointIndex)}`; + } + + static SendResult(data) { + if (process.send) { + // If forked, report by process send + process.send(data, () => { + if (ObjectHasOwn(process.env, 'NODE_RUN_BENCHMARK_FN')) { + // If, for any reason, the process is unable to self close within + // a second after completing, forcefully close it. + setTimeout(() => { + process.exit(0); + }, 5000).unref(); + } + }); + } else { + // Otherwise report by stdout + process.stdout.write(Benchmark.FormatResult(data)); + } + } + + static FormatResult(data) { + // Construct configuration string, " A=a, B=b, ..." + let conf = ''; + for (const key of ObjectKeys(data.conf)) { + conf += ` ${key}=${JSONStringify(data.conf[key])}`; + } + let rate = NumberPrototypeToString(data.rate).split('.'); + rate[0] = rate[0].replace(/(\d)(?=(?:\d\d\d)+(?!\d))/g, '$1,'); + rate = (rate[1] ? rate.join('.') : rate[0]); + return `${data.name}${conf}: ${rate}\n`; + } +} + +module.exports = { + Benchmark, + createBenchmark(fn, config, options) { + return new Benchmark(fn, config, options); + } +}; diff --git a/tsconfig.json b/tsconfig.json index 1594a3363393..4a47e6a6e8d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -46,6 +46,7 @@ "assert": ["./lib/assert.js"], "assert/strict": ["./lib/assert/strict.js"], "async_hooks": ["./lib/async_hooks.js"], + "benchmark": ["./lib/benchmark.js"], "buffer": ["./lib/buffer.js"], "child_process": ["./lib/child_process.js"], "cluster": ["./lib/cluster.js"], From b3702bc186e0265f36ada9619d437bc91ea9fced Mon Sep 17 00:00:00 2001 From: zombieleet Date: Wed, 12 Oct 2022 01:58:24 +0100 Subject: [PATCH 3/7] lib/*.js (internal/error): add new errors for benchmark --- lib/internal/errors.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index f6a8ce6549c5..2fb78dd857c5 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -947,6 +947,17 @@ E('ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED', 'Snapshot is not supported in this context ', TypeError); E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError); E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError); +E('ERR_BENCHMARK_CLOCK_PRECISION', 'insufficient clock precision for short benchmark', Error); +E('ERR_BENCHMARK_END_CALLED_MORE_THAN_ONCE', 'Called end multiple times', Error); +E('ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_INSTALLED', '%s', Error); +E('ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_SUPPROTED', '%s', Error); +E('ERR_BENCHMARK_HTTP_COULD_NOT_LOCATE_BENCHMARKER', '%s', Error); +E('ERR_BENCHMARK_HTTP_STRANGE_OUTPUT', '%s', Error); +E('ERR_BENCHMARK_HTTP_UNKNOWN_OUTPUT', '%s', Error); +E('ERR_BENCHMARK_INVALID_OPERATION_COUNT', 'called end() with operation count <= 0', RangeError); +E('ERR_BENCHMARK_NO_OPERATION_COUNT', 'called end() without specifying operation count', Error); +E('ERR_BENCHMARK_START_CALLED_MORE_THAN_ONCE', 'Called start more than once in a single benchmark', Error); +E('ERR_BENCHMARK_START_NOT_CALLED', 'called end without start', Error); E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError); E('ERR_BUFFER_OUT_OF_BOUNDS', // Using a default argument here is important so the argument is not counted From c2ad70123637f9566161e9ce33508eb0c0f3c7b2 Mon Sep 17 00:00:00 2001 From: zombieleet Date: Wed, 12 Oct 2022 01:59:46 +0100 Subject: [PATCH 4/7] doc: document new errors for benchmark module --- doc/api/errors.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/doc/api/errors.md b/doc/api/errors.md index 9bb467af7fc7..40cbcb80794f 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -726,6 +726,77 @@ An attempt was made to register something that is not a function as an The type of an asynchronous resource was invalid. Users are also able to define their own types if using the public embedder API. + + +### `ERR_BENCHMARK_HTTP_COULD_NOT_LOCATE_BENCHMARKER` + +Could not locate any of the http benchmarkers(autocannon, wrk and h2load). + + + +### `ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_SUPPROTED` + +Specified http benchmarker implementation does not exists. +Valid http benchmarkers are. + +1. autocannon +2. wrk +3. h2load + + + +### `ERR_BENCHMARK_CLOCK_PRECISION` + +Insufficient clock precision for short benchmark. + + + +### `ERR_BENCHMARK_END_CALLED_MORE_THAN_ONCE` + +This error is thrown when the end method is called multiple times. + + + +### `ERR_BENCHMARK_HTTP_BENCHMARKER_NOT_INSTALLED` + +This error is thrown when the specified http benchmarker cannot be found. + + + +### `ERR_BENCHMARK_HTTP_STRANGE_OUTPUT` + +Thrown when the specified http benchmarker produces a strange output. + + + +### `ERR_BENCHMARK_HTTP_UNKNOWN_OUTPUT` + +Thrown when the specified http benchmarker failed. + + + +### `ERR_BENCHMARK_INVALID_OPERATION_COUNT` + +The end method was called when operations count is <= 0 + + + +### `ERR_BENCHMARK_NO_OPERATION_COUNT` + +The end method was called without specifying operation count + + + +### `ERR_BENCHMARK_START_CALLED_MORE_THAN_ONCE` + +This error is thrown when the end method is called multiple times. + + + +### `ERR_BENCHMARK_START_NOT_CALLED` + +The start method was not called before calling the end method. + ### `ERR_BROTLI_COMPRESSION_FAILED` From c057e115aa755feff607d8857799128608eed9de Mon Sep 17 00:00:00 2001 From: zombieleet Date: Fri, 14 Oct 2022 19:03:10 +0100 Subject: [PATCH 5/7] lib/internal/bootstrap (loader.js): benchmark module should use node: scheme --- benchmark/common.js | 2 +- lib/{ => internal/benchmark}/_benchmark_progress.js | 0 lib/{ => internal/benchmark}/_http-benchmarkers.js | 0 lib/internal/bootstrap/loaders.js | 1 + 4 files changed, 2 insertions(+), 1 deletion(-) rename lib/{ => internal/benchmark}/_benchmark_progress.js (100%) rename lib/{ => internal/benchmark}/_http-benchmarkers.js (100%) diff --git a/benchmark/common.js b/benchmark/common.js index 34c4c0f951b7..f916b9579842 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -1,5 +1,5 @@ 'use strict'; -const { Benchmark, createBenchmark } = require('benchmark'); +const { Benchmark, createBenchmark } = require('node:benchmark'); const urls = { long: 'http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' + diff --git a/lib/_benchmark_progress.js b/lib/internal/benchmark/_benchmark_progress.js similarity index 100% rename from lib/_benchmark_progress.js rename to lib/internal/benchmark/_benchmark_progress.js diff --git a/lib/_http-benchmarkers.js b/lib/internal/benchmark/_http-benchmarkers.js similarity index 100% rename from lib/_http-benchmarkers.js rename to lib/internal/benchmark/_http-benchmarkers.js diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index a3c3d375f709..3f5fae2bf968 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -124,6 +124,7 @@ const legacyWrapperList = new SafeSet([ // Modules that can only be imported via the node: scheme. const schemelessBlockList = new SafeSet([ 'test', + 'benchmark', ]); // Set up process.binding() and process._linkedBinding(). From ac46c9db2453849bd12217c3cacb7116fba617a0 Mon Sep 17 00:00:00 2001 From: zombieleet Date: Mon, 17 Oct 2022 13:29:07 +0100 Subject: [PATCH 6/7] benchmark: move benchmark/compare.js implementation implementation of benchmark/compare.js is now part of lib/benchmark.js --- benchmark/compare.js | 92 ++-------- lib/benchmark.js | 161 ++++++++++++++++-- lib/internal/benchmark/_benchmark_progress.js | 19 +++ lib/internal/benchmark/_http-benchmarkers.js | 1 + 4 files changed, 179 insertions(+), 94 deletions(-) diff --git a/benchmark/compare.js b/benchmark/compare.js index 169948e006d6..59e438fefd21 100644 --- a/benchmark/compare.js +++ b/benchmark/compare.js @@ -1,11 +1,7 @@ 'use strict'; -const { fork } = require('child_process'); -const { inspect } = require('util'); -const path = require('path'); const CLI = require('./_cli.js'); -const BenchmarkProgress = require('./_benchmark_progress.js'); - +const { compareBenchmarks } = require('node:benchmark'); // // Parse arguments // @@ -30,84 +26,24 @@ if (!cli.optional.new || !cli.optional.old) { cli.abort(cli.usage); } -const binaries = ['old', 'new']; const runs = cli.optional.runs ? parseInt(cli.optional.runs, 10) : 30; -const benchmarks = cli.benchmarks(); +const benchmarkFiles = cli.benchmarks(); -if (benchmarks.length === 0) { +if (benchmarkFiles.length === 0) { console.error('No benchmarks found'); process.exitCode = 1; return; } -// Create queue from the benchmarks list such both node versions are tested -// `runs` amount of times each. -// Note: BenchmarkProgress relies on this order to estimate -// how much runs remaining for a file. All benchmarks generated from -// the same file must be run consecutively. -const queue = []; -for (const filename of benchmarks) { - for (let iter = 0; iter < runs; iter++) { - for (const binary of binaries) { - queue.push({ binary, filename, iter }); - } +compareBenchmarks( + { + binary: { + old: cli.optional.old, + new: cli.optional.new + }, + benchmarkFiles, + showProgress: !cli.optional['no-progress'], + set: cli.optional.set, + runs, } -} -// queue.length = binary.length * runs * benchmarks.length - -// Print csv header -console.log('"binary","filename","configuration","rate","time"'); - -const kStartOfQueue = 0; - -const showProgress = !cli.optional['no-progress']; -let progress; -if (showProgress) { - progress = new BenchmarkProgress(queue, benchmarks); - progress.startQueue(kStartOfQueue); -} - -(function recursive(i) { - const job = queue[i]; - - const child = fork(path.resolve(__dirname, job.filename), cli.optional.set, { - execPath: cli.optional[job.binary] - }); - - child.on('message', (data) => { - if (data.type === 'report') { - // Construct configuration string, " A=a, B=b, ..." - let conf = ''; - for (const key of Object.keys(data.conf)) { - conf += ` ${key}=${inspect(data.conf[key])}`; - } - conf = conf.slice(1); - // Escape quotes (") for correct csv formatting - conf = conf.replace(/"/g, '""'); - - console.log(`"${job.binary}","${job.filename}","${conf}",` + - `${data.rate},${data.time}`); - if (showProgress) { - // One item in the subqueue has been completed. - progress.completeConfig(data); - } - } else if (showProgress && data.type === 'config') { - // The child has computed the configurations, ready to run subqueue. - progress.startSubqueue(data, i); - } - }); - - child.once('close', (code) => { - if (code) { - process.exit(code); - } - if (showProgress) { - progress.completeRun(job); - } - - // If there are more benchmarks execute the next - if (i + 1 < queue.length) { - recursive(i + 1); - } - }); -})(kStartOfQueue); +); diff --git a/lib/benchmark.js b/lib/benchmark.js index 372cb0303d5a..84cbf23d0c8e 100644 --- a/lib/benchmark.js +++ b/lib/benchmark.js @@ -22,6 +22,8 @@ 'use strict'; const child_process = require('child_process'); +const util = require('util'); +const path = require('path'); const { setTimeout } = require('timers'); const { ObjectKeys, @@ -58,17 +60,21 @@ const { const { validateFunction, validateString, - validateNumber + validateNumber, + validateArray, + validateBoolean, + validateStringArray, } = require('internal/validators'); - -const { console } = require('internal/console/global'); +const { validatePath } = require('internal/fs/utils'); class Benchmark { constructor(fn, benchmarkConfigs, options = {}) { validateFunction(fn); - // TODO: validate config and options + + if (options.flags) + validateArray(options.flags); // Used to make sure a benchmark only start a timer once this._started = false; @@ -105,7 +111,6 @@ class Benchmark { // The configuration of the current job, head of the queue this.config = this.queue[0]; - process.nextTick(() => { if (ObjectHasOwn(process.env, 'NODE_RUN_BENCHMARK_FN')) { fn(this.config); @@ -117,24 +122,28 @@ class Benchmark { }); } + /** + * Runs the http benchmaker specified in `options.benchmarker` + * @param {object} options + * @param {string} options.path + * @param {number} options.connections + * @param {number} options.duration + * @param {string} options.benchmarker + * @param {Function} cb + */ http(options, cb) { - validateFunction(cb); - - // TODO: validate options - const http_options = { ...options }; http_options.benchmarker = http_options.benchmarker || - this.config.benchmarker || - this.extra_options.benchmarker || - http_benchmarkers.default_http_benchmarker; + this.config.benchmarker || + this.extra_options.benchmarker || + http_benchmarkers.default_http_benchmarker; http_benchmarkers.run( http_options, (error, code, used_benchmarker, result, elapsed) => { if (cb) { cb(code); } if (error) { - console.error(error); - process.exit(code || 1); + throw error; } this.config.benchmarker = used_benchmarker; this.report(result, elapsed); @@ -237,8 +246,7 @@ class Benchmark { const match = arg.match(validArgRE); if (!match) { - console.error(`bad argument: ${arg}`); - process.exit(1); + throw new ERR_INVALID_ARG_TYPE(`bad argument: ${arg}`); } const key = match[1]; @@ -260,6 +268,7 @@ class Benchmark { } _queue(options) { + const queue = []; const keys = ObjectKeys(options); @@ -391,8 +400,128 @@ class Benchmark { } module.exports = { + HTTP_BENCHMARK_PORT: http_benchmarkers.HTTP_BENCHMARK_PORT, Benchmark, + /** + * Returns an instance of Benchmark + * @param {Function} fn + * @param {Object} config + * @param {{ + * flags: Array + * }} options + * @returns {Benchmark} + */ createBenchmark(fn, config, options) { return new Benchmark(fn, config, options); + }, + + /** + * Benchmarks an operation using + * `options.binary.new` and `options.binary.old` and + * compares the result. + * @param {object} options + * @param {number} options.runs + * @param {boolean} options.showProgress + * @param {Array} options.benchmarkFiles + * @param {object} options.binary + * @param {string} options.binary.old + * @param {string} options.binary.new + * @param {Array} options.set + */ + compareBenchmarks(options) { + const kStartOfQueue = 0; + + validateStringArray(options.benchmarkFiles); + validateBoolean(options.showProgress); + validatePath(options.binary.old); + validatePath(options.binary.new); + + options.runs = typeof options.runs !== 'number' ? 30 : 0; + + validateNumber(options.runs); + + for (const file of options.benchmarkFiles) { + validatePath(file); + } + + if (options.set) { + validateStringArray(options.set); + for (const variable of options.set) { + const tokens = variable.split('='); + if ( + tokens.length > 2 || + tokens < 2 || + /^\s+$/.test(tokens[1]) + ) { + throw new ERR_INVALID_ARG_TYPE( + `Expected ${variable} to only follow this format key=value` + ); + } + } + } + + const queue = BenchmarkProgress.QueueBenchmarkFiles( + { + binaries: ObjectKeys(options.binary), + benchmarkFiles: options.benchmarkFiles, + runs: options.runs, + } + ); + + let progress; + + if (options.showProgress) { + progress = new BenchmarkProgress(queue, options.benchmarkFiles); + progress.startQueue(kStartOfQueue); + } + + process.stdout.write('"binary","filename","configuration","rate","time"\n'); + + (function recursive(i) { + const job = queue[i]; + + const child = child_process.fork(path.resolve(process.mainModule.path, job.filename), options.set, { + execPath: options.binary[job.binary] + }); + + child.on('message', (data) => { + if (data.type === 'report') { + // Construct configuration string, " A=a, B=b, ..." + let conf = ''; + for (const key of ObjectKeys(data.conf)) { + conf += ` ${key}=${util.inspect(data.conf[key])}`; + } + conf = StringPrototypeSlice(conf, 1); + // Escape quotes (") for correct csv formatting + conf = conf.replace(/"/g, '""'); + + process.stdout.write( + `"${job.binary}","${job.filename}","${conf}",` + + `${data.rate},${data.time}\n` + ); + + if (options.showProgress) { + // One item in the subqueue has been completed. + progress.completeConfig(data); + } + } else if (options.showProgress && data.type === 'config') { + // The child has computed the configurations, ready to run subqueue. + progress.startSubqueue(data, i); + } + }); + + child.once('close', (code) => { + if (code) { + process.exit(code); + } + if (options.showProgress) { + progress.completeRun(job); + } + // If there are more benchmarks execute the next + if (i + 1 < queue.length) { + recursive(i + 1); + } + }); + })(kStartOfQueue); } }; diff --git a/lib/internal/benchmark/_benchmark_progress.js b/lib/internal/benchmark/_benchmark_progress.js index ca177ba76042..b9f95b4cc894 100644 --- a/lib/internal/benchmark/_benchmark_progress.js +++ b/lib/internal/benchmark/_benchmark_progress.js @@ -148,6 +148,25 @@ class BenchmarkProgress { readline.cursorTo(process.stderr, 0); process.stderr.write(this.getProgress()); } + + /** + * Create queue from the benchmarks list such both node versions are tested + * `runs` amount of times each. + * Note: BenchmarkProgress relies on this order to estimate + * how much runs remaining for a file. All benchmarks generated from + * the same file must be run consecutively. + */ + static QueueBenchmarkFiles(options) { + const queue = []; + for (const filename of options.benchmarkFiles) { + for (let iter = 0; iter < options.runs; iter++) { + for (const binary of options.binaries) { + queue.push({ binary, filename, iter }); + } + } + } + return queue; + } } module.exports = BenchmarkProgress; diff --git a/lib/internal/benchmark/_http-benchmarkers.js b/lib/internal/benchmark/_http-benchmarkers.js index 05725e0c2f94..7bb508a505e2 100644 --- a/lib/internal/benchmark/_http-benchmarkers.js +++ b/lib/internal/benchmark/_http-benchmarkers.js @@ -198,6 +198,7 @@ exports.run = function(options, callback) { path: '/', connections: 100, duration: 5, + host: '127.0.0.1', benchmarker: exports.default_http_benchmarker, ...options }; From faa57ffa8a68044f3ae1ccc65bbb94e300a5bf3f Mon Sep 17 00:00:00 2001 From: zombieleet Date: Mon, 17 Oct 2022 13:41:25 +0100 Subject: [PATCH 7/7] lib (benchmark): add scheme support for http benchmarks add validaions for http options as well as normal benchmark options add few jsdoc comments --- lib/benchmark.js | 13 ++++++++-- lib/internal/benchmark/_http-benchmarkers.js | 25 ++++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/benchmark.js b/lib/benchmark.js index 84cbf23d0c8e..929a780c8c6b 100644 --- a/lib/benchmark.js +++ b/lib/benchmark.js @@ -40,10 +40,11 @@ const { Number, NumberMAX_VALUE, NumberPrototypeToString, - JSONStringify + JSONStringify, } = primordials; -const http_benchmarkers = require('_http-benchmarkers'); +const http_benchmarkers = require('internal/benchmark/_http-benchmarkers'); +const BenchmarkProgress = require('internal/benchmark/_benchmark_progress'); const { codes: { @@ -129,6 +130,7 @@ class Benchmark { * @param {number} options.connections * @param {number} options.duration * @param {string} options.benchmarker + * @param {string} options.scheme * @param {Function} cb */ http(options, cb) { @@ -309,6 +311,9 @@ class Benchmark { return queue; } + /** + * Start the benchmarker + */ start() { if (this._started) { throw new ERR_BENCHMARK_START_CALLED_MORE_THAN_ONCE(); @@ -317,6 +322,10 @@ class Benchmark { this._time = process.hrtime.bigint(); } + /** + * Stops the benchmarker + * @param {number} operations + */ end(operations) { // Get elapsed time now and do error checking later for accuracy. const time = process.hrtime.bigint(); diff --git a/lib/internal/benchmark/_http-benchmarkers.js b/lib/internal/benchmark/_http-benchmarkers.js index 7bb508a505e2..3bc64576f355 100644 --- a/lib/internal/benchmark/_http-benchmarkers.js +++ b/lib/internal/benchmark/_http-benchmarkers.js @@ -43,11 +43,17 @@ const { RegExpPrototypeExec } = primordials; +const { + validateFunction, + validateString, + validateNumber +} = require('internal/validators'); + const requirementsURL = 'https://github.com/nodejs/node/blob/HEAD/benchmark/writing-and-running-benchmarks.md#http-benchmark-requirements'; // The port used by servers and wrk -exports.PORT = Number(process.env.PORT) || 12346; +exports.HTTP_BENCHMARK_PORT = Number(process.env.PORT) || 12346; class LoadBalancerBenchmarker { constructor(name, executable, type) { @@ -75,7 +81,7 @@ class AutocannonBenchmarker extends LoadBalancerBenchmarker { ArrayPrototypePush(args, '-H', `${field}=${options.headers[field]}`); } const scheme = options.scheme || 'http'; - ArrayPrototypePush(args, `${scheme}://127.0.0.1:${options.port}${options.path}`); + ArrayPrototypePush(args, `${scheme}://${options.host}:${options.port}${options.path}`); const child = child_process.spawn(this.executable, args); return child; } @@ -108,7 +114,7 @@ class WrkBenchmarker extends LoadBalancerBenchmarker { '-d', duration, '-c', options.connections, '-t', MathMin(options.connections, require('os').cpus().length || 8), - `${scheme}://127.0.0.1:${options.port}${options.path}`, + `${scheme}://${options.host}:${options.port}${options.path}`, ]; for (const field in options.headers) { ArrayPrototypePush(args, '-H', `${field}: ${options.headers[field]}`); @@ -165,8 +171,7 @@ class H2LoadBenchmarker extends LoadBalancerBenchmarker { ArrayPrototypePush(args, `--encoder-header-table-size=${options.encoderHeaderTableSize}`); } const scheme = options.scheme || 'http'; - const host = options.host || '127.0.0.1'; - ArrayPrototypePush(args, `${scheme}://${host}:${options.port}${options.path}`); + ArrayPrototypePush(args, `${scheme}://${options.host}:${options.port}${options.path}`); const child = child_process.spawn(this.executable, args); return child; } @@ -193,6 +198,16 @@ http_benchmarkers.forEach((benchmarker) => { }); exports.run = function(options, callback) { + + validateFunction(callback); + + if (options.path) + validateString(options.path); + if (options.connections) + validateNumber(options.connections); + if (options.benchmarker) + validateString(options.benchmarker); + options = { port: exports.PORT, path: '/',