|
|
|
var assert = require('assert');
|
|
|
|
var fs = require('fs');
|
|
|
|
var path = require('path');
|
|
|
|
var child_process = require('child_process');
|
|
|
|
|
|
|
|
var silent = +process.env.NODE_BENCH_SILENT;
|
|
|
|
|
|
|
|
exports.PORT = process.env.PORT || 12346;
|
|
|
|
|
|
|
|
// If this is the main module, then run the benchmarks
|
|
|
|
if (module === require.main) {
|
|
|
|
hasWrk();
|
|
|
|
var type = process.argv[2];
|
|
|
|
var testFilter = process.argv[3];
|
|
|
|
if (!type) {
|
|
|
|
console.error('usage:\n ./iojs 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);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
hasWrk();
|
|
|
|
process.nextTick(function() {
|
|
|
|
self._run();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// benchmark an http server.
|
|
|
|
Benchmark.prototype.http = function(p, args, cb) {
|
|
|
|
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);
|
|
|
|
|
|
|
|
// one more more 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) {
|
|
|
|
newSet[j++] = s.concat(key + '=' + val);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return newSet;
|
|
|
|
}, [[main]]);
|
|
|
|
|
|
|
|
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] || !match[2] || !options[match[1]]) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
conf[match[1]] = 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 (!silent)
|
|
|
|
console.log('%s: %s', heading, value.toFixed(5));
|
|
|
|
|
|
|
|
process.exit(0);
|
|
|
|
};
|
|
|
|
|
|
|
|
Benchmark.prototype.getHeading = function() {
|
|
|
|
var conf = this.config;
|
|
|
|
return this._name + ' ' + Object.keys(conf).map(function(key) {
|
|
|
|
return key + '=' + conf[key];
|
|
|
|
}).join(' ');
|
|
|
|
};
|