1 var assert = require('assert');
2 var fs = require('fs');
3 var path = require('path');
4 var child_process = require('child_process');
6 var outputFormat = process.env.OUTPUT_FORMAT ||
7 (+process.env.NODE_BENCH_SILENT ? 'silent' : false) ||
10 // verify outputFormat
11 if (['default', 'csv', 'silent'].indexOf(outputFormat) == -1) {
12 throw new Error('OUTPUT_FORMAT set to invalid value');
15 exports.PORT = process.env.PORT || 12346;
17 // If this is the main module, then run the benchmarks
18 if (module === require.main) {
19 var type = process.argv[2];
20 var testFilter = process.argv[3];
22 console.error('usage:\n ./node benchmark/common.js <type> [testFilter]');
26 var dir = path.join(__dirname, type);
27 var tests = fs.readdirSync(dir);
30 var filteredTests = tests.filter(function(item){
31 if (item.lastIndexOf(testFilter) >= 0) {
36 if (filteredTests.length === 0) {
37 console.error('%s is not found in \n %j', testFilter, tests);
40 tests = filteredTests;
47 var result = child_process.spawnSync('wrk', ['-h']);
48 if (result.error && result.error.code === 'ENOENT') {
49 console.error('Couldn\'t locate `wrk` which is needed for running ' +
50 'benchmarks. Check benchmark/README.md for further instructions.');
55 function runBenchmarks() {
56 var test = tests.shift();
60 if (test.match(/^[\._]/))
61 return process.nextTick(runBenchmarks);
63 if (outputFormat == 'default')
64 console.error(type + '/' + test);
66 test = path.resolve(dir, test);
68 var a = (process.execArgv || []).concat(test);
69 var child = child_process.spawn(process.execPath, a, { stdio: 'inherit' });
70 child.on('close', function(code) {
80 exports.createBenchmark = function(fn, options) {
81 return new Benchmark(fn, options);
84 function Benchmark(fn, options) {
86 this.options = options;
87 this.config = parseOpts(options);
88 this._name = require.main.filename.split(/benchmark[\/\\]/).pop();
90 this._started = false;
94 process.nextTick(function() {
99 // benchmark an http server.
100 Benchmark.prototype.http = function(p, args, cb) {
103 var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/;
104 var url = 'http://127.0.0.1:' + exports.PORT + p;
106 args = args.concat(url);
109 var child = child_process.spawn('wrk', args);
111 child.stdout.setEncoding('utf8');
113 child.stdout.on('data', function(chunk) {
117 child.on('close', function(code) {
122 console.error('wrk failed with ' + code);
125 var match = out.match(regexp);
126 var qps = match && +match[1];
128 console.error('%j', out);
129 console.error('wrk produced strange output');
136 Benchmark.prototype._run = function() {
138 return this.fn(this.config);
140 // some options weren't set.
141 // run with all combinations
142 var main = require.main.filename;
145 var options = this.options;
147 var queue = Object.keys(options).reduce(function(set, key) {
148 var vals = options[key];
149 assert(Array.isArray(vals));
151 // match each item in the set with each item in the list
152 var newSet = new Array(set.length * vals.length);
154 set.forEach(function(s) {
155 vals.forEach(function(val) {
156 newSet[j++] = s.concat(key + '=' + val);
162 // output csv heading
163 if (outputFormat == 'csv')
164 console.log('filename,' + Object.keys(options).join(',') + ',result');
166 var node = process.execPath;
169 var argv = queue[i++];
172 argv = process.execArgv.concat(argv);
173 var child = child_process.spawn(node, argv, { stdio: 'inherit' });
174 child.on('close', function(code, signal) {
176 console.error('child process exited with code ' + code);
184 function parseOpts(options) {
185 // verify that there's an option provided for each of the options
186 // if they're not *all* specified, then we return null.
187 var keys = Object.keys(options);
188 var num = keys.length;
190 for (var i = 2; i < process.argv.length; i++) {
191 var match = process.argv[i].match(/^(.+)=(.+)$/);
192 if (!match || !match[1] || !match[2] || !options[match[1]]) {
195 conf[match[1]] = isFinite(match[2]) ? +match[2] : match[2]
199 // still go ahead and set whatever WAS set, if it was.
201 Object.keys(conf).forEach(function(k) {
202 options[k] = [conf[k]];
205 return num === 0 ? conf : null;
208 Benchmark.prototype.start = function() {
210 throw new Error('Called start more than once in a single benchmark');
212 this._started = true;
213 this._start = process.hrtime();
216 Benchmark.prototype.end = function(operations) {
217 var elapsed = process.hrtime(this._start);
220 throw new Error('called end without start');
221 if (typeof operations !== 'number')
222 throw new Error('called end() without specifying operation count');
224 var time = elapsed[0] + elapsed[1]/1e9;
225 var rate = operations/time;
229 Benchmark.prototype.report = function(value) {
230 var heading = this.getHeading();
232 if (outputFormat == 'default')
233 console.log('%s: %s', heading, value.toFixed(5));
234 else if (outputFormat == 'csv')
235 console.log('%s,%s', heading, value.toFixed(5));
240 Benchmark.prototype.getHeading = function() {
241 var conf = this.config;
243 if (outputFormat == 'default') {
244 return this._name + ' ' + Object.keys(conf).map(function(key) {
245 return key + '=' + conf[key];
247 } else if (outputFormat == 'csv') {
248 return this._name + ',' + Object.keys(conf).map(function(key) {