11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst child_process = require('child_process');
41cb0ef41Sopenharmony_ciconst path = require('path');
51cb0ef41Sopenharmony_ciconst fs = require('fs');
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ciconst requirementsURL =
81cb0ef41Sopenharmony_ci  'https://github.com/nodejs/node/blob/HEAD/doc/contributing/writing-and-running-benchmarks.md#http-benchmark-requirements';
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_ci// The port used by servers and wrk
111cb0ef41Sopenharmony_ciexports.PORT = Number(process.env.PORT) || 12346;
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_ciclass AutocannonBenchmarker {
141cb0ef41Sopenharmony_ci  constructor() {
151cb0ef41Sopenharmony_ci    this.name = 'autocannon';
161cb0ef41Sopenharmony_ci    this.executable =
171cb0ef41Sopenharmony_ci      process.platform === 'win32' ? 'autocannon.cmd' : 'autocannon';
181cb0ef41Sopenharmony_ci    const result = child_process.spawnSync(this.executable, ['-h']);
191cb0ef41Sopenharmony_ci    this.present = !(result.error && result.error.code === 'ENOENT');
201cb0ef41Sopenharmony_ci  }
211cb0ef41Sopenharmony_ci
221cb0ef41Sopenharmony_ci  create(options) {
231cb0ef41Sopenharmony_ci    const args = [
241cb0ef41Sopenharmony_ci      '-d', options.duration,
251cb0ef41Sopenharmony_ci      '-c', options.connections,
261cb0ef41Sopenharmony_ci      '-j',
271cb0ef41Sopenharmony_ci      '-n',
281cb0ef41Sopenharmony_ci    ];
291cb0ef41Sopenharmony_ci    for (const field in options.headers) {
301cb0ef41Sopenharmony_ci      args.push('-H', `${field}=${options.headers[field]}`);
311cb0ef41Sopenharmony_ci    }
321cb0ef41Sopenharmony_ci    const scheme = options.scheme || 'http';
331cb0ef41Sopenharmony_ci    args.push(`${scheme}://127.0.0.1:${options.port}${options.path}`);
341cb0ef41Sopenharmony_ci    const child = child_process.spawn(this.executable, args);
351cb0ef41Sopenharmony_ci    return child;
361cb0ef41Sopenharmony_ci  }
371cb0ef41Sopenharmony_ci
381cb0ef41Sopenharmony_ci  processResults(output) {
391cb0ef41Sopenharmony_ci    let result;
401cb0ef41Sopenharmony_ci    try {
411cb0ef41Sopenharmony_ci      result = JSON.parse(output);
421cb0ef41Sopenharmony_ci    } catch {
431cb0ef41Sopenharmony_ci      return undefined;
441cb0ef41Sopenharmony_ci    }
451cb0ef41Sopenharmony_ci    if (!result || !result.requests || !result.requests.average) {
461cb0ef41Sopenharmony_ci      return undefined;
471cb0ef41Sopenharmony_ci    }
481cb0ef41Sopenharmony_ci    return result.requests.average;
491cb0ef41Sopenharmony_ci  }
501cb0ef41Sopenharmony_ci}
511cb0ef41Sopenharmony_ci
521cb0ef41Sopenharmony_ciclass WrkBenchmarker {
531cb0ef41Sopenharmony_ci  constructor() {
541cb0ef41Sopenharmony_ci    this.name = 'wrk';
551cb0ef41Sopenharmony_ci    this.executable = 'wrk';
561cb0ef41Sopenharmony_ci    const result = child_process.spawnSync(this.executable, ['-h']);
571cb0ef41Sopenharmony_ci    this.present = !(result.error && result.error.code === 'ENOENT');
581cb0ef41Sopenharmony_ci  }
591cb0ef41Sopenharmony_ci
601cb0ef41Sopenharmony_ci  create(options) {
611cb0ef41Sopenharmony_ci    const duration = typeof options.duration === 'number' ?
621cb0ef41Sopenharmony_ci      Math.max(options.duration, 1) :
631cb0ef41Sopenharmony_ci      options.duration;
641cb0ef41Sopenharmony_ci    const scheme = options.scheme || 'http';
651cb0ef41Sopenharmony_ci    const args = [
661cb0ef41Sopenharmony_ci      '-d', duration,
671cb0ef41Sopenharmony_ci      '-c', options.connections,
681cb0ef41Sopenharmony_ci      '-t', Math.min(options.connections, require('os').availableParallelism() || 8),
691cb0ef41Sopenharmony_ci      `${scheme}://127.0.0.1:${options.port}${options.path}`,
701cb0ef41Sopenharmony_ci    ];
711cb0ef41Sopenharmony_ci    for (const field in options.headers) {
721cb0ef41Sopenharmony_ci      args.push('-H', `${field}: ${options.headers[field]}`);
731cb0ef41Sopenharmony_ci    }
741cb0ef41Sopenharmony_ci    const child = child_process.spawn(this.executable, args);
751cb0ef41Sopenharmony_ci    return child;
761cb0ef41Sopenharmony_ci  }
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_ci  processResults(output) {
791cb0ef41Sopenharmony_ci    const throughputRe = /Requests\/sec:[ \t]+([0-9.]+)/;
801cb0ef41Sopenharmony_ci    const match = output.match(throughputRe);
811cb0ef41Sopenharmony_ci    const throughput = match && +match[1];
821cb0ef41Sopenharmony_ci    if (!isFinite(throughput)) {
831cb0ef41Sopenharmony_ci      return undefined;
841cb0ef41Sopenharmony_ci    }
851cb0ef41Sopenharmony_ci    return throughput;
861cb0ef41Sopenharmony_ci  }
871cb0ef41Sopenharmony_ci}
881cb0ef41Sopenharmony_ci
891cb0ef41Sopenharmony_ci/**
901cb0ef41Sopenharmony_ci * Simple, single-threaded benchmarker for testing if the benchmark
911cb0ef41Sopenharmony_ci * works
921cb0ef41Sopenharmony_ci */
931cb0ef41Sopenharmony_ciclass TestDoubleBenchmarker {
941cb0ef41Sopenharmony_ci  constructor(type) {
951cb0ef41Sopenharmony_ci    // `type` is the type of benchmarker. Possible values are 'http', 'https',
961cb0ef41Sopenharmony_ci    // and 'http2'.
971cb0ef41Sopenharmony_ci    this.name = `test-double-${type}`;
981cb0ef41Sopenharmony_ci    this.executable = path.resolve(__dirname, '_test-double-benchmarker.js');
991cb0ef41Sopenharmony_ci    this.present = fs.existsSync(this.executable);
1001cb0ef41Sopenharmony_ci    this.type = type;
1011cb0ef41Sopenharmony_ci  }
1021cb0ef41Sopenharmony_ci
1031cb0ef41Sopenharmony_ci  create(options) {
1041cb0ef41Sopenharmony_ci    process.env.duration = process.env.duration || options.duration || 5;
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_ci    const scheme = options.scheme || 'http';
1071cb0ef41Sopenharmony_ci    const env = {
1081cb0ef41Sopenharmony_ci      test_url: `${scheme}://127.0.0.1:${options.port}${options.path}`,
1091cb0ef41Sopenharmony_ci      ...process.env,
1101cb0ef41Sopenharmony_ci    };
1111cb0ef41Sopenharmony_ci
1121cb0ef41Sopenharmony_ci    const child = child_process.fork(this.executable,
1131cb0ef41Sopenharmony_ci                                     [this.type],
1141cb0ef41Sopenharmony_ci                                     { silent: true, env });
1151cb0ef41Sopenharmony_ci    return child;
1161cb0ef41Sopenharmony_ci  }
1171cb0ef41Sopenharmony_ci
1181cb0ef41Sopenharmony_ci  processResults(output) {
1191cb0ef41Sopenharmony_ci    let result;
1201cb0ef41Sopenharmony_ci    try {
1211cb0ef41Sopenharmony_ci      result = JSON.parse(output);
1221cb0ef41Sopenharmony_ci    } catch {
1231cb0ef41Sopenharmony_ci      return undefined;
1241cb0ef41Sopenharmony_ci    }
1251cb0ef41Sopenharmony_ci    return result.throughput;
1261cb0ef41Sopenharmony_ci  }
1271cb0ef41Sopenharmony_ci}
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci/**
1301cb0ef41Sopenharmony_ci * HTTP/2 Benchmarker
1311cb0ef41Sopenharmony_ci */
1321cb0ef41Sopenharmony_ciclass H2LoadBenchmarker {
1331cb0ef41Sopenharmony_ci  constructor() {
1341cb0ef41Sopenharmony_ci    this.name = 'h2load';
1351cb0ef41Sopenharmony_ci    this.executable = 'h2load';
1361cb0ef41Sopenharmony_ci    const result = child_process.spawnSync(this.executable, ['-h']);
1371cb0ef41Sopenharmony_ci    this.present = !(result.error && result.error.code === 'ENOENT');
1381cb0ef41Sopenharmony_ci  }
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ci  create(options) {
1411cb0ef41Sopenharmony_ci    const args = [];
1421cb0ef41Sopenharmony_ci    if (typeof options.requests === 'number')
1431cb0ef41Sopenharmony_ci      args.push('-n', options.requests);
1441cb0ef41Sopenharmony_ci    if (typeof options.clients === 'number')
1451cb0ef41Sopenharmony_ci      args.push('-c', options.clients);
1461cb0ef41Sopenharmony_ci    if (typeof options.threads === 'number')
1471cb0ef41Sopenharmony_ci      args.push('-t', options.threads);
1481cb0ef41Sopenharmony_ci    if (typeof options.maxConcurrentStreams === 'number')
1491cb0ef41Sopenharmony_ci      args.push('-m', options.maxConcurrentStreams);
1501cb0ef41Sopenharmony_ci    if (typeof options.initialWindowSize === 'number')
1511cb0ef41Sopenharmony_ci      args.push('-w', options.initialWindowSize);
1521cb0ef41Sopenharmony_ci    if (typeof options.sessionInitialWindowSize === 'number')
1531cb0ef41Sopenharmony_ci      args.push('-W', options.sessionInitialWindowSize);
1541cb0ef41Sopenharmony_ci    if (typeof options.rate === 'number')
1551cb0ef41Sopenharmony_ci      args.push('-r', options.rate);
1561cb0ef41Sopenharmony_ci    if (typeof options.ratePeriod === 'number')
1571cb0ef41Sopenharmony_ci      args.push(`--rate-period=${options.ratePeriod}`);
1581cb0ef41Sopenharmony_ci    if (typeof options.duration === 'number')
1591cb0ef41Sopenharmony_ci      args.push('-T', options.duration);
1601cb0ef41Sopenharmony_ci    if (typeof options.timeout === 'number')
1611cb0ef41Sopenharmony_ci      args.push('-N', options.timeout);
1621cb0ef41Sopenharmony_ci    if (typeof options.headerTableSize === 'number')
1631cb0ef41Sopenharmony_ci      args.push(`--header-table-size=${options.headerTableSize}`);
1641cb0ef41Sopenharmony_ci    if (typeof options.encoderHeaderTableSize === 'number') {
1651cb0ef41Sopenharmony_ci      args.push(
1661cb0ef41Sopenharmony_ci        `--encoder-header-table-size=${options.encoderHeaderTableSize}`);
1671cb0ef41Sopenharmony_ci    }
1681cb0ef41Sopenharmony_ci    const scheme = options.scheme || 'http';
1691cb0ef41Sopenharmony_ci    const host = options.host || '127.0.0.1';
1701cb0ef41Sopenharmony_ci    args.push(`${scheme}://${host}:${options.port}${options.path}`);
1711cb0ef41Sopenharmony_ci    const child = child_process.spawn(this.executable, args);
1721cb0ef41Sopenharmony_ci    return child;
1731cb0ef41Sopenharmony_ci  }
1741cb0ef41Sopenharmony_ci
1751cb0ef41Sopenharmony_ci  processResults(output) {
1761cb0ef41Sopenharmony_ci    const rex = /(\d+\.\d+) req\/s/;
1771cb0ef41Sopenharmony_ci    return rex.exec(output)[1];
1781cb0ef41Sopenharmony_ci  }
1791cb0ef41Sopenharmony_ci}
1801cb0ef41Sopenharmony_ci
1811cb0ef41Sopenharmony_ciconst http_benchmarkers = [
1821cb0ef41Sopenharmony_ci  new WrkBenchmarker(),
1831cb0ef41Sopenharmony_ci  new AutocannonBenchmarker(),
1841cb0ef41Sopenharmony_ci  new TestDoubleBenchmarker('http'),
1851cb0ef41Sopenharmony_ci  new TestDoubleBenchmarker('https'),
1861cb0ef41Sopenharmony_ci  new TestDoubleBenchmarker('http2'),
1871cb0ef41Sopenharmony_ci  new H2LoadBenchmarker(),
1881cb0ef41Sopenharmony_ci];
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ciconst benchmarkers = {};
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_cihttp_benchmarkers.forEach((benchmarker) => {
1931cb0ef41Sopenharmony_ci  benchmarkers[benchmarker.name] = benchmarker;
1941cb0ef41Sopenharmony_ci  if (!exports.default_http_benchmarker && benchmarker.present) {
1951cb0ef41Sopenharmony_ci    exports.default_http_benchmarker = benchmarker.name;
1961cb0ef41Sopenharmony_ci  }
1971cb0ef41Sopenharmony_ci});
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ciexports.run = function(options, callback) {
2001cb0ef41Sopenharmony_ci  options = {
2011cb0ef41Sopenharmony_ci    port: exports.PORT,
2021cb0ef41Sopenharmony_ci    path: '/',
2031cb0ef41Sopenharmony_ci    connections: 100,
2041cb0ef41Sopenharmony_ci    duration: 5,
2051cb0ef41Sopenharmony_ci    benchmarker: exports.default_http_benchmarker,
2061cb0ef41Sopenharmony_ci    ...options,
2071cb0ef41Sopenharmony_ci  };
2081cb0ef41Sopenharmony_ci  if (!options.benchmarker) {
2091cb0ef41Sopenharmony_ci    callback(new Error('Could not locate required http benchmarker. See ' +
2101cb0ef41Sopenharmony_ci                       `${requirementsURL} for further instructions.`));
2111cb0ef41Sopenharmony_ci    return;
2121cb0ef41Sopenharmony_ci  }
2131cb0ef41Sopenharmony_ci  const benchmarker = benchmarkers[options.benchmarker];
2141cb0ef41Sopenharmony_ci  if (!benchmarker) {
2151cb0ef41Sopenharmony_ci    callback(new Error(`Requested benchmarker '${options.benchmarker}' ` +
2161cb0ef41Sopenharmony_ci                       'is  not supported'));
2171cb0ef41Sopenharmony_ci    return;
2181cb0ef41Sopenharmony_ci  }
2191cb0ef41Sopenharmony_ci  if (!benchmarker.present) {
2201cb0ef41Sopenharmony_ci    callback(new Error(`Requested benchmarker '${options.benchmarker}' ` +
2211cb0ef41Sopenharmony_ci                       'is  not installed'));
2221cb0ef41Sopenharmony_ci    return;
2231cb0ef41Sopenharmony_ci  }
2241cb0ef41Sopenharmony_ci
2251cb0ef41Sopenharmony_ci  const benchmarker_start = process.hrtime.bigint();
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ci  const child = benchmarker.create(options);
2281cb0ef41Sopenharmony_ci
2291cb0ef41Sopenharmony_ci  child.stderr.pipe(process.stderr);
2301cb0ef41Sopenharmony_ci
2311cb0ef41Sopenharmony_ci  let stdout = '';
2321cb0ef41Sopenharmony_ci  child.stdout.setEncoding('utf8');
2331cb0ef41Sopenharmony_ci  child.stdout.on('data', (chunk) => stdout += chunk);
2341cb0ef41Sopenharmony_ci
2351cb0ef41Sopenharmony_ci  child.once('close', (code) => {
2361cb0ef41Sopenharmony_ci    const benchmark_end = process.hrtime.bigint();
2371cb0ef41Sopenharmony_ci    if (code) {
2381cb0ef41Sopenharmony_ci      let error_message = `${options.benchmarker} failed with ${code}.`;
2391cb0ef41Sopenharmony_ci      if (stdout !== '') {
2401cb0ef41Sopenharmony_ci        error_message += ` Output: ${stdout}`;
2411cb0ef41Sopenharmony_ci      }
2421cb0ef41Sopenharmony_ci      callback(new Error(error_message), code);
2431cb0ef41Sopenharmony_ci      return;
2441cb0ef41Sopenharmony_ci    }
2451cb0ef41Sopenharmony_ci
2461cb0ef41Sopenharmony_ci    const result = benchmarker.processResults(stdout);
2471cb0ef41Sopenharmony_ci    if (result === undefined) {
2481cb0ef41Sopenharmony_ci      callback(new Error(
2491cb0ef41Sopenharmony_ci        `${options.benchmarker} produced strange output: ${stdout}`), code);
2501cb0ef41Sopenharmony_ci      return;
2511cb0ef41Sopenharmony_ci    }
2521cb0ef41Sopenharmony_ci
2531cb0ef41Sopenharmony_ci    const elapsed = benchmark_end - benchmarker_start;
2541cb0ef41Sopenharmony_ci    callback(null, code, options.benchmarker, result, elapsed);
2551cb0ef41Sopenharmony_ci  });
2561cb0ef41Sopenharmony_ci
2571cb0ef41Sopenharmony_ci};
258