Merge remote-tracking branch 'origin/v0.10'
[platform/upstream/nodejs.git] / benchmark / common.js
1 var assert = require('assert');
2 var path = require('path');
3 var silent = +process.env.NODE_BENCH_SILENT;
4
5 exports.PORT = process.env.PORT || 12346;
6
7 // If this is the main module, then run the benchmarks
8 if (module === require.main) {
9   var type = process.argv[2];
10   if (!type) {
11     console.error('usage:\n ./node benchmark/common.js <type>');
12     process.exit(1);
13   }
14
15   var fs = require('fs');
16   var dir = path.join(__dirname, type);
17   var tests = fs.readdirSync(dir);
18   var spawn = require('child_process').spawn;
19
20   runBenchmarks();
21
22   function runBenchmarks() {
23     var test = tests.shift();
24     if (!test)
25       return;
26
27     if (test.match(/^[\._]/))
28       return process.nextTick(runBenchmarks);
29
30     console.error(type + '/' + test);
31     test = path.resolve(dir, test);
32
33     var child = spawn(process.execPath, [ test ], { stdio: 'inherit' });
34     child.on('close', function(code) {
35       if (code)
36         process.exit(code);
37       else {
38         console.log('');
39         runBenchmarks();
40       }
41     });
42   }
43 }
44
45 exports.createBenchmark = function(fn, options) {
46   return new Benchmark(fn, options);
47 };
48
49 function Benchmark(fn, options) {
50   this.fn = fn;
51   this.options = options;
52   this.config = parseOpts(options);
53   this._name = require.main.filename.split(/benchmark[\/\\]/).pop();
54   this._start = [0,0];
55   this._started = false;
56   var self = this;
57   process.nextTick(function() {
58     self._run();
59   });
60 }
61
62 // benchmark an http server.
63 Benchmark.prototype.http = function(p, args, cb) {
64   var self = this;
65   var wrk = path.resolve(__dirname, '..', 'tools', 'wrk', 'wrk');
66   var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/;
67   var spawn = require('child_process').spawn;
68   var url = 'http://127.0.0.1:' + exports.PORT + p;
69
70   args = args.concat(url);
71
72   var out = '';
73   var child = spawn(wrk, args);
74
75   child.stdout.setEncoding('utf8');
76
77   child.stdout.on('data', function(chunk) {
78     out += chunk;
79   });
80
81   child.on('close', function(code) {
82     if (cb)
83       cb(code);
84
85     if (code) {
86       console.error('wrk failed with ' + code);
87       process.exit(code)
88     }
89     var m = out.match(regexp);
90     var qps = m && +m[1];
91     if (!qps) {
92       console.error('%j', out);
93       console.error('wrk produced strange output');
94       process.exit(1);
95     }
96     self.report(+qps);
97   });
98 };
99
100 Benchmark.prototype._run = function() {
101   if (this.config)
102     return this.fn(this.config);
103
104   // one more more options weren't set.
105   // run with all combinations
106   var main = require.main.filename;
107   var settings = [];
108   var queueLen = 1;
109   var options = this.options;
110
111   var queue = Object.keys(options).reduce(function(set, key) {
112     var vals = options[key];
113     assert(Array.isArray(vals));
114
115     // match each item in the set with each item in the list
116     var newSet = new Array(set.length * vals.length);
117     var j = 0;
118     set.forEach(function(s) {
119       vals.forEach(function(val) {
120         newSet[j++] = s.concat(key + '=' + val);
121       });
122     });
123     return newSet;
124   }, [[main]]);
125
126   var spawn = require('child_process').spawn;
127   var node = process.execPath;
128   var i = 0;
129   function run() {
130     var argv = queue[i++];
131     if (!argv)
132       return;
133     var child = spawn(node, argv, { stdio: 'inherit' });
134     child.on('close', function(code, signal) {
135       if (code)
136         console.error('child process exited with code ' + code);
137       else
138         run();
139     });
140   }
141   run();
142 };
143
144 function parseOpts(options) {
145   // verify that there's an option provided for each of the options
146   // if they're not *all* specified, then we return null.
147   var keys = Object.keys(options);
148   var num = keys.length;
149   var conf = {};
150   for (var i = 2; i < process.argv.length; i++) {
151     var m = process.argv[i].match(/^(.+)=(.+)$/);
152     if (!m || !m[1] || !m[2] || !options[m[1]])
153       return null;
154     else {
155       conf[m[1]] = isFinite(m[2]) ? +m[2] : m[2]
156       num--;
157     }
158   }
159   // still go ahead and set whatever WAS set, if it was.
160   if (num !== 0) {
161     Object.keys(conf).forEach(function(k) {
162       options[k] = [conf[k]];
163     });
164   }
165   return num === 0 ? conf : null;
166 };
167
168 Benchmark.prototype.start = function() {
169   if (this._started)
170     throw new Error('Called start more than once in a single benchmark');
171   this._started = true;
172   this._start = process.hrtime();
173 };
174
175 Benchmark.prototype.end = function(operations) {
176   var elapsed = process.hrtime(this._start);
177   if (!this._started)
178     throw new Error('called end without start');
179   if (typeof operations !== 'number')
180     throw new Error('called end() without specifying operation count');
181   var time = elapsed[0] + elapsed[1]/1e9;
182   var rate = operations/time;
183   this.report(rate);
184 };
185
186 Benchmark.prototype.report = function(value) {
187   var heading = this.getHeading();
188   if (!silent)
189     console.log('%s: %s', heading, value.toPrecision(5));
190   process.exit(0);
191 };
192
193 Benchmark.prototype.getHeading = function() {
194   var conf = this.config;
195   return this._name + ' ' + Object.keys(conf).map(function(key) {
196     return key + '=' + conf[key];
197   }).join(' ');
198 }