mirror of
https://github.com/nodejs/node.git
synced 2024-11-30 15:30:56 +01:00
15720fa25a
The benchmark runner spawns new processes for each configuration. The specific configuration is transfered by process.argv. This means that the values have to be parsed. As of right now only numbers and strings are parsed correctly. However other values such as objects where used. This fixes the benchmarks that used non-string/number values and prevents future issues by asserting the type. PR-URL: https://github.com/nodejs/node/pull/5177 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rod Vagg <rod@vagg.org>
260 lines
6.5 KiB
JavaScript
260 lines
6.5 KiB
JavaScript
'use strict';
|
|
var assert = require('assert');
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var child_process = require('child_process');
|
|
|
|
var outputFormat = process.env.OUTPUT_FORMAT ||
|
|
(+process.env.NODE_BENCH_SILENT ? 'silent' : false) ||
|
|
'default';
|
|
|
|
// verify outputFormat
|
|
if (['default', 'csv', 'silent'].indexOf(outputFormat) == -1) {
|
|
throw new Error('OUTPUT_FORMAT set to invalid value');
|
|
}
|
|
|
|
exports.PORT = process.env.PORT || 12346;
|
|
|
|
// If this is the main module, then run the benchmarks
|
|
if (module === require.main) {
|
|
var type = process.argv[2];
|
|
var testFilter = process.argv[3];
|
|
if (!type) {
|
|
console.error('usage:\n ./node benchmark/common.js <type> [testFilter]');
|
|
process.exit(1);
|
|
}
|
|
|
|
var dir = path.join(__dirname, type);
|
|
var tests = fs.readdirSync(dir);
|
|
|
|
if (testFilter) {
|
|
var filteredTests = tests.filter(function(item){
|
|
if (item.lastIndexOf(testFilter) >= 0) {
|
|
return item;
|
|
}
|
|
});
|
|
|
|
if (filteredTests.length === 0) {
|
|
console.error('%s is not found in \n %j', testFilter, tests);
|
|
return;
|
|
}
|
|
tests = filteredTests;
|
|
}
|
|
|
|
runBenchmarks();
|
|
}
|
|
|
|
function hasWrk() {
|
|
var result = child_process.spawnSync('wrk', ['-h']);
|
|
if (result.error && result.error.code === 'ENOENT') {
|
|
console.error('Couldn\'t locate `wrk` which is needed for running ' +
|
|
'benchmarks. Check benchmark/README.md for further instructions.');
|
|
process.exit(-1);
|
|
}
|
|
}
|
|
|
|
function runBenchmarks() {
|
|
var test = tests.shift();
|
|
if (!test)
|
|
return;
|
|
|
|
if (test.match(/^[\._]/))
|
|
return process.nextTick(runBenchmarks);
|
|
|
|
if (outputFormat == 'default')
|
|
console.error(type + '/' + test);
|
|
|
|
test = path.resolve(dir, test);
|
|
|
|
var a = (process.execArgv || []).concat(test);
|
|
var child = child_process.spawn(process.execPath, a, { stdio: 'inherit' });
|
|
child.on('close', function(code) {
|
|
if (code) {
|
|
process.exit(code);
|
|
} else {
|
|
console.log('');
|
|
runBenchmarks();
|
|
}
|
|
});
|
|
}
|
|
|
|
exports.createBenchmark = function(fn, options) {
|
|
return new Benchmark(fn, options);
|
|
};
|
|
|
|
function Benchmark(fn, options) {
|
|
this.fn = fn;
|
|
this.options = options;
|
|
this.config = parseOpts(options);
|
|
this._name = require.main.filename.split(/benchmark[\/\\]/).pop();
|
|
this._start = [0,0];
|
|
this._started = false;
|
|
|
|
var self = this;
|
|
|
|
process.nextTick(function() {
|
|
self._run();
|
|
});
|
|
}
|
|
|
|
// benchmark an http server.
|
|
Benchmark.prototype.http = function(p, args, cb) {
|
|
hasWrk();
|
|
var self = this;
|
|
var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/;
|
|
var url = 'http://127.0.0.1:' + exports.PORT + p;
|
|
|
|
args = args.concat(url);
|
|
|
|
var out = '';
|
|
var child = child_process.spawn('wrk', args);
|
|
|
|
child.stdout.setEncoding('utf8');
|
|
|
|
child.stdout.on('data', function(chunk) {
|
|
out += chunk;
|
|
});
|
|
|
|
child.on('close', function(code) {
|
|
if (cb)
|
|
cb(code);
|
|
|
|
if (code) {
|
|
console.error('wrk failed with ' + code);
|
|
process.exit(code)
|
|
}
|
|
var match = out.match(regexp);
|
|
var qps = match && +match[1];
|
|
if (!qps) {
|
|
console.error('%j', out);
|
|
console.error('wrk produced strange output');
|
|
process.exit(1);
|
|
}
|
|
self.report(+qps);
|
|
});
|
|
};
|
|
|
|
Benchmark.prototype._run = function() {
|
|
if (this.config)
|
|
return this.fn(this.config);
|
|
|
|
// some options weren't set.
|
|
// run with all combinations
|
|
var main = require.main.filename;
|
|
var settings = [];
|
|
var queueLen = 1;
|
|
var options = this.options;
|
|
|
|
var queue = Object.keys(options).reduce(function(set, key) {
|
|
var vals = options[key];
|
|
assert(Array.isArray(vals));
|
|
|
|
// match each item in the set with each item in the list
|
|
var newSet = new Array(set.length * vals.length);
|
|
var j = 0;
|
|
set.forEach(function(s) {
|
|
vals.forEach(function(val) {
|
|
if (typeof val !== 'number' && typeof val !== 'string') {
|
|
throw new TypeError(`configuration "${key}" had type ${typeof val}`);
|
|
}
|
|
|
|
newSet[j++] = s.concat(key + '=' + val);
|
|
});
|
|
});
|
|
return newSet;
|
|
}, [[main]]);
|
|
|
|
// output csv heading
|
|
if (outputFormat == 'csv')
|
|
console.log('filename,' + Object.keys(options).join(',') + ',result');
|
|
|
|
var node = process.execPath;
|
|
var i = 0;
|
|
function run() {
|
|
var argv = queue[i++];
|
|
if (!argv)
|
|
return;
|
|
argv = process.execArgv.concat(argv);
|
|
var child = child_process.spawn(node, argv, { stdio: 'inherit' });
|
|
child.on('close', function(code, signal) {
|
|
if (code)
|
|
console.error('child process exited with code ' + code);
|
|
else
|
|
run();
|
|
});
|
|
}
|
|
run();
|
|
};
|
|
|
|
function parseOpts(options) {
|
|
// verify that there's an option provided for each of the options
|
|
// if they're not *all* specified, then we return null.
|
|
var keys = Object.keys(options);
|
|
var num = keys.length;
|
|
var conf = {};
|
|
for (var i = 2; i < process.argv.length; i++) {
|
|
var match = process.argv[i].match(/^(.+)=(.*)$/);
|
|
if (!match || !match[1] || !options[match[1]]) {
|
|
return null;
|
|
} else {
|
|
conf[match[1]] = (match[2].length && isFinite(match[2])
|
|
? +match[2]
|
|
: match[2]);
|
|
num--;
|
|
}
|
|
}
|
|
// still go ahead and set whatever WAS set, if it was.
|
|
if (num !== 0) {
|
|
Object.keys(conf).forEach(function(k) {
|
|
options[k] = [conf[k]];
|
|
});
|
|
}
|
|
return num === 0 ? conf : null;
|
|
};
|
|
|
|
Benchmark.prototype.start = function() {
|
|
if (this._started)
|
|
throw new Error('Called start more than once in a single benchmark');
|
|
|
|
this._started = true;
|
|
this._start = process.hrtime();
|
|
};
|
|
|
|
Benchmark.prototype.end = function(operations) {
|
|
var elapsed = process.hrtime(this._start);
|
|
|
|
if (!this._started)
|
|
throw new Error('called end without start');
|
|
if (typeof operations !== 'number')
|
|
throw new Error('called end() without specifying operation count');
|
|
|
|
var time = elapsed[0] + elapsed[1]/1e9;
|
|
var rate = operations/time;
|
|
this.report(rate);
|
|
};
|
|
|
|
Benchmark.prototype.report = function(value) {
|
|
var heading = this.getHeading();
|
|
|
|
if (outputFormat == 'default')
|
|
console.log('%s: %s', heading, value.toFixed(5));
|
|
else if (outputFormat == 'csv')
|
|
console.log('%s,%s', heading, value.toFixed(5));
|
|
|
|
process.exit(0);
|
|
};
|
|
|
|
Benchmark.prototype.getHeading = function() {
|
|
var conf = this.config;
|
|
|
|
if (outputFormat == 'default') {
|
|
return this._name + ' ' + Object.keys(conf).map(function(key) {
|
|
return key + '=' + conf[key];
|
|
}).join(' ');
|
|
} else if (outputFormat == 'csv') {
|
|
return this._name + ',' + Object.keys(conf).map(function(key) {
|
|
return conf[key];
|
|
}).join(',');
|
|
}
|
|
};
|