benchmark/common.js
var assert = require('assert');
var path = require('path');
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) {
var type = process.argv[2];
if (!type) {
console.error('usage:\n ./node benchmark/common.js <type>');
process.exit(1);
}
var fs = require('fs');
var dir = path.join(__dirname, type);
var tests = fs.readdirSync(dir);
var spawn = require('child_process').spawn;
runBenchmarks();
}
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 = 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) {
var self = this;
var wrk = path.resolve(__dirname, '..', 'tools', 'wrk', 'wrk');
var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/;
var spawn = require('child_process').spawn;
var url = 'http://127.0.0.1:' + exports.PORT + p;
args = args.concat(url);
var out = '';
var child = 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 m = out.match(regexp);
var qps = m && +m[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 spawn = require('child_process').spawn;
var node = process.execPath;
var i = 0;
function run() {
var argv = queue[i++];
if (!argv)
return;
var child = 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 m = process.argv[i].match(/^(.+)=(.+)$/);
if (!m || !m[1] || !m[2] || !options[m[1]])
return null;
else {
conf[m[1]] = isFinite(m[2]) ? +m[2] : m[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.toPrecision(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(' ');
}