summaryrefslogtreecommitdiff
path: root/benchmark/_http-benchmarkers.js
blob: ad586f1cb60ef68a100c92222cbdc32886160a33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
'use strict';

const child_process = require('child_process');

// The port used by servers and wrk
exports.PORT = process.env.PORT || 12346;

function AutocannonBenchmarker() {
  this.name = 'autocannon';
  this.autocannon_exe = process.platform === 'win32' ?
                        'autocannon.cmd' :
                        'autocannon';
  const result = child_process.spawnSync(this.autocannon_exe, ['-h']);
  this.present = !(result.error && result.error.code === 'ENOENT');
}

AutocannonBenchmarker.prototype.create = function(options) {
  const args = [
    '-d', options.duration,
    '-c', options.connections,
    '-j',
    '-n',
    `http://127.0.0.1:${options.port}${options.path}`
  ];
  const child = child_process.spawn(this.autocannon_exe, args);
  return child;
};

AutocannonBenchmarker.prototype.processResults = function(output) {
  let result;
  try {
    result = JSON.parse(output);
  } catch (err) {
    // Do nothing, let next line handle this
  }
  if (!result || !result.requests || !result.requests.average) {
    return undefined;
  } else {
    return result.requests.average;
  }
};

function WrkBenchmarker() {
  this.name = 'wrk';
  this.regexp = /Requests\/sec:[ \t]+([0-9.]+)/;
  const result = child_process.spawnSync('wrk', ['-h']);
  this.present = !(result.error && result.error.code === 'ENOENT');
}

WrkBenchmarker.prototype.create = function(options) {
  const args = [
    '-d', options.duration,
    '-c', options.connections,
    '-t', 8,
    `http://127.0.0.1:${options.port}${options.path}`
  ];
  const child = child_process.spawn('wrk', args);
  return child;
};

WrkBenchmarker.prototype.processResults = function(output) {
  const match = output.match(this.regexp);
  const result = match && +match[1];
  if (!isFinite(result)) {
    return undefined;
  } else {
    return result;
  }
};

const http_benchmarkers = [new WrkBenchmarker(), new AutocannonBenchmarker()];

const benchmarkers = {};

http_benchmarkers.forEach((benchmarker) => {
  benchmarkers[benchmarker.name] = benchmarker;
  if (!exports.default_http_benchmarker && benchmarker.present) {
    exports.default_http_benchmarker = benchmarker.name;
  }
});

exports.run = function(options, callback) {
  options = Object.assign({
    port: exports.PORT,
    path: '/',
    connections: 100,
    duration: 10,
    benchmarker: exports.default_http_benchmarker
  }, options);
  if (!options.benchmarker) {
    callback(new Error('Could not locate required http benchmarker. See ' +
                       'https://github.com/nodejs/node/blob/master/doc/guides/writing-and-running-benchmarks.md##http-benchmark-requirements ' +
                       'for further instructions.'));
    return;
  }
  const benchmarker = benchmarkers[options.benchmarker];
  if (!benchmarker) {
    callback(new Error(`Requested benchmarker '${options.benchmarker}' is ` +
                       'not supported'));
    return;
  }
  if (!benchmarker.present) {
    callback(new Error(`Requested benchmarker '${options.benchmarker}' is ` +
                       'not installed'));
    return;
  }

  const benchmarker_start = process.hrtime();

  const child = benchmarker.create(options);

  child.stderr.pipe(process.stderr);

  let stdout = '';
  child.stdout.on('data', (chunk) => stdout += chunk.toString());

  child.once('close', function(code) {
    const elapsed = process.hrtime(benchmarker_start);
    if (code) {
      let error_message = `${options.benchmarker} failed with ${code}.`;
      if (stdout !== '') {
        error_message += ` Output: ${stdout}`;
      }
      callback(new Error(error_message), code);
      return;
    }

    const result = benchmarker.processResults(stdout);
    if (result === undefined) {
      callback(new Error(`${options.benchmarker} produced strange output: ` +
                         stdout, code));
      return;
    }

    callback(null, code, options.benchmarker, result, elapsed);
  });

};