From 00e1962495f9d42b7bf403952566e420acdf48d0 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 7 Feb 2013 19:10:58 -0800 Subject: [PATCH 01/45] bench: Add common script --- benchmark/common.js | 110 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 benchmark/common.js diff --git a/benchmark/common.js b/benchmark/common.js new file mode 100644 index 0000000000..2ceb713191 --- /dev/null +++ b/benchmark/common.js @@ -0,0 +1,110 @@ +var assert = require('assert'); +var path = require('path'); + +exports.PORT = process.env.PORT || 12346; + +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 = path.basename(require.main.filename, '.js'); + this._start = [0,0]; + this._started = false; + var self = this; + process.nextTick(function() { + self._run(); + }); +} + +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--; + } + } + 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; + var heading = this.getHeading(); + console.log('%s: %s', heading, rate.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('_'); +} From dd069a253940812282410b221276876b8968b3f1 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:26:43 -0800 Subject: [PATCH 02/45] bench: Add ab() method to common for http testing --- benchmark/common.js | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/benchmark/common.js b/benchmark/common.js index 2ceb713191..04cf382a0c 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -20,6 +20,42 @@ function Benchmark(fn, options) { }); } +// run ab against a server. +Benchmark.prototype.ab = function(path, args, cb) { + var url = 'http://127.0.0.1:' + exports.PORT + path; + args.push(url); + + var self = this; + var out = ''; + var spawn = require('child_process').spawn; + // console.error('ab %s', args.join(' ')); + var child = spawn('ab', 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('ab failed with ' + code); + process.exit(code) + } + var m = out.match(/Requests per second: +([0-9\.]+)/); + var qps = m && +m[1]; + if (!qps) { + process.stderr.write(out + '\n'); + console.error('ab produced strange output'); + process.exit(1); + } + self.report(+qps); + }); +}; + Benchmark.prototype._run = function() { if (this.config) return this.fn(this.config); @@ -97,8 +133,12 @@ Benchmark.prototype.end = function(operations) { 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(); - console.log('%s: %s', heading, rate.toPrecision(5)); + console.log('%s: %s', heading, value.toPrecision(5)); process.exit(0); }; From 37077de83dbb97f1b51839fb0ebff608aa9ea3c8 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:21:48 -0800 Subject: [PATCH 03/45] bench: add runner --- benchmark/common.js | 59 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/benchmark/common.js b/benchmark/common.js index 04cf382a0c..289f17d128 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -3,6 +3,45 @@ var path = require('path'); 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 '); + process.exit(1); + } + + var path = require('path'); + 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 child = spawn(process.execPath, [ test ], { 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); }; @@ -11,7 +50,7 @@ function Benchmark(fn, options) { this.fn = fn; this.options = options; this.config = parseOpts(options); - this._name = path.basename(require.main.filename, '.js'); + this._name = require.main.filename.split(/benchmark[\/\\]/).pop(); this._start = [0,0]; this._started = false; var self = this; @@ -76,7 +115,7 @@ Benchmark.prototype._run = function() { var j = 0; set.forEach(function(s) { vals.forEach(function(val) { - newSet[j++] = s.concat('--' + key + '=' + val); + newSet[j++] = s.concat(key + '=' + val); }); }); return newSet; @@ -101,13 +140,13 @@ Benchmark.prototype._run = function() { }; function parseOpts(options) { - // verify that there's an --option provided for each of the 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(/^--(.+)=(.+)$/); + var m = process.argv[i].match(/^(.+)=(.+)$/); if (!m || !m[1] || !m[2] || !options[m[1]]) return null; else { @@ -115,6 +154,12 @@ function parseOpts(options) { 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; }; @@ -144,7 +189,7 @@ Benchmark.prototype.report = function(value) { Benchmark.prototype.getHeading = function() { var conf = this.config; - return this._name + '_' + Object.keys(conf).map(function(key) { - return key + '_' + conf[key]; - }).join('_'); + return this._name + ' ' + Object.keys(conf).map(function(key) { + return key + '=' + conf[key]; + }).join(' '); } From aa2edd4b89c51836424b211db756ba907230b58b Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:28:48 -0800 Subject: [PATCH 04/45] bench: A compare script for analyzing benchmarks --- benchmark/compare.js | 116 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 benchmark/compare.js diff --git a/benchmark/compare.js b/benchmark/compare.js new file mode 100644 index 0000000000..05733a1e37 --- /dev/null +++ b/benchmark/compare.js @@ -0,0 +1,116 @@ +var usage = 'node benchmark/compare.js '; + +var show = 'both'; +var nodes = []; +for (var i = 2; i < process.argv.length; i++) { + var arg = process.argv[i]; + switch (arg) { + case '--red': case '-r': + show = show === 'green' ? 'both' : 'red'; + break; + case '--green': case '-g': + show = show === 'red' ? 'both' : 'green'; + break; + default: + nodes.push(arg); + break; + } +} + +var runBench = process.env.NODE_BENCH || 'bench'; + +if (nodes.length !== 2) + return console.error('usage:\n %s', usage); + +var spawn = require('child_process').spawn; +var results = {}; +var n = 2; + +run(); + +function run() { + if (n === 0) + return compare(); + + n--; + + var node = nodes[n]; + console.error('running %s', node); + var env = {}; + for (var i in process.env) + env[i] = process.env[i]; + env.NODE = node; + var child = spawn('make', [ runBench ], { env: env }); + + var out = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', function(c) { + out += c; + }); + + child.stderr.pipe(process.stderr); + + child.on('close', function(code) { + if (code) { + console.error('%s exited with code=%d', node, code); + process.exit(code); + } else { + out.trim().split(/\r?\n/).forEach(function(line) { + line = line.trim(); + if (!line) + return; + + var s = line.split(':'); + var num = +s.pop(); + if (!num && num !== 0) + return + + line = s.join(':'); + var res = results[line] = results[line] || {}; + res[node] = num; + }); + + run(); + } + }); +} + +function compare() { + // each result is an object with {"foo.js arg=bar":12345,...} + // compare each thing, and show which node did the best. + // node[0] is shown in green, node[1] shown in red. + var green = '\033[1;32m'; + var red = '\033[1;31m'; + var reset = '\033[m'; + var maxLen = -Infinity; + var util = require('util'); + Object.keys(results).map(function(bench) { + var res = results[bench]; + var n0 = res[nodes[0]]; + var n1 = res[nodes[1]]; + + var pct = ((n0 - n1) / ((n0 + n1) / 2) * 100).toFixed(2); + + var g = n0 > n1 ? green : ''; + var r = n0 > n1 ? '' : red; + var c = r || g; + + if (show === 'green' && !g || show === 'red' && !r) + return; + + var r0 = util.format('%s%s: %d%s', g, nodes[0], n0, reset); + var r1 = util.format('%s%s: %d%s', r, nodes[1], n1, reset); + var pct = c + pct + '%' + reset; + var l = util.format('%s: %s %s', bench, r0, r1); + maxLen = Math.max(l.length + pct.length, maxLen); + return [l, pct]; + }).filter(function(l) { + return l; + }).forEach(function(line) { + var l = line[0]; + var pct = line[1]; + var dotLen = maxLen - l.length - pct.length + 2; + var dots = ' ' + new Array(Math.max(0, dotLen)).join('.') + ' '; + console.log(l + dots + pct); + }); +} From baea73ccda8a54d93f0de4f60ffcee568dac8c01 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 7 Feb 2013 19:10:48 -0800 Subject: [PATCH 05/45] bench: Move net-pipe into benchmark/net --- benchmark/{ => net}/net-pipe.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename benchmark/{ => net}/net-pipe.js (100%) diff --git a/benchmark/net-pipe.js b/benchmark/net/net-pipe.js similarity index 100% rename from benchmark/net-pipe.js rename to benchmark/net/net-pipe.js From e82f97401f9d7c54155b52b310f1893e45205c46 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 7 Feb 2013 19:13:26 -0800 Subject: [PATCH 06/45] bench: net benchmarks using common script --- benchmark/net/net-c2s.js | 112 +++++++++++++++++++++++++ benchmark/net/net-pipe.js | 131 +++++++++++++++--------------- benchmark/net/net-s2c.js | 112 +++++++++++++++++++++++++ benchmark/net/tcp-raw-c2s.js | 136 +++++++++++++++++++++++++++++++ benchmark/net/tcp-raw-pipe.js | 149 ++++++++++++++++++++++++++++++++++ benchmark/net/tcp-raw-s2c.js | 136 +++++++++++++++++++++++++++++++ 6 files changed, 711 insertions(+), 65 deletions(-) create mode 100644 benchmark/net/net-c2s.js create mode 100644 benchmark/net/net-s2c.js create mode 100644 benchmark/net/tcp-raw-c2s.js create mode 100644 benchmark/net/tcp-raw-pipe.js create mode 100644 benchmark/net/tcp-raw-s2c.js diff --git a/benchmark/net/net-c2s.js b/benchmark/net/net-c2s.js new file mode 100644 index 0000000000..0467643f3a --- /dev/null +++ b/benchmark/net/net-c2s.js @@ -0,0 +1,112 @@ +// test the speed of .pipe() with sockets + +var common = require('../common.js'); +var PORT = common.PORT; + +var bench = common.createBenchmark(main, { + len: [102400, 1024 * 1024 * 16], + type: ['utf', 'asc', 'buf'], + dur: [1, 3], +}); + +var dur; +var len; +var type; +var chunk; +var encoding; + +function main(conf) { + dur = +conf.dur; + len = +conf.len; + type = conf.type; + + switch (type) { + case 'buf': + chunk = new Buffer(len); + chunk.fill('x'); + break; + case 'utf': + encoding = 'utf8'; + chunk = new Array(len / 2 + 1).join('ü'); + break; + case 'asc': + encoding = 'ascii'; + chunk = new Array(len + 1).join('x'); + break; + default: + throw new Error('invalid type: ' + type); + break; + } + + server(); +} + +var net = require('net'); + +function Writer() { + this.received = 0; + this.writable = true; +} + +Writer.prototype.write = function(chunk, encoding, cb) { + this.received += chunk.length; + + if (typeof encoding === 'function') + encoding(); + else if (typeof cb === 'function') + cb(); + + return true; +}; + +// doesn't matter, never emits anything. +Writer.prototype.on = function() {}; +Writer.prototype.once = function() {}; +Writer.prototype.emit = function() {}; + + +function Reader() { + this.flow = this.flow.bind(this); + this.readable = true; +} + +Reader.prototype.pipe = function(dest) { + this.dest = dest; + this.flow(); + return dest; +}; + +Reader.prototype.flow = function() { + var dest = this.dest; + var res = dest.write(chunk, encoding); + if (!res) + dest.once('drain', this.flow); + else + process.nextTick(this.flow); +}; + + +function server() { + var reader = new Reader(); + var writer = new Writer(); + + // the actual benchmark. + var server = net.createServer(function(socket) { + socket.pipe(writer); + }); + + server.listen(PORT, function() { + var socket = net.connect(PORT); + socket.on('connect', function() { + bench.start(); + + reader.pipe(socket); + + setTimeout(function() { + var bytes = writer.received; + var gbits = (bytes * 8) / (1024 * 1024 * 1024); + bench.end(gbits); + }, dur * 1000); + }); + }); +} diff --git a/benchmark/net/net-pipe.js b/benchmark/net/net-pipe.js index e981cc3b4e..d3908e8439 100644 --- a/benchmark/net/net-pipe.js +++ b/benchmark/net/net-pipe.js @@ -1,21 +1,54 @@ // test the speed of .pipe() with sockets +var common = require('../common.js'); +var PORT = common.PORT; + +var bench = common.createBenchmark(main, { + len: [102400, 1024 * 1024 * 16], + type: ['utf', 'asc', 'buf'], + dur: [1, 3], +}); + +var dur; +var len; +var type; +var chunk; +var encoding; + +function main(conf) { + dur = +conf.dur; + len = +conf.len; + type = conf.type; + + switch (type) { + case 'buf': + chunk = new Buffer(len); + chunk.fill('x'); + break; + case 'utf': + encoding = 'utf8'; + chunk = new Array(len / 2 + 1).join('ü'); + break; + case 'asc': + encoding = 'ascii'; + chunk = new Array(len + 1).join('x'); + break; + default: + throw new Error('invalid type: ' + type); + break; + } + + server(); +} + var net = require('net'); -var N = parseInt(process.argv[2]) || 100; -var start; function Writer() { - this.start = null; this.received = 0; this.writable = true; - this.printStats = this.printStats.bind(this); - this.interval = setInterval(this.printStats, 1000); } Writer.prototype.write = function(chunk, encoding, cb) { - if (!this.start) - this.start = process.hrtime(); - this.received += chunk.length; if (typeof encoding === 'function') @@ -31,49 +64,6 @@ Writer.prototype.on = function() {}; Writer.prototype.once = function() {}; Writer.prototype.emit = function() {}; -var rates = []; -var statCounter = 0; -Writer.prototype.printStats = function() { - if (!this.start || !this.received) - return; - var elapsed = process.hrtime(this.start); - elapsed = elapsed[0] * 1E9 + elapsed[1]; - var bits = this.received * 8; - var gbits = bits / (1024 * 1024 * 1024); - var rate = gbits / elapsed * 1E9; - rates.push(rate); - console.log('%s Gbits/sec (%d bits / %d ns)', rate.toFixed(4), bits, elapsed); - - // reset to keep getting instant time. - this.start = process.hrtime(); - this.received = 0; - - if (++statCounter === N) { - report(); - process.exit(0); - } -}; - -function report() { - rates.sort(); - var min = rates[0]; - var max = rates[rates.length - 1]; - var median = rates[rates.length >> 1]; - var avg = 0; - rates.forEach(function(rate) { avg += rate }); - avg /= rates.length; - console.error('min:%s avg:%s max:%s median:%s', - min.toFixed(2), - avg.toFixed(2), - max.toFixed(2), - median.toFixed(2)); -} - -var len = process.env.LENGTH || 16 * 1024 * 1024; -var chunk = new Buffer(len); -for (var i = 0; i < len; i++) { - chunk[i] = i % 256; -} function Reader() { this.flow = this.flow.bind(this); @@ -88,7 +78,7 @@ Reader.prototype.pipe = function(dest) { Reader.prototype.flow = function() { var dest = this.dest; - var res = dest.write(chunk); + var res = dest.write(chunk, encoding); if (!res) dest.once('drain', this.flow); else @@ -96,19 +86,30 @@ Reader.prototype.flow = function() { }; -var reader = new Reader(); -var writer = new Writer(); +function server() { + var reader = new Reader(); + var writer = new Writer(); -// the actual benchmark. -var server = net.createServer(function(socket) { - socket.pipe(socket); -}); - -server.listen(1337, function() { - var socket = net.connect(1337); - socket.on('connect', function() { - reader.pipe(socket); - socket.pipe(writer); + // the actual benchmark. + var server = net.createServer(function(socket) { + socket.pipe(socket); }); -}); + server.listen(PORT, function() { + var socket = net.connect(PORT); + socket.on('connect', function() { + bench.start(); + + reader.pipe(socket); + socket.pipe(writer); + + setTimeout(function() { + // multiply by 2 since we're sending it first one way + // then then back again. + var bytes = writer.received * 2; + var gbits = (bytes * 8) / (1024 * 1024 * 1024); + bench.end(gbits); + }, dur * 1000); + }); + }); +} diff --git a/benchmark/net/net-s2c.js b/benchmark/net/net-s2c.js new file mode 100644 index 0000000000..96cb67d242 --- /dev/null +++ b/benchmark/net/net-s2c.js @@ -0,0 +1,112 @@ +// test the speed of .pipe() with sockets + +var common = require('../common.js'); +var PORT = common.PORT; + +var bench = common.createBenchmark(main, { + len: [102400, 1024 * 1024 * 16], + type: ['utf', 'asc', 'buf'], + dur: [1, 3], +}); + +var dur; +var len; +var type; +var chunk; +var encoding; + +function main(conf) { + dur = +conf.dur; + len = +conf.len; + type = conf.type; + + switch (type) { + case 'buf': + chunk = new Buffer(len); + chunk.fill('x'); + break; + case 'utf': + encoding = 'utf8'; + chunk = new Array(len / 2 + 1).join('ü'); + break; + case 'asc': + encoding = 'ascii'; + chunk = new Array(len + 1).join('x'); + break; + default: + throw new Error('invalid type: ' + type); + break; + } + + server(); +} + +var net = require('net'); + +function Writer() { + this.received = 0; + this.writable = true; +} + +Writer.prototype.write = function(chunk, encoding, cb) { + this.received += chunk.length; + + if (typeof encoding === 'function') + encoding(); + else if (typeof cb === 'function') + cb(); + + return true; +}; + +// doesn't matter, never emits anything. +Writer.prototype.on = function() {}; +Writer.prototype.once = function() {}; +Writer.prototype.emit = function() {}; + + +function Reader() { + this.flow = this.flow.bind(this); + this.readable = true; +} + +Reader.prototype.pipe = function(dest) { + this.dest = dest; + this.flow(); + return dest; +}; + +Reader.prototype.flow = function() { + var dest = this.dest; + var res = dest.write(chunk, encoding); + if (!res) + dest.once('drain', this.flow); + else + process.nextTick(this.flow); +}; + + +function server() { + var reader = new Reader(); + var writer = new Writer(); + + // the actual benchmark. + var server = net.createServer(function(socket) { + reader.pipe(socket); + }); + + server.listen(PORT, function() { + var socket = net.connect(PORT); + socket.on('connect', function() { + bench.start(); + + socket.pipe(writer); + + setTimeout(function() { + var bytes = writer.received; + var gbits = (bytes * 8) / (1024 * 1024 * 1024); + bench.end(gbits); + }, dur * 1000); + }); + }); +} diff --git a/benchmark/net/tcp-raw-c2s.js b/benchmark/net/tcp-raw-c2s.js new file mode 100644 index 0000000000..2664e25fe0 --- /dev/null +++ b/benchmark/net/tcp-raw-c2s.js @@ -0,0 +1,136 @@ +// In this benchmark, we connect a client to the server, and write +// as many bytes as we can in the specified time (default = 10s) + +var common = require('../common.js'); + +// if there are --dur=N and --len=N args, then +// run the function with those settings. +// if not, then queue up a bunch of child processes. +var bench = common.createBenchmark(main, { + len: [102400, 1024 * 1024 * 16], + type: ['utf', 'asc', 'buf'], + dur: [1, 3], +}); + +var TCP = process.binding('tcp_wrap').TCP; +var PORT = common.PORT; + +var dur; +var len; +var type; + +function main(conf) { + dur = +conf.dur; + len = +conf.len; + type = conf.type; + server(); +} + + +function fail(syscall) { + var e = new Error(syscall + ' ' + errno); + e.errno = e.code = errno; + e.syscall = syscall; + throw e; +} + +function server() { + var serverHandle = new TCP(); + var r = serverHandle.bind('127.0.0.1', PORT); + if (r) + fail('bind'); + + var r = serverHandle.listen(511); + if (r) + fail('listen'); + + serverHandle.onconnection = function(clientHandle) { + if (!clientHandle) + fail('connect'); + + // the meat of the benchmark is right here: + bench.start(); + var bytes = 0; + + setTimeout(function() { + // report in Gb/sec + bench.end((bytes * 8) / (1024 * 1024 * 1024)); + }, dur * 1000); + + clientHandle.onread = function(buffer, offset, length) { + // we're not expecting to ever get an EOF from the client. + // just lots of data forever. + if (!buffer) + fail('read'); + + // don't slice the buffer. the point of this is to isolate, not + // simulate real traffic. + // var chunk = buffer.slice(offset, offset + length); + bytes += length; + }; + + clientHandle.readStart(); + }; + + client(); +} + +function client() { + var chunk; + switch (type) { + case 'buf': + chunk = new Buffer(len); + chunk.fill('x'); + break; + case 'utf': + chunk = new Array(len / 2 + 1).join('ü'); + break; + case 'asc': + chunk = new Array(len + 1).join('x'); + break; + default: + throw new Error('invalid type: ' + type); + break; + } + + var clientHandle = new TCP(); + var connectReq = clientHandle.connect('127.0.0.1', PORT); + + if (!connectReq) + fail('connect'); + + clientHandle.readStart(); + + connectReq.oncomplete = function() { + while (clientHandle.writeQueueSize === 0) + write(); + }; + + function write() { + var writeReq + switch (type) { + case 'buf': + writeReq = clientHandle.writeBuffer(chunk); + break; + case 'utf': + writeReq = clientHandle.writeUtf8String(chunk); + break; + case 'asc': + writeReq = clientHandle.writeAsciiString(chunk); + break; + } + + if (!writeReq) + fail('write'); + + writeReq.oncomplete = afterWrite; + } + + function afterWrite(status, handle, req) { + if (status) + fail('write'); + + while (clientHandle.writeQueueSize === 0) + write(); + } +} diff --git a/benchmark/net/tcp-raw-pipe.js b/benchmark/net/tcp-raw-pipe.js new file mode 100644 index 0000000000..d55c2c5d7e --- /dev/null +++ b/benchmark/net/tcp-raw-pipe.js @@ -0,0 +1,149 @@ +// In this benchmark, we connect a client to the server, and write +// as many bytes as we can in the specified time (default = 10s) + +var common = require('../common.js'); + +// if there are --dur=N and --len=N args, then +// run the function with those settings. +// if not, then queue up a bunch of child processes. +var bench = common.createBenchmark(main, { + len: [102400, 1024 * 1024 * 16], + type: ['utf', 'asc', 'buf'], + dur: [1, 3], +}); + +var TCP = process.binding('tcp_wrap').TCP; +var PORT = common.PORT; + +var dur; +var len; +var type; + +function main(conf) { + dur = +conf.dur; + len = +conf.len; + type = conf.type; + server(); +} + + +function fail(syscall) { + var e = new Error(syscall + ' ' + errno); + e.errno = e.code = errno; + e.syscall = syscall; + throw e; +} + +function server() { + var serverHandle = new TCP(); + var r = serverHandle.bind('127.0.0.1', PORT); + if (r) + fail('bind'); + + var r = serverHandle.listen(511); + if (r) + fail('listen'); + + serverHandle.onconnection = function(clientHandle) { + if (!clientHandle) + fail('connect'); + + clientHandle.onread = function(buffer, offset, length) { + // we're not expecting to ever get an EOF from the client. + // just lots of data forever. + if (!buffer) + fail('read'); + + var chunk = buffer.slice(offset, offset + length); + var writeReq = clientHandle.writeBuffer(chunk); + + if (!writeReq) + fail('write'); + + writeReq.oncomplete = function(status, handle, req) { + if (status) + fail('write'); + }; + }; + + clientHandle.readStart(); + }; + + client(); +} + +function client() { + var chunk; + switch (type) { + case 'buf': + chunk = new Buffer(len); + chunk.fill('x'); + break; + case 'utf': + chunk = new Array(len / 2 + 1).join('ü'); + break; + case 'asc': + chunk = new Array(len + 1).join('x'); + break; + default: + throw new Error('invalid type: ' + type); + break; + } + + var clientHandle = new TCP(); + var connectReq = clientHandle.connect('127.0.0.1', PORT); + var bytes = 0; + + if (!connectReq) + fail('connect'); + + clientHandle.readStart(); + + clientHandle.onread = function(buffer, start, length) { + if (!buffer) + fail('read'); + + bytes += length; + }; + + connectReq.oncomplete = function() { + bench.start(); + + setTimeout(function() { + // multiply by 2 since we're sending it first one way + // then then back again. + bench.end(2 * (bytes * 8) / (1024 * 1024 * 1024)); + }, dur * 1000); + + while (clientHandle.writeQueueSize === 0) + write(); + }; + + function write() { + var writeReq + switch (type) { + case 'buf': + writeReq = clientHandle.writeBuffer(chunk); + break; + case 'utf': + writeReq = clientHandle.writeUtf8String(chunk); + break; + case 'asc': + writeReq = clientHandle.writeAsciiString(chunk); + break; + } + + if (!writeReq) + fail('write'); + + writeReq.oncomplete = afterWrite; + } + + function afterWrite(status, handle, req) { + if (status) + fail('write'); + + while (clientHandle.writeQueueSize === 0) + write(); + } +} diff --git a/benchmark/net/tcp-raw-s2c.js b/benchmark/net/tcp-raw-s2c.js new file mode 100644 index 0000000000..94a90f29de --- /dev/null +++ b/benchmark/net/tcp-raw-s2c.js @@ -0,0 +1,136 @@ +// In this benchmark, we connect a client to the server, and write +// as many bytes as we can in the specified time (default = 10s) + +var common = require('../common.js'); + +// if there are dur=N and len=N args, then +// run the function with those settings. +// if not, then queue up a bunch of child processes. +var bench = common.createBenchmark(main, { + len: [102400, 1024 * 1024 * 16], + type: ['utf', 'asc', 'buf'], + dur: [1, 3], +}); + +var TCP = process.binding('tcp_wrap').TCP; +var PORT = common.PORT; + +var dur; +var len; +var type; + +function main(conf) { + dur = +conf.dur; + len = +conf.len; + type = conf.type; + server(); +} + + +function fail(syscall) { + var e = new Error(syscall + ' ' + errno); + e.errno = e.code = errno; + e.syscall = syscall; + throw e; +} + +function server() { + var serverHandle = new TCP(); + var r = serverHandle.bind('127.0.0.1', PORT); + if (r) + fail('bind'); + + var r = serverHandle.listen(511); + if (r) + fail('listen'); + + serverHandle.onconnection = function(clientHandle) { + if (!clientHandle) + fail('connect'); + + var chunk; + switch (type) { + case 'buf': + chunk = new Buffer(len); + chunk.fill('x'); + break; + case 'utf': + chunk = new Array(len / 2 + 1).join('ü'); + break; + case 'asc': + chunk = new Array(len + 1).join('x'); + break; + default: + throw new Error('invalid type: ' + type); + break; + } + + clientHandle.readStart(); + + while (clientHandle.writeQueueSize === 0) + write(); + + function write() { + var writeReq + switch (type) { + case 'buf': + writeReq = clientHandle.writeBuffer(chunk); + break; + case 'utf': + writeReq = clientHandle.writeUtf8String(chunk); + break; + case 'asc': + writeReq = clientHandle.writeAsciiString(chunk); + break; + } + + if (!writeReq) + fail('write'); + + writeReq.oncomplete = afterWrite; + } + + function afterWrite(status, handle, req) { + if (status) + fail('write'); + + while (clientHandle.writeQueueSize === 0) + write(); + } + }; + + client(); +} + +function client() { + var clientHandle = new TCP(); + var connectReq = clientHandle.connect('127.0.0.1', PORT); + + if (!connectReq) + fail('connect'); + + connectReq.oncomplete = function() { + var bytes = 0; + clientHandle.onread = function(buffer, offset, length) { + // we're not expecting to ever get an EOF from the client. + // just lots of data forever. + if (!buffer) + fail('read'); + + // don't slice the buffer. the point of this is to isolate, not + // simulate real traffic. + // var chunk = buffer.slice(offset, offset + length); + bytes += length; + }; + + clientHandle.readStart(); + + // the meat of the benchmark is right here: + bench.start(); + + setTimeout(function() { + // report in Gb/sec + bench.end((bytes * 8) / (1024 * 1024 * 1024)); + }, dur * 1000); + }; +} From 051c1317f99ede15a831d1f542bd89a47ab77397 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 22:08:25 -0800 Subject: [PATCH 07/45] bench: Remove throughput (covered by benchmark/net) --- benchmark/throughput-child.js | 24 ------------------------ benchmark/throughput.js | 21 --------------------- 2 files changed, 45 deletions(-) delete mode 100644 benchmark/throughput-child.js delete mode 100644 benchmark/throughput.js diff --git a/benchmark/throughput-child.js b/benchmark/throughput-child.js deleted file mode 100644 index 260b2807de..0000000000 --- a/benchmark/throughput-child.js +++ /dev/null @@ -1,24 +0,0 @@ -var net = require('net'); -var received = 0; -var start = process.hrtime(); -var socket = net.connect(8000); - -socket.on('data', function(d) { - received += d.length; -}); - -var interval = setInterval(function() { - // After 1 gigabyte shutdown. - if (received > 10 * 1024 * 1024 * 1024) { - socket.destroy(); - clearInterval(interval); - process.exit(0); - } else { - // Otherwise print some stats. - var elapsed = process.hrtime(start); - var sec = elapsed[0] + elapsed[1]/1E9; - var gigabytes = received / (1024 * 1024 * 1024); - var gigabits = gigabytes * 8.0; - console.log((gigabits / sec) + " gbit/sec") - } -}, 1000); diff --git a/benchmark/throughput.js b/benchmark/throughput.js deleted file mode 100644 index a1ff1b653e..0000000000 --- a/benchmark/throughput.js +++ /dev/null @@ -1,21 +0,0 @@ -var fork = require('child_process').fork; -var net = require('net'); -var buffer = new Buffer(1024 * 1024); - -function write(socket) { - if (!socket.writable) return; - - socket.write(buffer, function() { - write(socket); - }); -} - -var server = net.createServer(function(socket) { - server.close(); - write(socket); -}); - -server.listen(8000, function() { - fork(__dirname + '/throughput-child.js'); -}); - From d5d04a51d641cca321e8470a64e84dd8b7a97e4a Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 17:57:49 -0800 Subject: [PATCH 08/45] bench: Remove client_latency No one actually knows what this is supposed to be doing, anyway. It's not a good benchmark. --- benchmark/client_latency.js | 80 ------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 benchmark/client_latency.js diff --git a/benchmark/client_latency.js b/benchmark/client_latency.js deleted file mode 100644 index 09630a0da8..0000000000 --- a/benchmark/client_latency.js +++ /dev/null @@ -1,80 +0,0 @@ -// first start node http_simple.js -var http = require('http'); - -var latency = []; - -var numRequests = parseInt(process.argv[2], 10) || 100; -var maxSockets = parseInt(process.argv[3], 10) || 100; -var runs = parseInt(process.argv[4], 10) || 100; -var prefix = process.argv[5] || ''; -if (prefix) prefix += '_'; -var r = 0; - -var port = parseInt(process.env.PORT, 10) || 8000; -var host = process.env.HOST || '127.0.0.1'; - -http.globalAgent.maxSockets = maxSockets; - -run(); - -function run() { - if (r++ === runs) { - return finish(); - } - - // make numRequests in parallel - // retain the order in which they are *made*. This requires trapping - // each one in a closure, since otherwise, we'll of course end - // up mostly sorting them in ascending order, since the cb from a - // fast request will almost always be called before the cb from a - // slow one. - var c = numRequests; - var lat = []; - latency.push(lat); - for (var i = 0; i < numRequests; i++) (function (i) { - makeRequest(function(l) { - lat[i] = l; - c--; - if (c === 0) run(); - }); - })(i); -} - -function makeRequest(cb) { - var opts = { host: host, - port: port, - uri: 'http://'+host+':'+port+'/bytes/12', - forever: true, - path: '/bytes/12' }; - var pre = Date.now(); - var req = http.get(opts, function(res) { - return cb(Date.now() - pre); - }); -} - -function finish() { - var data = []; - latency.forEach(function(run, i) { - run.forEach(function(l, j) { - data[j] = data[j] || []; - data[j][i] = l; - }); - }); - - data = data.map(function (l, i) { - return l.join('\t') - }).join('\n') + '\n'; - - var fname = prefix + - 'client_latency_' + - numRequests + '_' + - maxSockets + '_' + - runs + '.tab'; - var path = require('path'); - fname = path.resolve(__dirname, '..', 'out', fname); - fname = path.relative(process.cwd(), fname); - require('fs').writeFile(fname, data, function(er) { - if (er) throw er; - console.log('written: %s', fname); - }); -} From 536ce446898f3b788d7f0134de8397db55161b07 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:43:22 -0800 Subject: [PATCH 09/45] bench: http benchmarks Also: make http_simple less chatty --- benchmark/http/cluster.js | 37 +++++++++++++++++++++++++++++++++++ benchmark/http/http_simple.js | 29 +++++++++++++++++++++++++++ benchmark/http_simple.js | 14 +++---------- 3 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 benchmark/http/cluster.js create mode 100644 benchmark/http/http_simple.js diff --git a/benchmark/http/cluster.js b/benchmark/http/cluster.js new file mode 100644 index 0000000000..a8c7abe689 --- /dev/null +++ b/benchmark/http/cluster.js @@ -0,0 +1,37 @@ +var common = require('../common.js'); +var PORT = common.PORT; + +var cluster = require('cluster'); +if (cluster.isMaster) { + var bench = common.createBenchmark(main, { + // unicode confuses ab on os x. + type: ['bytes', 'buffer'], + length: [4, 1024, 102400], + c: [50, 150] + }); +} else { + require('../http_simple.js'); +} + +function main(conf) { + process.env.PORT = PORT; + var workers = 0; + var w1 = cluster.fork(); + var w2 = cluster.fork(); + + cluster.on('listening', function() { + workers++; + if (workers < 2) + return; + + setTimeout(function() { + var path = '/' + conf.type + '/' + conf.length; + var args = ['-r', '-t', 5, '-c', conf.c, '-k']; + + bench.ab(path, args, function() { + w1.destroy(); + w2.destroy(); + }); + }, 2000); + }); +} diff --git a/benchmark/http/http_simple.js b/benchmark/http/http_simple.js new file mode 100644 index 0000000000..2cef41f46f --- /dev/null +++ b/benchmark/http/http_simple.js @@ -0,0 +1,29 @@ +var common = require('../common.js'); +var PORT = common.PORT; + +var bench = common.createBenchmark(main, { + // unicode confuses ab on os x. + type: ['bytes', 'buffer'], + length: [4, 1024, 102400], + c: [50, 150] +}); + +function main(conf) { + process.env.PORT = PORT; + var spawn = require('child_process').spawn; + var simple = require('path').resolve(__dirname, '../http_simple.js'); + var server = spawn(process.execPath, [simple]); + setTimeout(function() { + var path = '/' + conf.type + '/' + conf.length; //+ '/' + conf.chunks; + var args = ['-r', '-t', 5]; + + if (+conf.c !== 1) + args.push('-c', conf.c); + + args.push('-k'); + + bench.ab(path, args, function() { + server.kill(); + }); + }, 2000); +} diff --git a/benchmark/http_simple.js b/benchmark/http_simple.js index 236b046e92..54500b49b5 100644 --- a/benchmark/http_simple.js +++ b/benchmark/http_simple.js @@ -4,8 +4,6 @@ var path = require('path'), var port = parseInt(process.env.PORT || 8000); -console.log('pid ' + process.pid); - var fixed = makeString(20 * 1024, 'C'), storedBytes = {}, storedBuffer = {}, @@ -18,7 +16,7 @@ if (useDomains) { var domain = require('domain'); var gdom = domain.create(); gdom.on('error', function(er) { - console.log('Error on global domain', er); + console.error('Error on global domain', er); throw er; }); gdom.enter(); @@ -43,7 +41,6 @@ var server = http.createServer(function (req, res) { if (n <= 0) throw new Error('bytes called with n <= 0') if (storedBytes[n] === undefined) { - console.log('create storedBytes[n]'); storedBytes[n] = makeString(n, 'C'); } body = storedBytes[n]; @@ -53,7 +50,6 @@ var server = http.createServer(function (req, res) { if (n <= 0) throw new Error('buffer called with n <= 0'); if (storedBuffer[n] === undefined) { - console.log('create storedBuffer[n]'); storedBuffer[n] = new Buffer(n); for (var i = 0; i < n; i++) { storedBuffer[n][i] = 'C'.charCodeAt(0); @@ -66,7 +62,6 @@ var server = http.createServer(function (req, res) { if (n <= 0) throw new Error('unicode called with n <= 0'); if (storedUnicode[n] === undefined) { - console.log('create storedUnicode[n]'); storedUnicode[n] = makeString(n, '\u263A'); } body = storedUnicode[n]; @@ -120,9 +115,6 @@ function makeString(size, c) { } server.listen(port, function () { - console.log('Listening at http://127.0.0.1:'+port+'/'); -}); - -process.on('exit', function() { - console.error('libuv counters', process.uvCounters()); + if (module === require.main) + console.error('Listening at http://127.0.0.1:'+port+'/'); }); From 3b16657e77511d92b55d1d2157485d8dd0df0c00 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:51:53 -0800 Subject: [PATCH 10/45] bench: misc/url --- benchmark/{ => misc}/url.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename benchmark/{ => misc}/url.js (74%) diff --git a/benchmark/url.js b/benchmark/misc/url.js similarity index 74% rename from benchmark/url.js rename to benchmark/misc/url.js index fd8f72c6ea..6c2799b6cb 100644 --- a/benchmark/url.js +++ b/benchmark/misc/url.js @@ -1,5 +1,5 @@ -var util = require('util'); var url = require('url') +var n = 25 * 100; var urls = [ 'http://nodejs.org/docs/latest/api/url.html#url_url_format_urlobj', @@ -18,16 +18,15 @@ var paths = [ benchmark('parse()', url.parse); benchmark('format()', url.format); - paths.forEach(function(p) { - benchmark('resolve("' + p + '")', function(u) { url.resolve(u, p) }); + benchmark('resolve("' + p + '")', function(u) { + url.resolve(u, p) + }); }); function benchmark(name, fun) { - process.stdout.write('benchmarking ' + name + ' ... '); - var timestamp = process.hrtime(); - for (var i = 0; i < 25 * 1000; ++i) { + for (var i = 0; i < n; ++i) { for (var j = 0, k = urls.length; j < k; ++j) fun(urls[j]); } timestamp = process.hrtime(timestamp); @@ -35,6 +34,7 @@ function benchmark(name, fun) { var seconds = timestamp[0]; var nanos = timestamp[1]; var time = seconds + nanos / 1e9; + var rate = n / time; - process.stdout.write(util.format('%s sec\n', time.toFixed(3))); + console.log('misc/url.js %s: %s', name, rate.toPrecision(5)); } From 921c3c2097f170c881c2f3f264b89f792c204075 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 13:24:27 -0800 Subject: [PATCH 11/45] bench: misc/startup.js --- benchmark/misc/startup.js | 40 +++++++++++++++++++++++++++++++++++++++ benchmark/startup.js | 26 ------------------------- 2 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 benchmark/misc/startup.js delete mode 100644 benchmark/startup.js diff --git a/benchmark/misc/startup.js b/benchmark/misc/startup.js new file mode 100644 index 0000000000..25b3f56fef --- /dev/null +++ b/benchmark/misc/startup.js @@ -0,0 +1,40 @@ +var common = require('../common.js'); +var spawn = require('child_process').spawn; +var path = require('path'); +var emptyJsFile = path.resolve(__dirname, '../../test/fixtures/semicolon.js'); +var starts = 100; +var i = 0; +var start; + +var bench = common.createBenchmark(startNode, { + dur: [1, 3] +}); + +function startNode(conf) { + var dur = +conf.dur; + var go = true; + var starts = 0; + var open = 0; + + setTimeout(function() { + go = false; + }, dur * 1000); + + bench.start(); + start(); + + function start() { + var node = spawn(process.execPath || process.argv[0], [emptyJsFile]); + node.on('exit', function(exitCode) { + if (exitCode !== 0) { + throw new Error('Error during node startup'); + } + starts++; + + if (go) + start(); + else + bench.end(starts); + }); + } +} diff --git a/benchmark/startup.js b/benchmark/startup.js deleted file mode 100644 index 97bb8d5189..0000000000 --- a/benchmark/startup.js +++ /dev/null @@ -1,26 +0,0 @@ -var spawn = require('child_process').spawn, - path = require('path'), - emptyJsFile = path.join(__dirname, '../test/fixtures/semicolon.js'), - starts = 100, - i = 0, - start; - -function startNode() { - var node = spawn(process.execPath || process.argv[0], [emptyJsFile]); - node.on('exit', function(exitCode) { - if (exitCode !== 0) { - throw new Error('Error during node startup'); - } - - i++; - if (i < starts) { - startNode(); - } else{ - var duration = +new Date - start; - console.log('Started node %d times in %s ms. %d ms / start.', starts, duration, duration / starts); - } - }); -} - -start = +new Date; -startNode(); From cc38528acf976c6f10da45b1d824f86444067fb1 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 13:37:24 -0800 Subject: [PATCH 12/45] bench: buffer-base64-encode --- .../{ => buffers}/buffer-base64-encode.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) rename benchmark/{ => buffers}/buffer-base64-encode.js (74%) diff --git a/benchmark/buffer-base64-encode.js b/benchmark/buffers/buffer-base64-encode.js similarity index 74% rename from benchmark/buffer-base64-encode.js rename to benchmark/buffers/buffer-base64-encode.js index 7ead3a575d..f2b8e9a482 100644 --- a/benchmark/buffer-base64-encode.js +++ b/benchmark/buffers/buffer-base64-encode.js @@ -19,9 +19,18 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -var N = 64*1024*1024 -var b = Buffer(N); -var s = ''; -for (var i = 0; i < 256; ++i) s += String.fromCharCode(i); -for (var i = 0; i < N; i += 256) b.write(s, i, 256, 'ascii'); -for (var i = 0; i < 32; ++i) b.toString('base64'); +var common = require('../common.js'); + +var bench = common.createBenchmark(main, {}); + +function main(conf) { + var N = 64 * 1024 * 1024; + var b = Buffer(N); + var s = ''; + for (var i = 0; i < 256; ++i) s += String.fromCharCode(i); + + bench.start(); + for (var i = 0; i < N; i += 256) b.write(s, i, 256, 'ascii'); + for (var i = 0; i < 32; ++i) b.toString('base64'); + bench.end(64); +} From 419607e8ebcab4b2235ec132623467fcf6fe74ee Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 13:41:16 -0800 Subject: [PATCH 13/45] bench: Buffer creation --- benchmark/buffer_creation.js | 6 ------ benchmark/buffers/buffer_creation.js | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) delete mode 100644 benchmark/buffer_creation.js create mode 100644 benchmark/buffers/buffer_creation.js diff --git a/benchmark/buffer_creation.js b/benchmark/buffer_creation.js deleted file mode 100644 index 3bc711e3ba..0000000000 --- a/benchmark/buffer_creation.js +++ /dev/null @@ -1,6 +0,0 @@ -SlowBuffer = require('buffer').SlowBuffer; - -for (var i = 0; i < 1e6; i++) { - b = new SlowBuffer(10); - b[1] = 2 -} diff --git a/benchmark/buffers/buffer_creation.js b/benchmark/buffers/buffer_creation.js new file mode 100644 index 0000000000..d1d91f0c12 --- /dev/null +++ b/benchmark/buffers/buffer_creation.js @@ -0,0 +1,17 @@ +SlowBuffer = require('buffer').SlowBuffer; + +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + len: [10, 1024], + n: [1024] +}); + +function main(conf) { + var len = +conf.len; + var n = +conf.n; + bench.start(); + for (var i = 0; i < n * 1024; i++) { + b = new SlowBuffer(len); + } + bench.end(n); +} From 55aa2571a0f0fe1748960fcbf557737d922a05f3 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:53:27 -0800 Subject: [PATCH 14/45] bench: Buffer read/write benchmarks --- benchmark/buffer_read.js | 97 ---------------------------- benchmark/buffer_write.js | 103 ------------------------------ benchmark/buffers/buffer_read.js | 28 ++++++++ benchmark/buffers/buffer_write.js | 63 ++++++++++++++++++ 4 files changed, 91 insertions(+), 200 deletions(-) delete mode 100644 benchmark/buffer_read.js delete mode 100644 benchmark/buffer_write.js create mode 100644 benchmark/buffers/buffer_read.js create mode 100644 benchmark/buffers/buffer_write.js diff --git a/benchmark/buffer_read.js b/benchmark/buffer_read.js deleted file mode 100644 index f128bf2226..0000000000 --- a/benchmark/buffer_read.js +++ /dev/null @@ -1,97 +0,0 @@ -const LEN = 1e7; -const noAssert = process.argv[3] == 'true' ? true - : process.argv[3] == 'false' ? false - : undefined; - -var timer = require('./_bench_timer'); - -var buff = (process.argv[2] == 'slow') ? - (new require('buffer').SlowBuffer(8)) : - (new Buffer(8)); -var i; - -buff.writeDoubleLE(0, 0, noAssert); - -timer('readUInt8', function() { - for (i = 0; i < LEN; i++) { - buff.readUInt8(0, noAssert); - } -}); - -timer('readUInt16LE', function() { - for (i = 0; i < LEN; i++) { - buff.readUInt16LE(0, noAssert); - } -}); - -timer('readUInt16BE', function() { - for (i = 0; i < LEN; i++) { - buff.readUInt16BE(0, noAssert); - } -}); - -timer('readUInt32LE', function() { - for (i = 0; i < LEN; i++) { - buff.readUInt32LE(0, noAssert); - } -}); - -timer('readUInt32BE', function() { - for (i = 0; i < LEN; i++) { - buff.readUInt32BE(0, noAssert); - } -}); - -timer('readInt8', function() { - for (i = 0; i < LEN; i++) { - buff.readInt8(0, noAssert); - } -}); - -timer('readInt16LE', function() { - for (i = 0; i < LEN; i++) { - buff.readInt16LE(0, noAssert); - } -}); - -timer('readInt16BE', function() { - for (i = 0; i < LEN; i++) { - buff.readInt16BE(0, noAssert); - } -}); - -timer('readInt32LE', function() { - for (i = 0; i < LEN; i++) { - buff.readInt32LE(0, noAssert); - } -}); - -timer('readInt32BE', function() { - for (i = 0; i < LEN; i++) { - buff.readInt32BE(0, noAssert); - } -}); - -timer('readFloatLE', function() { - for (i = 0; i < LEN; i++) { - buff.readFloatLE(0, noAssert); - } -}); - -timer('readFloatBE', function() { - for (i = 0; i < LEN; i++) { - buff.readFloatBE(0, noAssert); - } -}); - -timer('readDoubleLE', function() { - for (i = 0; i < LEN; i++) { - buff.readDoubleLE(0, noAssert); - } -}); - -timer('readDoubleBE', function() { - for (i = 0; i < LEN; i++) { - buff.readDoubleBE(0, noAssert); - } -}); diff --git a/benchmark/buffer_write.js b/benchmark/buffer_write.js deleted file mode 100644 index 70f9926563..0000000000 --- a/benchmark/buffer_write.js +++ /dev/null @@ -1,103 +0,0 @@ -const LEN = 1e7; - -const INT8 = 0x7f; -const INT16 = 0x7fff; -const INT32 = 0x7fffffff; -const UINT8 = INT8 * 2; -const UINT16 = INT16 * 2; -const UINT32 = INT32 * 2; - -const noAssert = process.argv[3] == 'true' ? true - : process.argv[3] == 'false' ? false - : undefined; - -var timer = require('./_bench_timer'); - -var buff = (process.argv[2] == 'slow') ? - (new require('buffer').SlowBuffer(8)) : - (new Buffer(8)); -var i; - -timer('writeUInt8', function() { - for (i = 0; i < LEN; i++) { - buff.writeUInt8(i % UINT8, 0, noAssert); - } -}); - -timer('writeUInt16LE', function() { - for (i = 0; i < LEN; i++) { - buff.writeUInt16LE(i % UINT16, 0, noAssert); - } -}); - -timer('writeUInt16BE', function() { - for (i = 0; i < LEN; i++) { - buff.writeUInt16BE(i % UINT16, 0, noAssert); - } -}); - -timer('writeUInt32LE', function() { - for (i = 0; i < LEN; i++) { - buff.writeUInt32LE(i % UINT32, 0, noAssert); - } -}); - -timer('writeUInt32BE', function() { - for (i = 0; i < LEN; i++) { - buff.writeUInt32BE(i % UINT32, 0, noAssert); - } -}); - -timer('writeInt8', function() { - for (i = 0; i < LEN; i++) { - buff.writeInt8(i % INT8, 0, noAssert); - } -}); - -timer('writeInt16LE', function() { - for (i = 0; i < LEN; i++) { - buff.writeInt16LE(i % INT16, 0, noAssert); - } -}); - -timer('writeInt16BE', function() { - for (i = 0; i < LEN; i++) { - buff.writeInt16BE(i % INT16, 0, noAssert); - } -}); - -timer('writeInt32LE', function() { - for (i = 0; i < LEN; i++) { - buff.writeInt32LE(i % INT32, 0, noAssert); - } -}); - -timer('writeInt32BE', function() { - for (i = 0; i < LEN; i++) { - buff.writeInt32BE(i % INT32, 0, noAssert); - } -}); - -timer('writeFloatLE', function() { - for (i = 0; i < LEN; i++) { - buff.writeFloatLE(i * 0.1, 0, noAssert); - } -}); - -timer('writeFloatBE', function() { - for (i = 0; i < LEN; i++) { - buff.writeFloatBE(i * 0.1, 0, noAssert); - } -}); - -timer('writeDoubleLE', function() { - for (i = 0; i < LEN; i++) { - buff.writeDoubleLE(i * 0.1, 0, noAssert); - } -}); - -timer('writeDoubleBE', function() { - for (i = 0; i < LEN; i++) { - buff.writeDoubleBE(i * 0.1, 0, noAssert); - } -}); diff --git a/benchmark/buffers/buffer_read.js b/benchmark/buffers/buffer_read.js new file mode 100644 index 0000000000..fccd99dcd2 --- /dev/null +++ b/benchmark/buffers/buffer_read.js @@ -0,0 +1,28 @@ +var common = require('../common.js'); + +var bench = common.createBenchmark(main, { + noAssert: [false, true], + buffer: ['fast', 'slow'], + type: ['UInt8', 'UInt16LE', 'UInt16BE', + 'UInt32LE', 'UInt32BE', + 'Int8', 'Int16LE', 'Int16BE', + 'Int32LE', 'Int32BE', + 'FloatLE', 'FloatBE', + 'DoubleLE', 'DoubleBE'], + millions: [1] +}); + +function main(conf) { + var noAssert = conf.noAssert === 'true'; + var len = +conf.millions * 1e6; + var clazz = conf.buf === 'fast' ? Buffer : require('buffer').SlowBuffer; + var buff = new clazz(8); + var fn = 'read' + conf.type; + + buff.writeDoubleLE(0, 0, noAssert); + bench.start(); + for (var i = 0; i < len; i++) { + buff[fn](0, noAssert); + } + bench.end(len / 1e6); +} diff --git a/benchmark/buffers/buffer_write.js b/benchmark/buffers/buffer_write.js new file mode 100644 index 0000000000..4dbfcb6096 --- /dev/null +++ b/benchmark/buffers/buffer_write.js @@ -0,0 +1,63 @@ + +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + noAssert: [false, true], + buffer: ['fast', 'slow'], + type: ['UInt8', 'UInt16LE', 'UInt16BE', + 'UInt32LE', 'UInt32BE', + 'Int8', 'Int16LE', 'Int16BE', + 'Int32LE', 'Int32BE', + 'FloatLE', 'FloatBE', + 'DoubleLE', 'DoubleBE'], + millions: [1] +}); + +const INT8 = 0x7f; +const INT16 = 0x7fff; +const INT32 = 0x7fffffff; +const UINT8 = INT8 * 2; +const UINT16 = INT16 * 2; +const UINT32 = INT32 * 2; + +var mod = { + writeInt8: INT8, + writeInt16BE: INT16, + writeInt16LE: INT16, + writeInt32BE: INT32, + writeInt32LE: INT32, + writeUInt8: UINT8, + writeUInt16BE: UINT16, + writeUInt16LE: UINT16, + writeUInt32BE: UINT32, + writeUInt32LE: UINT32 +}; + +function main(conf) { + var noAssert = conf.noAssert === 'true'; + var len = +conf.millions * 1e6; + var clazz = conf.buf === 'fast' ? Buffer : require('buffer').SlowBuffer; + var buff = new clazz(8); + var fn = 'write' + conf.type; + + if (fn.match(/Int/)) + benchInt(buff, fn, len, noAssert); + else + benchFloat(buff, fn, len, noAssert); +} + +function benchInt(buff, fn, len, noAssert) { + var m = mod[fn]; + bench.start(); + for (var i = 0; i < len; i++) { + buff[fn](i % m, 0, noAssert); + } + bench.end(len / 1e6); +} + +function benchFloat(buff, fn, len, noAssert) { + bench.start(); + for (var i = 0; i < len; i++) { + buff[fn](i * 0.1, 0, noAssert); + } + bench.end(len / 1e6); +} From 048f7fd37ceb2fcdc33b06d36b6e998bd1085952 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 14:09:41 -0800 Subject: [PATCH 15/45] bench: Merge fast_buffer_creation and buffer_creation --- benchmark/buffers/buffer_creation.js | 4 +++- benchmark/fast_buffer_creation.js | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 benchmark/fast_buffer_creation.js diff --git a/benchmark/buffers/buffer_creation.js b/benchmark/buffers/buffer_creation.js index d1d91f0c12..bc0c557118 100644 --- a/benchmark/buffers/buffer_creation.js +++ b/benchmark/buffers/buffer_creation.js @@ -2,6 +2,7 @@ SlowBuffer = require('buffer').SlowBuffer; var common = require('../common.js'); var bench = common.createBenchmark(main, { + type: ['fast', 'slow'], len: [10, 1024], n: [1024] }); @@ -9,9 +10,10 @@ var bench = common.createBenchmark(main, { function main(conf) { var len = +conf.len; var n = +conf.n; + var clazz = conf.type === 'fast' ? Buffer : SlowBuffer; bench.start(); for (var i = 0; i < n * 1024; i++) { - b = new SlowBuffer(len); + b = new clazz(len); } bench.end(n); } diff --git a/benchmark/fast_buffer_creation.js b/benchmark/fast_buffer_creation.js deleted file mode 100644 index fbd0c7579a..0000000000 --- a/benchmark/fast_buffer_creation.js +++ /dev/null @@ -1,4 +0,0 @@ -for (var i = 0; i < 1e6; i++) { - b = new Buffer(10); - b[1] = 2; -} From 56b22956ad3c76a747b359452fec987eab4fac5e Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 14:12:35 -0800 Subject: [PATCH 16/45] bench: Remove unused 'fast_buffer2' benchmarks --- benchmark/fast_buffer2.js | 42 ------------------------------ benchmark/fast_buffer2_creation.js | 6 ----- 2 files changed, 48 deletions(-) delete mode 100644 benchmark/fast_buffer2.js delete mode 100644 benchmark/fast_buffer2_creation.js diff --git a/benchmark/fast_buffer2.js b/benchmark/fast_buffer2.js deleted file mode 100644 index 861ae3baae..0000000000 --- a/benchmark/fast_buffer2.js +++ /dev/null @@ -1,42 +0,0 @@ -var SlowBuffer = require('buffer').SlowBuffer; -var POOLSIZE = 8*1024; -var pool; - -function allocPool () { - pool = new SlowBuffer(POOLSIZE); - pool.used = 0; -} - -function FastBuffer (length) { - this.length = length; - - if (length > POOLSIZE) { - // Big buffer, just alloc one. - this.parent = new Buffer(length); - this.offset = 0; - } else { - // Small buffer. - if (!pool || pool.length - pool.used < length) allocPool(); - this.parent = pool; - this.offset = pool.used; - pool.used += length; - } - - // HERE HERE HERE - SlowBuffer.makeFastBuffer(this.parent, this, this.offset, this.length); -} - -exports.FastBuffer = FastBuffer; - -FastBuffer.prototype.get = function (i) { - if (i < 0 || i >= this.length) throw new Error("oob"); - return this.parent[this.offset + i]; -}; - -FastBuffer.prototype.set = function (i, v) { - if (i < 0 || i >= this.length) throw new Error("oob"); - return this.parent[this.offset + i] = v; -}; - -// TODO define slice, toString, write, etc. -// slice should not use c++ diff --git a/benchmark/fast_buffer2_creation.js b/benchmark/fast_buffer2_creation.js deleted file mode 100644 index 877f5695d2..0000000000 --- a/benchmark/fast_buffer2_creation.js +++ /dev/null @@ -1,6 +0,0 @@ - -FastBuffer = require('./fast_buffer2').FastBuffer; -for (var i = 0; i < 1e6; i++) { - b = new FastBuffer(10); - b[1] = 2; -} From 3f67a48dd461e1454b81cde33f5c005de5910fa3 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 22:01:50 -0800 Subject: [PATCH 17/45] bench: Add buffers/dataview_set --- benchmark/buffers/dataview_set.js | 57 ++++++++++++++++ benchmark/dataview_set.js | 104 ------------------------------ 2 files changed, 57 insertions(+), 104 deletions(-) create mode 100644 benchmark/buffers/dataview_set.js delete mode 100644 benchmark/dataview_set.js diff --git a/benchmark/buffers/dataview_set.js b/benchmark/buffers/dataview_set.js new file mode 100644 index 0000000000..ce0064edb6 --- /dev/null +++ b/benchmark/buffers/dataview_set.js @@ -0,0 +1,57 @@ + +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + type: ['Uint8', 'Uint16LE', 'Uint16BE', + 'Uint32LE', 'Uint32BE', + 'Int8', 'Int16LE', 'Int16BE', + 'Int32LE', 'Int32BE', + 'Float32LE', 'Float32BE', + 'Float64LE', 'Float64BE'], + millions: [1] +}); + +const INT8 = 0x7f; +const INT16 = 0x7fff; +const INT32 = 0x7fffffff; +const UINT8 = INT8 * 2; +const UINT16 = INT16 * 2; +const UINT32 = INT32 * 2; + +var mod = { + setInt8: INT8, + setInt16: INT16, + setInt32: INT32, + setUint8: UINT8, + setUint16: UINT16, + setUint32: UINT32 +}; + +function main(conf) { + var len = +conf.millions * 1e6; + var ab = new ArrayBuffer(8); + var dv = new DataView(ab, 0, 8); + var le = /LE$/.test(conf.type); + var fn = 'set' + conf.type.replace(/[LB]E$/, ''); + + if (/int/i.test(fn)) + benchInt(dv, fn, len, le); + else + benchFloat(dv, fn, len, le); +} + +function benchInt(dv, fn, len, le) { + var m = mod[fn]; + bench.start(); + for (var i = 0; i < len; i++) { + dv[fn](0, i % m, le); + } + bench.end(len / 1e6); +} + +function benchFloat(dv, fn, len, le) { + bench.start(); + for (var i = 0; i < len; i++) { + dv[fn](0, i * 0.1, le); + } + bench.end(len / 1e6); +} diff --git a/benchmark/dataview_set.js b/benchmark/dataview_set.js deleted file mode 100644 index d0eca61515..0000000000 --- a/benchmark/dataview_set.js +++ /dev/null @@ -1,104 +0,0 @@ -const LEN = 1e7; - -const INT8 = 0x7f; -const INT16 = 0x7fff; -const INT32 = 0x7fffffff; -const UINT8 = INT8 * 2; -const UINT16 = INT16 * 2; -const UINT32 = INT32 * 2; - -const noAssert = process.argv[3] == 'true' ? true - : process.argv[3] == 'false' ? false - : undefined; - -var timer = require('./_bench_timer'); - -var buff = (process.argv[2] == 'slow') ? - (new require('buffer').SlowBuffer(8)) : - (new Buffer(8)); -var dv = new DataView(buff); -var i; - -timer('setUint8', function() { - for (i = 0; i < LEN; i++) { - dv.setUint8(0, i % UINT8); - } -}); - -timer('setUint16 - LE', function() { - for (i = 0; i < LEN; i++) { - dv.setUint16(0, i % UINT16, true); - } -}); - -timer('setUint16 - BE', function() { - for (i = 0; i < LEN; i++) { - dv.setUint16(0, i % UINT16); - } -}); - -timer('setUint32 - LE', function() { - for (i = 0; i < LEN; i++) { - dv.setUint32(0, i % UINT32, true); - } -}); - -timer('setUint32 - BE', function() { - for (i = 0; i < LEN; i++) { - dv.setUint32(0, i % UINT32); - } -}); - -timer('setInt8', function() { - for (i = 0; i < LEN; i++) { - dv.setInt8(0, i % INT8); - } -}); - -timer('setInt16 - LE', function() { - for (i = 0; i < LEN; i++) { - dv.setInt16(0, i % INT16, true); - } -}); - -timer('setInt16 - BE', function() { - for (i = 0; i < LEN; i++) { - dv.setInt16(0, i % INT16); - } -}); - -timer('setInt32 - LE', function() { - for (i = 0; i < LEN; i++) { - dv.setInt32(0, i % INT32, true); - } -}); - -timer('setInt32 - BE', function() { - for (i = 0; i < LEN; i++) { - dv.setInt32(0, i % INT32); - } -}); - -timer('setFloat32 - LE', function() { - for (i = 0; i < LEN; i++) { - dv.setFloat32(0, i * 0.1, true); - } -}); - -timer('setFloat32 - BE', function() { - for (i = 0; i < LEN; i++) { - dv.setFloat32(0, i * 0.1); - } -}); - -timer('setFloat64 - LE', function() { - for (i = 0; i < LEN; i++) { - dv.setFloat64(0, i * 0.1, true); - } -}); - -timer('setFloat64 - BE', function() { - for (i = 0; i < LEN; i++) { - dv.setFloat64(0, i * 0.1); - } -}); From e87ed91faceb1c8418973f3c3a06edfc063f46cf Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 13:32:39 -0800 Subject: [PATCH 18/45] bench: Arrays --- benchmark/arrays/var_int.js | 27 ++++++++++++++++----------- benchmark/arrays/zero_float.js | 27 ++++++++++++++++----------- benchmark/arrays/zero_int.js | 27 ++++++++++++++++----------- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/benchmark/arrays/var_int.js b/benchmark/arrays/var_int.js index 17e12989f5..47a7e62dc7 100644 --- a/benchmark/arrays/var_int.js +++ b/benchmark/arrays/var_int.js @@ -1,15 +1,20 @@ -var types = 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '); +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + type: 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '), + n: [25] +}); -var type = types[types.indexOf(process.argv[2])]; -if (!type) - type = types[0]; +function main(conf) { + var type = conf.type; + var clazz = global[type]; + var n = +conf.n; -console.error('Benchmarking', type); -var clazz = global[type]; - -var arr = new clazz(25 * 10e5); -for (var i = 0; i < 10; ++i) { - for (var j = 0, k = arr.length; j < k; ++j) { - arr[j] = (j ^ k) & 127; + bench.start(); + var arr = new clazz(n * 1e6); + for (var i = 0; i < 10; ++i) { + for (var j = 0, k = arr.length; j < k; ++j) { + arr[j] = (j ^ k) & 127; + } } + bench.end(n); } diff --git a/benchmark/arrays/zero_float.js b/benchmark/arrays/zero_float.js index bdb8765d0a..a6624205bf 100644 --- a/benchmark/arrays/zero_float.js +++ b/benchmark/arrays/zero_float.js @@ -1,15 +1,20 @@ -var types = 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '); +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + type: 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '), + n: [25] +}); -var type = types[types.indexOf(process.argv[2])]; -if (!type) - type = types[0]; +function main(conf) { + var type = conf.type; + var clazz = global[type]; + var n = +conf.n; -console.error('Benchmarking', type); -var clazz = global[type]; - -var arr = new clazz(25 * 10e5); -for (var i = 0; i < 10; ++i) { - for (var j = 0, k = arr.length; j < k; ++j) { - arr[j] = 0.0; + bench.start(); + var arr = new clazz(n * 1e6); + for (var i = 0; i < 10; ++i) { + for (var j = 0, k = arr.length; j < k; ++j) { + arr[j] = 0.0; + } } + bench.end(n); } diff --git a/benchmark/arrays/zero_int.js b/benchmark/arrays/zero_int.js index 17dac62c14..29a2d58b66 100644 --- a/benchmark/arrays/zero_int.js +++ b/benchmark/arrays/zero_int.js @@ -1,15 +1,20 @@ -var types = 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '); +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + type: 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '), + n: [25] +}); -var type = types[types.indexOf(process.argv[2])]; -if (!type) - type = types[0]; +function main(conf) { + var type = conf.type; + var clazz = global[type]; + var n = +conf.n; -console.error('Benchmarking', type); -var clazz = global[type]; - -var arr = new clazz(25 * 10e5); -for (var i = 0; i < 10; ++i) { - for (var j = 0, k = arr.length; j < k; ++j) { - arr[j] = 0; + bench.start(); + var arr = new clazz(n * 1e6); + for (var i = 0; i < 10; ++i) { + for (var j = 0, k = arr.length; j < k; ++j) { + arr[j] = 0; + } } + bench.end(n); } From 4e1bcdcab9f1d1eda72724b80d99cf6a564a3d52 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 18:22:43 -0800 Subject: [PATCH 19/45] bench: Add function_call to bench-misc --- benchmark/function_call/bench.js | 43 ------------------- benchmark/function_call/wscript | 15 ------- benchmark/misc/function_call/.gitignore | 1 + benchmark/misc/function_call/Makefile | 2 + benchmark/{ => misc}/function_call/binding.cc | 5 +-- benchmark/misc/function_call/binding.gyp | 8 ++++ benchmark/misc/function_call/index.js | 33 ++++++++++++++ 7 files changed, 46 insertions(+), 61 deletions(-) delete mode 100644 benchmark/function_call/bench.js delete mode 100644 benchmark/function_call/wscript create mode 100644 benchmark/misc/function_call/.gitignore create mode 100644 benchmark/misc/function_call/Makefile rename benchmark/{ => misc}/function_call/binding.cc (74%) create mode 100644 benchmark/misc/function_call/binding.gyp create mode 100644 benchmark/misc/function_call/index.js diff --git a/benchmark/function_call/bench.js b/benchmark/function_call/bench.js deleted file mode 100644 index 55e6ea116f..0000000000 --- a/benchmark/function_call/bench.js +++ /dev/null @@ -1,43 +0,0 @@ -var binding = require('./build/default/binding'); - -c = 0 - -function js() { - return c++; //(new Date()).getTime(); -} - -var cxx = binding.hello; - -var i, N = 100000000; - -console.log(js()); -console.log(cxx()); - - - -var start = new Date(); -for (i = 0; i < N; i++) { - js(); -} -var jsDiff = new Date() - start; -console.log(N +" JS function calls: " + jsDiff); - - -var start = new Date(); -for (i = 0; i < N; i++) { - cxx(); -} -var cxxDiff = new Date() - start; -console.log(N +" C++ function calls: " + cxxDiff); - -function toMicro (diff) { - return (diff / N) * 1000000; -} - -console.log("\nJS function call speed: %d microseconds", toMicro(jsDiff)); -console.log("C++ function call speed: %d microseconds", toMicro(cxxDiff)); - - -console.log("\nJS speedup " + (cxxDiff / jsDiff)); - - diff --git a/benchmark/function_call/wscript b/benchmark/function_call/wscript deleted file mode 100644 index 3db367fe0e..0000000000 --- a/benchmark/function_call/wscript +++ /dev/null @@ -1,15 +0,0 @@ -srcdir = '.' -blddir = 'build' -VERSION = '0.0.1' - -def set_options(opt): - opt.tool_options('compiler_cxx') - -def configure(conf): - conf.check_tool('compiler_cxx') - conf.check_tool('node_addon') - -def build(bld): - obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') - obj.target = 'binding' - obj.source = 'binding.cc' diff --git a/benchmark/misc/function_call/.gitignore b/benchmark/misc/function_call/.gitignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/benchmark/misc/function_call/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmark/misc/function_call/Makefile b/benchmark/misc/function_call/Makefile new file mode 100644 index 0000000000..9dc1f30329 --- /dev/null +++ b/benchmark/misc/function_call/Makefile @@ -0,0 +1,2 @@ +binding: + node-gyp rebuild --nodedir=../../.. diff --git a/benchmark/function_call/binding.cc b/benchmark/misc/function_call/binding.cc similarity index 74% rename from benchmark/function_call/binding.cc rename to benchmark/misc/function_call/binding.cc index 75882c1ef8..6ea928464c 100644 --- a/benchmark/function_call/binding.cc +++ b/benchmark/misc/function_call/binding.cc @@ -1,6 +1,5 @@ #include #include -#include using namespace v8; @@ -8,12 +7,12 @@ static int c = 0; static Handle Hello(const Arguments& args) { HandleScope scope; - //time_t tv = time(NULL); return scope.Close(Integer::New(c++)); } extern "C" void init (Handle target) { HandleScope scope; - //target->Set(String::New("hello"), String::New("World")); NODE_SET_METHOD(target, "hello", Hello); } + +NODE_MODULE(binding, init); diff --git a/benchmark/misc/function_call/binding.gyp b/benchmark/misc/function_call/binding.gyp new file mode 100644 index 0000000000..3bfb84493f --- /dev/null +++ b/benchmark/misc/function_call/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/benchmark/misc/function_call/index.js b/benchmark/misc/function_call/index.js new file mode 100644 index 0000000000..ac05478a3e --- /dev/null +++ b/benchmark/misc/function_call/index.js @@ -0,0 +1,33 @@ +// show the difference between calling a short js function +// relative to a comparable C++ function. +// Reports millions of calls per second. +// Note that JS speed goes up, while cxx speed stays about the same. + +var assert = require('assert'); +var common = require('../../common.js'); + +var binding = require('./build/Release/binding'); +var cxx = binding.hello; + +var c = 0; +function js() { + return c++; +} + +assert(js() === cxx()); + +var bench = common.createBenchmark(main, { + type: ['js', 'cxx'], + millions: [1,10,50] +}); + +function main(conf) { + var n = +conf.millions * 1e6; + + var fn = conf.type === 'cxx' ? cxx : js; + bench.start(); + for (var i = 0; i < n; i++) { + fn(); + } + bench.end(+conf.millions); +} From 44be55fc4e490975eb3292dca12cfac1eec234bd Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 22:42:19 -0800 Subject: [PATCH 20/45] bench: Move process_loop to misc/spawn-echo --- benchmark/misc/spawn-echo.js | 25 +++++++++++++++++++++++++ benchmark/process_loop.js | 19 ------------------- 2 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 benchmark/misc/spawn-echo.js delete mode 100644 benchmark/process_loop.js diff --git a/benchmark/misc/spawn-echo.js b/benchmark/misc/spawn-echo.js new file mode 100644 index 0000000000..2b1b989e67 --- /dev/null +++ b/benchmark/misc/spawn-echo.js @@ -0,0 +1,25 @@ +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + thousands: [1] +}); + +var spawn = require('child_process').spawn; +function main(conf) { + var len = +conf.thousands * 1000; + + bench.start(); + go(len, len); +} + +function go(n, left) { + if (--left === 0) + return bench.end(n); + + var child = spawn('echo', ['hello']); + child.on('exit', function(code) { + if (code) + process.exit(code); + else + go(n, left); + }); +} diff --git a/benchmark/process_loop.js b/benchmark/process_loop.js deleted file mode 100644 index eeba06ab97..0000000000 --- a/benchmark/process_loop.js +++ /dev/null @@ -1,19 +0,0 @@ -var util = require("util"), - childProcess = require("child_process"); - -function next (i) { - if (i <= 0) return; - - var child = childProcess.spawn("echo", ["hello"]); - - child.stdout.addListener("data", function (chunk) { - util.print(chunk); - }); - - child.addListener("exit", function (code) { - if (code != 0) process.exit(-1); - next(i - 1); - }); -} - -next(500); From f7a4ccb409c14fed9c52956765c95b10bacb779a Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 22:50:30 -0800 Subject: [PATCH 21/45] bench: Move nexttick-2 to misc/next-tick-depth x --- .../next-tick-depth.js} | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) rename benchmark/{next-tick-2.js => misc/next-tick-depth.js} (73%) diff --git a/benchmark/next-tick-2.js b/benchmark/misc/next-tick-depth.js similarity index 73% rename from benchmark/next-tick-2.js rename to benchmark/misc/next-tick-depth.js index 44a2b41b54..b8ae27879e 100644 --- a/benchmark/next-tick-2.js +++ b/benchmark/misc/next-tick-depth.js @@ -19,23 +19,22 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -var count = 2e6, - left = count, - start; +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + millions: [2] +}); -function onNextTick() { - if (--left) { - process.nextTick(onNextTick); - } else { - finalize(); - } -} +process.maxTickDepth = Infinity; -function finalize() { - var duration = (new Date()).getTime() - start, - ticksPerSec = count / duration * 1000; - console.log("nextTick callbacks per second: " + Math.round(ticksPerSec)); -} +function main(conf) { + var n = +conf.millions * 1e6; -start = (new Date()).getTime(); -process.nextTick(onNextTick); + bench.start(); + process.nextTick(onNextTick); + function onNextTick() { + if (--n) + process.nextTick(onNextTick); + else + bench.end(+conf.millions); + } +} From 7e5cd08061dc17f38dec307ae8fc43e44b85a777 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 22:54:27 -0800 Subject: [PATCH 22/45] bench: move next-tick to misc/next-tick-breadth --- benchmark/misc/next-tick-breadth.js | 21 +++++++++++++++++++++ benchmark/next-tick.js | 17 ----------------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 benchmark/misc/next-tick-breadth.js delete mode 100644 benchmark/next-tick.js diff --git a/benchmark/misc/next-tick-breadth.js b/benchmark/misc/next-tick-breadth.js new file mode 100644 index 0000000000..6524081443 --- /dev/null +++ b/benchmark/misc/next-tick-breadth.js @@ -0,0 +1,21 @@ + +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + millions: [2] +}); + +function main(conf) { + var N = +conf.millions * 1e6; + var n = 0; + + function cb() { + n++; + if (n === N) + bench.end(n / 1e6); + } + + bench.start(); + for (var i = 0; i < N; i++) { + process.nextTick(cb); + } +} diff --git a/benchmark/next-tick.js b/benchmark/next-tick.js deleted file mode 100644 index 9352f8dc0a..0000000000 --- a/benchmark/next-tick.js +++ /dev/null @@ -1,17 +0,0 @@ -// run with `time node benchmark/next-tick.js` -var assert = require('assert'); - -var N = 1e7; -var n = 0; - -process.on('exit', function() { - assert.equal(n, N); -}); - -function cb() { - n++; -} - -for (var i = 0; i < N; ++i) { - process.nextTick(cb); -} From 3761be3d99fc63858a5cf6f23f2374922d41be4b Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:00:18 -0800 Subject: [PATCH 23/45] bench: Move timers to misc/timers --- benchmark/misc/timers.js | 40 ++++++++++++++++++++++++++++++++++++++++ benchmark/timers.js | 5 ----- 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 benchmark/misc/timers.js delete mode 100644 benchmark/timers.js diff --git a/benchmark/misc/timers.js b/benchmark/misc/timers.js new file mode 100644 index 0000000000..23f571bb45 --- /dev/null +++ b/benchmark/misc/timers.js @@ -0,0 +1,40 @@ +var common = require('../common.js'); + +var bench = common.createBenchmark(main, { + thousands: [500], + type: ['depth', 'breadth'] +}); + +function main(conf) { + var n = +conf.thousands * 1e3; + if (conf.type === 'breadth') + breadth(n); + else + depth(n); +} + +function depth(N) { + var n = 0; + bench.start(); + setTimeout(cb); + function cb() { + n++; + if (n === N) + bench.end(N / 1e3); + else + setTimeout(cb); + } +} + +function breadth(N) { + var n = 0; + bench.start(); + function cb() { + n++; + if (n === N) + bench.end(N / 1e3); + } + for (var i = 0; i < N; i++) { + setTimeout(cb); + } +} diff --git a/benchmark/timers.js b/benchmark/timers.js deleted file mode 100644 index 095cca119b..0000000000 --- a/benchmark/timers.js +++ /dev/null @@ -1,5 +0,0 @@ -function next (i) { - if (i <= 0) return; - setTimeout(function () { next(i-1); }, 1); -} -next(700); From fef35fc4f1da9e06790d5b3b34b3bcdb085dd3a2 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:03:42 -0800 Subject: [PATCH 24/45] bench: Remove settimeout (Covered by misc/timeout.js) --- benchmark/settimeout.js | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 benchmark/settimeout.js diff --git a/benchmark/settimeout.js b/benchmark/settimeout.js deleted file mode 100644 index dd52dc90e9..0000000000 --- a/benchmark/settimeout.js +++ /dev/null @@ -1,15 +0,0 @@ -console.log("wait..."); -var done = 0; -var N = 5000000; -var begin = new Date(); -for (var i = 0; i < N; i++) { - setTimeout(function () { - if (++done == N) { - var end = new Date(); - console.log("smaller is better"); - console.log("startup: %d", start - begin); - console.log("done: %d", end - start); - } - }, 1000); -} -var start = new Date(); From 2a2942bd7f39f687e61188fc593ab9d23ceff1c8 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:07:37 -0800 Subject: [PATCH 25/45] bench: Move string_creation into misc --- benchmark/misc/string_creation.js | 16 ++++++++++++++++ benchmark/string_creation.js | 6 ------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 benchmark/misc/string_creation.js delete mode 100644 benchmark/string_creation.js diff --git a/benchmark/misc/string_creation.js b/benchmark/misc/string_creation.js new file mode 100644 index 0000000000..74dabd66c0 --- /dev/null +++ b/benchmark/misc/string_creation.js @@ -0,0 +1,16 @@ + +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + millions: [100] +}) + +function main(conf) { + var n = +conf.millions * 1e6; + bench.start(); + var s; + for (var i = 0; i < n; i++) { + s = '01234567890'; + s[1] = "a"; + } + bench.end(n / 1e6); +} diff --git a/benchmark/string_creation.js b/benchmark/string_creation.js deleted file mode 100644 index 7f81ec1090..0000000000 --- a/benchmark/string_creation.js +++ /dev/null @@ -1,6 +0,0 @@ - - -for (var i = 0; i < 9e7; i++) { - s = '01234567890'; - s[1] = "a"; -} From 844b33205c77fe32d2aae76209eb444044b9aeff Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:10:12 -0800 Subject: [PATCH 26/45] bench: Move v8_bench into misc --- benchmark/{ => misc}/v8_bench.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename benchmark/{ => misc}/v8_bench.js (61%) diff --git a/benchmark/v8_bench.js b/benchmark/misc/v8_bench.js similarity index 61% rename from benchmark/v8_bench.js rename to benchmark/misc/v8_bench.js index fbd3748ca2..8bf49f3f61 100644 --- a/benchmark/v8_bench.js +++ b/benchmark/misc/v8_bench.js @@ -3,9 +3,12 @@ var fs = require('fs'); var path = require('path'); var vm = require('vm'); -var dir = path.join(__dirname, '..', 'deps', 'v8', 'benchmarks'); +var dir = path.join(__dirname, '..', '..', 'deps', 'v8', 'benchmarks'); -global.print = console.log; +global.print = function(s) { + if (s === '----') return; + console.log('misc/v8_bench.js %s', s); +}; global.load = function (x) { var source = fs.readFileSync(path.join(dir, x), 'utf8'); From 6d116be7cf26abf3a4940e9e75ec329248de96d8 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:55:36 -0800 Subject: [PATCH 27/45] bench: Move fs-readfile.js to fs/readfile.js --- benchmark/fs-readfile.js | 72 ---------------------------------------- benchmark/fs/readfile.js | 48 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 72 deletions(-) delete mode 100644 benchmark/fs-readfile.js create mode 100644 benchmark/fs/readfile.js diff --git a/benchmark/fs-readfile.js b/benchmark/fs-readfile.js deleted file mode 100644 index 3aa72e1a45..0000000000 --- a/benchmark/fs-readfile.js +++ /dev/null @@ -1,72 +0,0 @@ -// Call fs.readFile over and over again really fast. -// Then see how many times it got called. -// Yes, this is a silly benchmark. Most benchmarks are silly. - -var path = require('path'); -var filename = path.resolve(__dirname, 'http.sh'); -var fs = require('fs'); -var count = 0; -var go = true; -var len = -1; -var assert = require('assert'); - -var concurrency = 1; -var encoding = null; -var time = 10; - -for (var i = 2; i < process.argv.length; i++) { - var arg = process.argv[i]; - if (arg.match(/^-e$/)) { - encoding = process.argv[++i] || null; - } else if (arg.match(/^-c$/)) { - concurrency = ~~process.argv[++i]; - if (concurrency < 1) concurrency = 1; - } else if (arg === '-t') { - time = ~~process.argv[++i]; - if (time < 1) time = 1; - } -} - - -setTimeout(function() { - go = false; -}, time * 1000); - -function round(n) { - return Math.floor(n * 100) / 100; -} - -var start = process.hrtime(); -while (concurrency--) readFile(); - -function readFile() { - if (!go) { - process.stdout.write('\n'); - console.log('read the file %d times (higher is better)', count); - var end = process.hrtime(); - var elapsed = [end[0] - start[0], end[1] - start[1]]; - var ns = elapsed[0] * 1E9 + elapsed[1]; - var nsper = round(ns / count); - console.log('%d ns per read (lower is better)', nsper); - var readsper = round(count / (ns / 1E9)); - console.log('%d reads per sec (higher is better)', readsper); - process.exit(0); - return; - } - - if (!(count % 1000)) { - process.stdout.write('.'); - } - - if (encoding) fs.readFile(filename, encoding, then); - else fs.readFile(filename, then); - - function then(er, data) { - assert.ifError(er); - count++; - // basic sanity test: we should get the same number of bytes each time. - if (count === 1) len = data.length; - else assert(len === data.length); - readFile(); - } -} diff --git a/benchmark/fs/readfile.js b/benchmark/fs/readfile.js new file mode 100644 index 0000000000..38548ef647 --- /dev/null +++ b/benchmark/fs/readfile.js @@ -0,0 +1,48 @@ +// Call fs.readFile over and over again really fast. +// Then see how many times it got called. +// Yes, this is a silly benchmark. Most benchmarks are silly. + +var path = require('path'); +var common = require('../common.js'); +var filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); +var fs = require('fs'); + +var bench = common.createBenchmark(main, { + dur: [1, 3], + len: [1024, 16 * 1024 * 1024], + concurrent: [1, 10] +}); + +function main(conf) { + var len = +conf.len; + try { fs.unlinkSync(filename); } catch (e) {} + var data = new Buffer(len); + data.fill('x'); + fs.writeFileSync(filename, data); + data = null; + + var reads = 0; + bench.start(); + setTimeout(function() { + bench.end(reads); + try { fs.unlinkSync(filename); } catch (e) {} + }, +conf.dur * 1000); + + function read() { + fs.readFile(filename, afterRead); + } + + function afterRead(er, data) { + if (er) + throw er; + + if (data.length !== len) + throw new Error('wrong number of bytes returned'); + + reads++; + read(); + } + + var cur = +conf.concurrent; + while (cur--) read(); +} From 1fc6f993402f9b0f5b291c104e6edf379d75ba21 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 21:05:43 -0800 Subject: [PATCH 28/45] bench: Add read-stream throughput --- benchmark/fs/read-stream-throughput.js | 87 ++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 benchmark/fs/read-stream-throughput.js diff --git a/benchmark/fs/read-stream-throughput.js b/benchmark/fs/read-stream-throughput.js new file mode 100644 index 0000000000..af9a235b91 --- /dev/null +++ b/benchmark/fs/read-stream-throughput.js @@ -0,0 +1,87 @@ +// test the througput of the fs.WriteStream class. + +var path = require('path'); +var common = require('../common.js'); +var filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); +var fs = require('fs'); +var filesize = 1000 * 1024 * 1024; +var assert = require('assert'); + +var type, encoding, size; + +var bench = common.createBenchmark(main, { + type: ['buf', 'asc', 'utf'], + size: [1024, 4096, 65535, 1024*1024] +}); + +function main(conf) { + type = conf.type; + size = +conf.size; + + switch (type) { + case 'buf': + encoding = null; + break; + case 'asc': + encoding = 'ascii'; + break; + case 'utf': + encoding = 'utf8'; + break; + default: + throw new Error('invalid type'); + } + + makeFile(runTest); +} + +function runTest() { + assert(fs.statSync(filename).size === filesize); + var rs = fs.createReadStream(filename, { + bufferSize: size, + encoding: encoding + }); + + rs.on('open', function() { + bench.start(); + }); + + var bytes = 0; + rs.on('data', function(chunk) { + bytes += chunk.length; + }); + + rs.on('end', function() { + try { fs.unlinkSync(filename); } catch (e) {} + // MB/sec + bench.end(bytes / (1024 * 1024)); + }); +} + +function makeFile() { + var buf = new Buffer(filesize / 1024); + if (encoding === 'utf8') { + // ü + for (var i = 0; i < buf.length; i++) { + buf[i] = i % 2 === 0 ? 0xC3 : 0xBC; + } + } else if (encoding === 'ascii') { + buf.fill('a'); + } else { + buf.fill('x'); + } + + try { fs.unlinkSync(filename); } catch (e) {} + var w = 1024; + var ws = fs.createWriteStream(filename); + ws.on('close', runTest); + ws.on('drain', write); + write(); + function write() { + do { + w--; + } while (false !== ws.write(buf) && w > 0); + if (w === 0) + ws.end(); + } +} From 2a64edb025b5b62315748faecd76f8382755fc34 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 10:42:23 -0800 Subject: [PATCH 29/45] bench: Add fs write stream throughput --- benchmark/fs/write-stream-throughput.js | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 benchmark/fs/write-stream-throughput.js diff --git a/benchmark/fs/write-stream-throughput.js b/benchmark/fs/write-stream-throughput.js new file mode 100644 index 0000000000..d37299ab38 --- /dev/null +++ b/benchmark/fs/write-stream-throughput.js @@ -0,0 +1,78 @@ +// test the througput of the fs.WriteStream class. + +var path = require('path'); +var common = require('../common.js'); +var filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); +var fs = require('fs'); + +var bench = common.createBenchmark(main, { + dur: [1, 3], + type: ['buf', 'asc', 'utf'], + size: [2, 1024, 65535, 1024 * 1024] +}); + +function main(conf) { + var dur = +conf.dur; + var type = conf.type; + var size = +conf.size; + var encoding; + + var chunk; + switch (type) { + case 'buf': + chunk = new Buffer(size); + chunk.fill('b'); + break; + case 'asc': + chunk = new Array(size + 1).join('a'); + encoding = 'ascii'; + break; + case 'utf': + chunk = new Array(Math.ceil(size/2) + 1).join('ü'); + encoding = 'utf8'; + break; + default: + throw new Error('invalid type'); + } + + try { fs.unlinkSync(filename); } catch (e) {} + + var started = false; + var ending = false; + var ended = false; + setTimeout(function() { + ending = true; + f.end(); + }, dur * 1000); + + var f = fs.createWriteStream(filename); + f.on('drain', write); + f.on('open', write); + f.on('close', done); + f.on('finish', function() { + ended = true; + var written = fs.statSync(filename).size / 1024; + try { fs.unlinkSync(filename); } catch (e) {} + bench.end(written / 1024); + }); + + + function write() { + // don't try to write after we end, even if a 'drain' event comes. + // v0.8 streams are so sloppy! + if (ending) + return; + + if (!started) { + started = true; + bench.start(); + } + + while (false !== f.write(chunk, encoding)); + } + + function done() { + if (!ended) + f.emit('finish'); + } +} From 8a3f52170e1d5b0539f979894dc76a48d6d54a46 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 21:33:22 -0800 Subject: [PATCH 30/45] bench: Remove io.js Better covered by the other benchmark/fs scripts. --- benchmark/io.js | 109 ------------------------------------------------ 1 file changed, 109 deletions(-) delete mode 100644 benchmark/io.js diff --git a/benchmark/io.js b/benchmark/io.js deleted file mode 100644 index 1c18e05f61..0000000000 --- a/benchmark/io.js +++ /dev/null @@ -1,109 +0,0 @@ -var fs = require('fs'); -var util = require('util'); -var Buffer = require('buffer').Buffer; - -var path = "/tmp/wt.dat"; -var tsize = 1000 * 1048576; -var bsizes = [1024, 4096, 8192, 16384, 32768, 65536]; - -function bufit(size) { - var buf = new Buffer(size); - for (var i = 0; i 0) { - //if (remaining % 90000 == 0) console.error("remaining: %d", remaining); - //process.nextTick(dowrite); - } else { - s.emit('done') - s.end(); - } - } - - s.on('drain', function () { - dowrite(); - if (c++ % 2000 == 0) util.print("."); - }); - - dowrite(); - - return s; -} - -function readtest(size, bsize) { - var s = fs.createReadStream(path, {'flags': 'r', 'encoding': 'binary', 'mode': 0644, 'bufferSize': bsize}); - s.addListener("data", function (chunk) { - // got a chunk... - - }); - return s; -} - -function wt(tsize, bsize, done) { - var start = Date.now(); - s = writetest(tsize, bsize); - s.addListener('close', function() { - var end = Date.now(); - var diff = end - start; - console.log('Wrote '+ tsize +' bytes in '+ diff/1000 +'s using '+ bsize +' byte buffers: '+ ((tsize/(diff/1000)) / 1048576) +' mB/s'); - done(); - }); -} - -function rt(tsize, bsize, done) { - var start = Date.now(); - s = readtest(tsize, bsize); - s.addListener('close', function() { - var end = Date.now(); - var diff = end - start; - console.log('Read '+ tsize +' bytes in '+ diff/1000 +'s using '+ bsize +' byte buffers: '+ ((tsize/(diff/1000)) / 1048576) +' mB/s'); - done(); - }); -} - -var bs= 0; - -function nextwt() { - if (bsizes.length <= bs) { - bs = 0; - nextrt(); - return; - } - wt(tsize, bsizes[bs], nextwt); - bs += 1; -} - -function nextrt() { - if (bsizes.length <= bs) { - fs.unlink(path, function (err) { - if (err) throw err; - console.log('All done!'); - }); - return; - } - rt(tsize, bsizes[bs], nextrt); - bs += 1; -} - -nextwt(); From 8c719f7c71ed415d7c5e2952782d1352d14a9389 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 21:33:37 -0800 Subject: [PATCH 31/45] bench: Make io.c output easier to read --- benchmark/io.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmark/io.c b/benchmark/io.c index 66d90d5a1d..3b31eb02a9 100644 --- a/benchmark/io.c +++ b/benchmark/io.c @@ -45,7 +45,6 @@ static void writetest(int size, size_t bsize) for (i = 0; i < size; i += bsize) { int rv = write(fd, buf, bsize); - if (c++ % 2000 == 0) fprintf(stderr, "."); if (rv < 0) { perror("write failed"); exit(254); @@ -66,7 +65,7 @@ static void writetest(int size, size_t bsize) elapsed = (end - start) / 1e6; mbps = ((tsize/elapsed)) / 1048576; - fprintf(stderr, "\nWrote %d bytes in %03fs using %ld byte buffers: %03fmB/s\n", size, elapsed, bsize, mbps); + fprintf(stderr, "Wrote %d bytes in %03fs using %ld byte buffers: %03f\n", size, elapsed, bsize, mbps); } void readtest(int size, size_t bsize) From bafc51c0f9bbd178e347ff2ea9d110e2f1a4202e Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 14:38:00 -0800 Subject: [PATCH 32/45] bench: Move tls-connect into benchmark/tls Also, make it work properly with current node. --- benchmark/tls-connect.js | 86 ------------------------------------ benchmark/tls/tls-connect.js | 63 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 86 deletions(-) delete mode 100644 benchmark/tls-connect.js create mode 100644 benchmark/tls/tls-connect.js diff --git a/benchmark/tls-connect.js b/benchmark/tls-connect.js deleted file mode 100644 index 512adca142..0000000000 --- a/benchmark/tls-connect.js +++ /dev/null @@ -1,86 +0,0 @@ - -var assert = require('assert'), - fs = require('fs'), - path = require('path'), - tls = require('tls'); - - -var target_connections = 10000, - concurrency = 10; - -for (var i = 2; i < process.argv.length; i++) { - switch (process.argv[i]) { - case '-c': - concurrency = ~~process.argv[++i]; - break; - - case '-n': - target_connections = ~~process.argv[++i]; - break; - - default: - throw new Error('Invalid flag: ' + process.argv[i]); - } -} - - -var cert_dir = path.resolve(__dirname, '../test/fixtures'), - options = { key: fs.readFileSync(cert_dir + '/test_key.pem'), - cert: fs.readFileSync(cert_dir + '/test_cert.pem'), - ca: [ fs.readFileSync(cert_dir + '/test_ca.pem') ] }; - -var server = tls.createServer(options, onConnection); -server.listen(8000); - - -var initiated_connections = 0, - server_connections = 0, - client_connections = 0, - start = Date.now(); - -for (var i = 0; i < concurrency; i++) - makeConnection(); - - -process.on('exit', onExit); - - -function makeConnection() { - if (initiated_connections >= target_connections) - return; - - initiated_connections++; - - var conn = tls.connect(8000, function() { - client_connections++; - - if (client_connections % 100 === 0) - console.log(client_connections + ' of ' + target_connections + - ' connections made'); - - conn.end(); - makeConnection(); - }); -} - - -function onConnection(conn) { - server_connections++; - - if (server_connections === target_connections) - server.close(); -} - - -function onExit() { - var end = Date.now(), - s = (end - start) / 1000, - persec = Math.round(target_connections / s); - - assert.equal(initiated_connections, target_connections); - assert.equal(client_connections, target_connections); - assert.equal(server_connections, target_connections); - - console.log('%d connections in %d s', target_connections, s); - console.log('%d connections per second', persec); -} diff --git a/benchmark/tls/tls-connect.js b/benchmark/tls/tls-connect.js new file mode 100644 index 0000000000..b3bd3e3c7a --- /dev/null +++ b/benchmark/tls/tls-connect.js @@ -0,0 +1,63 @@ +var assert = require('assert'), + fs = require('fs'), + path = require('path'), + tls = require('tls'); + +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + concurrency: [1, 10], + dur: [1, 3] +}); + +var clientConn = 0; +var serverConn = 0; +var server; +var dur; +var concurrency; +var running = true; + +function main(conf) { + dur = +conf.dur; + concurrency = +conf.concurrency; + + var cert_dir = path.resolve(__dirname, '../../test/fixtures'), + options = { key: fs.readFileSync(cert_dir + '/test_key.pem'), + cert: fs.readFileSync(cert_dir + '/test_cert.pem'), + ca: [ fs.readFileSync(cert_dir + '/test_ca.pem') ] }; + + server = tls.createServer(options, onConnection); + server.listen(common.PORT, onListening); +} + +function onListening() { + setTimeout(done, dur * 1000); + bench.start(); + for (var i = 0; i < concurrency; i++) + makeConnection(); +} + +function onConnection(conn) { + serverConn++; +} + +function makeConnection() { + var conn = tls.connect({ port: common.PORT, + rejectUnauthorized: false }, function() { + clientConn++; + conn.on('error', function(er) { + console.error('client error', er); + throw er; + }); + conn.end(); + if (running) makeConnection(); + }); +} + +function done() { + running = false; + // it's only an established connection if they both saw it. + // because we destroy the server somewhat abruptly, these + // don't always match. Generally, serverConn will be + // the smaller number, but take the min just to be sure. + bench.end(Math.min(serverConn, clientConn)); +} From 0a406869df990d030d2383abc7f1388aead57d0c Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 18:27:02 -0800 Subject: [PATCH 33/45] bench: Replace tls-fragmentation with tls/throughput --- benchmark/tls-fragmentation.js | 63 ----------------------------- benchmark/tls/throughput.js | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 63 deletions(-) delete mode 100644 benchmark/tls-fragmentation.js create mode 100644 benchmark/tls/throughput.js diff --git a/benchmark/tls-fragmentation.js b/benchmark/tls-fragmentation.js deleted file mode 100644 index 5abe093cd2..0000000000 --- a/benchmark/tls-fragmentation.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -if (!process.versions.openssl) { - console.error('Skipping because node compiled without OpenSSL.'); - process.exit(0); -} - -var common = require('../common'); -var assert = require('assert'); -var tls = require('tls'); -var fs = require('fs'); -var path = require('path'); - -var options = { - key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')), - cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem')) -}; - -var fragment = 'fr'; -var dataSize = 1024 * 1024; -var sent = 0; -var received = 0; - -var server = tls.createServer(options, function (stream) { - for (sent = 0; sent <= dataSize; sent += fragment.length) { - stream.write(fragment); - } - stream.end(); -}); - -server.listen(common.PORT, function () { - var client = tls.connect(common.PORT, function () { - client.on('data', function (data) { - received += data.length; - }); - client.on('end', function () { - server.close(); - }); - }); -}); - -process.on('exit', function () { - assert.equal(sent, received); -}); diff --git a/benchmark/tls/throughput.js b/benchmark/tls/throughput.js new file mode 100644 index 0000000000..3896382492 --- /dev/null +++ b/benchmark/tls/throughput.js @@ -0,0 +1,74 @@ +var common = require('../common.js'); +var bench = common.createBenchmark(main, { + dur: [1, 3], + type: ['buf', 'asc', 'utf'], + size: [2, 1024, 1024 * 1024] +}); + +var dur, type, encoding, size; +var server; + +var path = require('path'); +var fs = require('fs'); +var cert_dir = path.resolve(__dirname, '../../test/fixtures'); +var options; +var tls = require('tls'); + +function main(conf) { + dur = +conf.dur; + type = conf.type; + size = +conf.size; + + var chunk; + switch (type) { + case 'buf': + chunk = new Buffer(size); + chunk.fill('b'); + break; + case 'asc': + chunk = new Array(size + 1).join('a'); + encoding = 'ascii'; + break; + case 'utf': + chunk = new Array(size/2 + 1).join('ü'); + encoding = 'utf8'; + break; + default: + throw new Error('invalid type'); + } + + options = { key: fs.readFileSync(cert_dir + '/test_key.pem'), + cert: fs.readFileSync(cert_dir + '/test_cert.pem'), + ca: [ fs.readFileSync(cert_dir + '/test_ca.pem') ] }; + + server = tls.createServer(options, onConnection); + setTimeout(done, dur * 1000); + server.listen(common.PORT, function() { + var opt = { port: common.PORT, rejectUnauthorized: false }; + var conn = tls.connect(opt, function() { + bench.start(); + conn.on('drain', write); + write(); + }); + + function write() { + var i = 0; + while (false !== conn.write(chunk, encoding)); + } + }); + + var received = 0; + function onConnection(conn) { + conn.on('data', function(chunk) { + received += chunk.length; + }); + } + + function done() { + var mbits = (received * 8) / (1024 * 1024); + bench.end(mbits); + conn.destroy(); + server.close(); + } +} + From 7d517458278bd364935e58abcae0944ea0766157 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:04:29 -0800 Subject: [PATCH 34/45] bench: Remove old run script --- benchmark/run.js | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 benchmark/run.js diff --git a/benchmark/run.js b/benchmark/run.js deleted file mode 100644 index 2891687376..0000000000 --- a/benchmark/run.js +++ /dev/null @@ -1,31 +0,0 @@ -var path = require("path"); -var util = require("util"); -var childProcess = require("child_process"); -var benchmarks = [ "timers.js" - , "process_loop.js" - , "static_http_server.js" - ]; - -var benchmarkDir = path.dirname(__filename); - -function exec (script, callback) { - var start = new Date(); - var child = childProcess.spawn(process.argv[0], [path.join(benchmarkDir, script)]); - child.addListener("exit", function (code) { - var elapsed = new Date() - start; - callback(elapsed, code); - }); -} - -function runNext (i) { - if (i >= benchmarks.length) return; - util.print(benchmarks[i] + ": "); - exec(benchmarks[i], function (elapsed, code) { - if (code != 0) { - console.log("ERROR "); - } - console.log(elapsed); - runNext(i+1); - }); -}; -runNext(0); From 0e59efd079d6369cecd78719379cad2c3254e90d Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 11 Feb 2013 23:22:12 -0800 Subject: [PATCH 35/45] make: Add benchmark make targets --- Makefile | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 779547bbcb..671fc7d873 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ NINJA ?= ninja DESTDIR ?= SIGN ?= +NODE ?= ./node + # Default to verbose builds. # To do quiet/pretty builds, run `make V=` to set V to an empty string, # or set the V environment variable to an empty string. @@ -311,7 +313,31 @@ dist-upload: $(TARBALL) $(PKG) scp $(TARBALL) node@nodejs.org:~/web/nodejs.org/dist/$(VERSION)/$(TARBALL) scp $(PKG) node@nodejs.org:~/web/nodejs.org/dist/$(VERSION)/$(TARNAME).pkg -bench: +bench-net: all + @$(NODE) benchmark/common.js net + +bench-tls: all + @$(NODE) benchmark/common.js tls + +bench-http: all + @$(NODE) benchmark/common.js http + +bench-fs: all + @$(NODE) benchmark/common.js fs + +bench-misc: all + @$(MAKE) -C benchmark/misc/function_call/ + @$(NODE) benchmark/common.js misc + +bench-array: all + @$(NODE) benchmark/common.js arrays + +bench-buffer: all + @$(NODE) benchmark/common.js buffers + +bench: bench-misc bench-net bench-http bench-fs bench-array bench-buffer bench-tls + +bench-http-simple: benchmark/http_simple_bench.sh bench-idle: From 7658f4c29c162819f72219bacf73aa840c1e59e4 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 12 Feb 2013 00:13:18 -0800 Subject: [PATCH 36/45] bench: Fail gracefully if function_call binding fails --- benchmark/misc/function_call/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/benchmark/misc/function_call/index.js b/benchmark/misc/function_call/index.js index ac05478a3e..fc8542a068 100644 --- a/benchmark/misc/function_call/index.js +++ b/benchmark/misc/function_call/index.js @@ -6,7 +6,16 @@ var assert = require('assert'); var common = require('../../common.js'); -var binding = require('./build/Release/binding'); +// this fails when we try to open with a different version of node, +// which is quite common for benchmarks. so in that case, just +// abort quietly. + +try { + var binding = require('./build/Release/binding'); +} catch (er) { + console.error('misc/function_call.js Binding failed to load'); + process.exit(0); +} var cxx = binding.hello; var c = 0; From 087c43796178733da54bff9e8291a5a312c466f1 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 10:47:29 -0800 Subject: [PATCH 37/45] bench: Add --html to compare script --- benchmark/compare.js | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/benchmark/compare.js b/benchmark/compare.js index 05733a1e37..5b4beda3f1 100644 --- a/benchmark/compare.js +++ b/benchmark/compare.js @@ -1,7 +1,9 @@ -var usage = 'node benchmark/compare.js '; +var usage = 'node benchmark/compare.js [--html] [--red|-r] [--green|-g]'; var show = 'both'; var nodes = []; +var html = false; + for (var i = 2; i < process.argv.length; i++) { var arg = process.argv[i]; switch (arg) { @@ -11,12 +13,32 @@ for (var i = 2; i < process.argv.length; i++) { case '--green': case '-g': show = show === 'red' ? 'both' : 'green'; break; + case '--html': + html = true; + break; + case '-h': case '-?': case '--help': + console.log(usage); + process.exit(0); default: nodes.push(arg); break; } } +if (!html) { + var start = ''; + var green = '\033[1;32m'; + var red = '\033[1;31m'; + var reset = '\033[m'; + var end = ''; +} else { + var start = '
';
+  var green = '';
+  var red = '';
+  var reset = '';
+  var end = '
'; +} + var runBench = process.env.NODE_BENCH || 'bench'; if (nodes.length !== 2) @@ -79,11 +101,10 @@ function compare() { // each result is an object with {"foo.js arg=bar":12345,...} // compare each thing, and show which node did the best. // node[0] is shown in green, node[1] shown in red. - var green = '\033[1;32m'; - var red = '\033[1;31m'; - var reset = '\033[m'; var maxLen = -Infinity; var util = require('util'); + console.log(start); + Object.keys(results).map(function(bench) { var res = results[bench]; var n0 = res[nodes[0]]; @@ -113,4 +134,5 @@ function compare() { var dots = ' ' + new Array(Math.max(0, dotLen)).join('.') + ' '; console.log(l + dots + pct); }); + console.log(end); } From 035aa6b4cefc0b1d5bab8ffdcb5c63593ef8d190 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 10:48:55 -0800 Subject: [PATCH 38/45] bench: Show % change rather than % difference --- benchmark/compare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/compare.js b/benchmark/compare.js index 5b4beda3f1..8e1ab0963a 100644 --- a/benchmark/compare.js +++ b/benchmark/compare.js @@ -110,7 +110,7 @@ function compare() { var n0 = res[nodes[0]]; var n1 = res[nodes[1]]; - var pct = ((n0 - n1) / ((n0 + n1) / 2) * 100).toFixed(2); + var pct = ((n0 - n1) / n1 * 100).toFixed(2); var g = n0 > n1 ? green : ''; var r = n0 > n1 ? '' : red; From e850cbab1c83c9457f653c6241da5c74d2d0ab0e Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 12:19:08 -0800 Subject: [PATCH 39/45] tools: Add wrk for benchmarking http servers --- .gitignore | 2 + Makefile | 4 + tools/wrk/.gitignore | 2 + tools/wrk/LICENSE | 177 +++ tools/wrk/Makefile | 35 + tools/wrk/NOTICE | 115 ++ tools/wrk/README | 37 + tools/wrk/src/ae.c | 435 ++++++++ tools/wrk/src/ae.h | 118 ++ tools/wrk/src/ae_epoll.c | 130 +++ tools/wrk/src/ae_evport.c | 315 ++++++ tools/wrk/src/ae_kqueue.c | 132 +++ tools/wrk/src/ae_select.c | 99 ++ tools/wrk/src/aprintf.c | 27 + tools/wrk/src/aprintf.h | 6 + tools/wrk/src/config.h | 13 + tools/wrk/src/http_parser.c | 2058 +++++++++++++++++++++++++++++++++++ tools/wrk/src/http_parser.h | 317 ++++++ tools/wrk/src/stats.c | 73 ++ tools/wrk/src/stats.h | 21 + tools/wrk/src/tinymt64.c | 129 +++ tools/wrk/src/tinymt64.h | 210 ++++ tools/wrk/src/units.c | 96 ++ tools/wrk/src/units.h | 10 + tools/wrk/src/wrk.c | 482 ++++++++ tools/wrk/src/wrk.h | 75 ++ tools/wrk/src/zmalloc.c | 287 +++++ tools/wrk/src/zmalloc.h | 83 ++ 28 files changed, 5488 insertions(+) create mode 100644 tools/wrk/.gitignore create mode 100644 tools/wrk/LICENSE create mode 100644 tools/wrk/Makefile create mode 100644 tools/wrk/NOTICE create mode 100644 tools/wrk/README create mode 100644 tools/wrk/src/ae.c create mode 100644 tools/wrk/src/ae.h create mode 100644 tools/wrk/src/ae_epoll.c create mode 100644 tools/wrk/src/ae_evport.c create mode 100644 tools/wrk/src/ae_kqueue.c create mode 100644 tools/wrk/src/ae_select.c create mode 100644 tools/wrk/src/aprintf.c create mode 100644 tools/wrk/src/aprintf.h create mode 100644 tools/wrk/src/config.h create mode 100644 tools/wrk/src/http_parser.c create mode 100644 tools/wrk/src/http_parser.h create mode 100644 tools/wrk/src/stats.c create mode 100644 tools/wrk/src/stats.h create mode 100644 tools/wrk/src/tinymt64.c create mode 100644 tools/wrk/src/tinymt64.h create mode 100644 tools/wrk/src/units.c create mode 100644 tools/wrk/src/units.h create mode 100644 tools/wrk/src/wrk.c create mode 100644 tools/wrk/src/wrk.h create mode 100644 tools/wrk/src/zmalloc.c create mode 100644 tools/wrk/src/zmalloc.h diff --git a/.gitignore b/.gitignore index 2d29bbc265..b43635344a 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ deps/openssl/openssl.xml # build/release artifacts /*.tar.gz /SHASUMS.txt* + +/tools/wrk/wrk diff --git a/Makefile b/Makefile index 671fc7d873..887b285454 100644 --- a/Makefile +++ b/Makefile @@ -313,6 +313,10 @@ dist-upload: $(TARBALL) $(PKG) scp $(TARBALL) node@nodejs.org:~/web/nodejs.org/dist/$(VERSION)/$(TARBALL) scp $(PKG) node@nodejs.org:~/web/nodejs.org/dist/$(VERSION)/$(TARNAME).pkg +wrk: tools/wrk/wrk +tools/wrk/wrk: + $(MAKE) -C tools/wrk/ + bench-net: all @$(NODE) benchmark/common.js net diff --git a/tools/wrk/.gitignore b/tools/wrk/.gitignore new file mode 100644 index 0000000000..ae66e30c0e --- /dev/null +++ b/tools/wrk/.gitignore @@ -0,0 +1,2 @@ +obj/* +/wrk diff --git a/tools/wrk/LICENSE b/tools/wrk/LICENSE new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/tools/wrk/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/tools/wrk/Makefile b/tools/wrk/Makefile new file mode 100644 index 0000000000..59dc693de3 --- /dev/null +++ b/tools/wrk/Makefile @@ -0,0 +1,35 @@ +CFLAGS := -std=c99 -Wall -O2 -pthread +LDFLAGS := -pthread +LIBS := -lm +ifeq ($(shell uname),SunOS) +LIBS += -lnsl -lsocket -lresolv +endif + +SRC := wrk.c aprintf.c stats.c units.c ae.c zmalloc.c http_parser.c tinymt64.c +BIN := wrk + +ODIR := obj +OBJ := $(patsubst %.c,$(ODIR)/%.o,$(SRC)) + +all: $(BIN) + +clean: + $(RM) $(BIN) obj/* + +$(BIN): $(OBJ) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +$(OBJ): config.h Makefile | $(ODIR) + +$(ODIR): + @mkdir $@ + +$(ODIR)/%.o : %.c + $(CC) $(CFLAGS) -c -o $@ $< + +.PHONY: all clean +.SUFFIXES: +.SUFFIXES: .c .o + +vpath %.c src +vpath %.h src diff --git a/tools/wrk/NOTICE b/tools/wrk/NOTICE new file mode 100644 index 0000000000..e6cfc3bd8e --- /dev/null +++ b/tools/wrk/NOTICE @@ -0,0 +1,115 @@ +========================================================================= +== NOTICE file corresponding to section 4(d) of the Apache License, == +== Version 2.0, in this case for the wrk distribution. == +========================================================================= + +wrk +Copyright 2012 Will Glozer, http://glozer.net + +========================================================================= +== Redis Event Library Notice == +========================================================================= + +This product includes software developed by Salvatore Sanfilippo and +other contributors to the redis project. + +Copyright (c) 2006-2010, Salvatore Sanfilippo +Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com + +Copyright (c) 2006-2009, Salvatore Sanfilippo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the * + distribution. + + * Neither the name of Redis nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================================================= +== HTTP Parser Notice == +========================================================================= + +This product includes software developed by Igor Sysoev, Joyent, Inc., +and other Node contributors. + +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +========================================================================= +== Tiny Mersenne Twister (TinyMT) Notice == +========================================================================= + +Copyright (c) 2011 Mutsuo Saito, Makoto Matsumoto, Hiroshima University +and The University of Tokyo. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + + * Neither the name of the Hiroshima University nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tools/wrk/README b/tools/wrk/README new file mode 100644 index 0000000000..a80768bc26 --- /dev/null +++ b/tools/wrk/README @@ -0,0 +1,37 @@ +wrk - a HTTP benchmarking tool + + wrk is a modern HTTP benchmarking tool capable of generating significant + load when run on a single multi-core CPU. It combines a multithreaded + design with scalable event notification systems such as epoll and kqueue. + +Basic Usage + + wrk -t8 -c400 -r10m http://localhost:8080/index.html + + This runs wrk with 8 threads, keeping 400 connections open, and making a + total of 10 million HTTP GET requests to http://localhost:8080/index.html + + Output: + + Making 10000000 requests to http://localhost:8080/index.html + 8 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 439.75us 350.49us 7.60ms 92.88% + Req/Sec 61.13k 8.26k 72.00k 87.54% + 10000088 requests in 19.87s, 3.42GB read + Requests/sec: 503396.23 + Transfer/sec: 176.16MB + +Benchmarking Tips + + The machine running wrk must have a sufficient number of ephemeral ports + available and closed sockets should be recycled quickly. To handle the + initial connection burst the server's listen(2) backlog should be greater + than the number of concurrent connections being tested. + +Acknowledgements + + wrk contains code from a number of open source projects including the + 'ae' event loop from redis, the nginx/joyent/node.js 'http-parser' and + the Tiny Mersenne Twister PRNG. Please consult the NOTICE file for + licensing details. diff --git a/tools/wrk/src/ae.c b/tools/wrk/src/ae.c new file mode 100644 index 0000000000..6ca9a51538 --- /dev/null +++ b/tools/wrk/src/ae.c @@ -0,0 +1,435 @@ +/* A simple event-driven programming library. Originally I wrote this code + * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated + * it in form of a library for easy reuse. + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ae.h" +#include "zmalloc.h" +#include "config.h" + +/* Include the best multiplexing layer supported by this system. + * The following should be ordered by performances, descending. */ +#ifdef HAVE_EVPORT +#include "ae_evport.c" +#else + #ifdef HAVE_EPOLL + #include "ae_epoll.c" + #else + #ifdef HAVE_KQUEUE + #include "ae_kqueue.c" + #else + #include "ae_select.c" + #endif + #endif +#endif + +aeEventLoop *aeCreateEventLoop(int setsize) { + aeEventLoop *eventLoop; + int i; + + if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err; + eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize); + eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize); + if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; + eventLoop->setsize = setsize; + eventLoop->lastTime = time(NULL); + eventLoop->timeEventHead = NULL; + eventLoop->timeEventNextId = 0; + eventLoop->stop = 0; + eventLoop->maxfd = -1; + eventLoop->beforesleep = NULL; + if (aeApiCreate(eventLoop) == -1) goto err; + /* Events with mask == AE_NONE are not set. So let's initialize the + * vector with it. */ + for (i = 0; i < setsize; i++) + eventLoop->events[i].mask = AE_NONE; + return eventLoop; + +err: + if (eventLoop) { + zfree(eventLoop->events); + zfree(eventLoop->fired); + zfree(eventLoop); + } + return NULL; +} + +void aeDeleteEventLoop(aeEventLoop *eventLoop) { + aeApiFree(eventLoop); + zfree(eventLoop->events); + zfree(eventLoop->fired); + zfree(eventLoop); +} + +void aeStop(aeEventLoop *eventLoop) { + eventLoop->stop = 1; +} + +int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, + aeFileProc *proc, void *clientData) +{ + if (fd >= eventLoop->setsize) { + errno = ERANGE; + return AE_ERR; + } + aeFileEvent *fe = &eventLoop->events[fd]; + + if (aeApiAddEvent(eventLoop, fd, mask) == -1) + return AE_ERR; + fe->mask |= mask; + if (mask & AE_READABLE) fe->rfileProc = proc; + if (mask & AE_WRITABLE) fe->wfileProc = proc; + fe->clientData = clientData; + if (fd > eventLoop->maxfd) + eventLoop->maxfd = fd; + return AE_OK; +} + +void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) +{ + if (fd >= eventLoop->setsize) return; + aeFileEvent *fe = &eventLoop->events[fd]; + + if (fe->mask == AE_NONE) return; + fe->mask = fe->mask & (~mask); + if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { + /* Update the max fd */ + int j; + + for (j = eventLoop->maxfd-1; j >= 0; j--) + if (eventLoop->events[j].mask != AE_NONE) break; + eventLoop->maxfd = j; + } + aeApiDelEvent(eventLoop, fd, mask); +} + +int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { + if (fd >= eventLoop->setsize) return 0; + aeFileEvent *fe = &eventLoop->events[fd]; + + return fe->mask; +} + +static void aeGetTime(long *seconds, long *milliseconds) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + *seconds = tv.tv_sec; + *milliseconds = tv.tv_usec/1000; +} + +static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) { + long cur_sec, cur_ms, when_sec, when_ms; + + aeGetTime(&cur_sec, &cur_ms); + when_sec = cur_sec + milliseconds/1000; + when_ms = cur_ms + milliseconds%1000; + if (when_ms >= 1000) { + when_sec ++; + when_ms -= 1000; + } + *sec = when_sec; + *ms = when_ms; +} + +long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, + aeTimeProc *proc, void *clientData, + aeEventFinalizerProc *finalizerProc) +{ + long long id = eventLoop->timeEventNextId++; + aeTimeEvent *te; + + te = zmalloc(sizeof(*te)); + if (te == NULL) return AE_ERR; + te->id = id; + aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); + te->timeProc = proc; + te->finalizerProc = finalizerProc; + te->clientData = clientData; + te->next = eventLoop->timeEventHead; + eventLoop->timeEventHead = te; + return id; +} + +int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) +{ + aeTimeEvent *te, *prev = NULL; + + te = eventLoop->timeEventHead; + while(te) { + if (te->id == id) { + if (prev == NULL) + eventLoop->timeEventHead = te->next; + else + prev->next = te->next; + if (te->finalizerProc) + te->finalizerProc(eventLoop, te->clientData); + zfree(te); + return AE_OK; + } + prev = te; + te = te->next; + } + return AE_ERR; /* NO event with the specified ID found */ +} + +/* Search the first timer to fire. + * This operation is useful to know how many time the select can be + * put in sleep without to delay any event. + * If there are no timers NULL is returned. + * + * Note that's O(N) since time events are unsorted. + * Possible optimizations (not needed by Redis so far, but...): + * 1) Insert the event in order, so that the nearest is just the head. + * Much better but still insertion or deletion of timers is O(N). + * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). + */ +static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) +{ + aeTimeEvent *te = eventLoop->timeEventHead; + aeTimeEvent *nearest = NULL; + + while(te) { + if (!nearest || te->when_sec < nearest->when_sec || + (te->when_sec == nearest->when_sec && + te->when_ms < nearest->when_ms)) + nearest = te; + te = te->next; + } + return nearest; +} + +/* Process time events */ +static int processTimeEvents(aeEventLoop *eventLoop) { + int processed = 0; + aeTimeEvent *te; + long long maxId; + time_t now = time(NULL); + + /* If the system clock is moved to the future, and then set back to the + * right value, time events may be delayed in a random way. Often this + * means that scheduled operations will not be performed soon enough. + * + * Here we try to detect system clock skews, and force all the time + * events to be processed ASAP when this happens: the idea is that + * processing events earlier is less dangerous than delaying them + * indefinitely, and practice suggests it is. */ + if (now < eventLoop->lastTime) { + te = eventLoop->timeEventHead; + while(te) { + te->when_sec = 0; + te = te->next; + } + } + eventLoop->lastTime = now; + + te = eventLoop->timeEventHead; + maxId = eventLoop->timeEventNextId-1; + while(te) { + long now_sec, now_ms; + long long id; + + if (te->id > maxId) { + te = te->next; + continue; + } + aeGetTime(&now_sec, &now_ms); + if (now_sec > te->when_sec || + (now_sec == te->when_sec && now_ms >= te->when_ms)) + { + int retval; + + id = te->id; + retval = te->timeProc(eventLoop, id, te->clientData); + processed++; + /* After an event is processed our time event list may + * no longer be the same, so we restart from head. + * Still we make sure to don't process events registered + * by event handlers itself in order to don't loop forever. + * To do so we saved the max ID we want to handle. + * + * FUTURE OPTIMIZATIONS: + * Note that this is NOT great algorithmically. Redis uses + * a single time event so it's not a problem but the right + * way to do this is to add the new elements on head, and + * to flag deleted elements in a special way for later + * deletion (putting references to the nodes to delete into + * another linked list). */ + if (retval != AE_NOMORE) { + aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); + } else { + aeDeleteTimeEvent(eventLoop, id); + } + te = eventLoop->timeEventHead; + } else { + te = te->next; + } + } + return processed; +} + +/* Process every pending time event, then every pending file event + * (that may be registered by time event callbacks just processed). + * Without special flags the function sleeps until some file event + * fires, or when the next time event occurs (if any). + * + * If flags is 0, the function does nothing and returns. + * if flags has AE_ALL_EVENTS set, all the kind of events are processed. + * if flags has AE_FILE_EVENTS set, file events are processed. + * if flags has AE_TIME_EVENTS set, time events are processed. + * if flags has AE_DONT_WAIT set the function returns ASAP until all + * the events that's possible to process without to wait are processed. + * + * The function returns the number of events processed. */ +int aeProcessEvents(aeEventLoop *eventLoop, int flags) +{ + int processed = 0, numevents; + + /* Nothing to do? return ASAP */ + if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; + + /* Note that we want call select() even if there are no + * file events to process as long as we want to process time + * events, in order to sleep until the next time event is ready + * to fire. */ + if (eventLoop->maxfd != -1 || + ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { + int j; + aeTimeEvent *shortest = NULL; + struct timeval tv, *tvp; + + if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) + shortest = aeSearchNearestTimer(eventLoop); + if (shortest) { + long now_sec, now_ms; + + /* Calculate the time missing for the nearest + * timer to fire. */ + aeGetTime(&now_sec, &now_ms); + tvp = &tv; + tvp->tv_sec = shortest->when_sec - now_sec; + if (shortest->when_ms < now_ms) { + tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000; + tvp->tv_sec --; + } else { + tvp->tv_usec = (shortest->when_ms - now_ms)*1000; + } + if (tvp->tv_sec < 0) tvp->tv_sec = 0; + if (tvp->tv_usec < 0) tvp->tv_usec = 0; + } else { + /* If we have to check for events but need to return + * ASAP because of AE_DONT_WAIT we need to set the timeout + * to zero */ + if (flags & AE_DONT_WAIT) { + tv.tv_sec = tv.tv_usec = 0; + tvp = &tv; + } else { + /* Otherwise we can block */ + tvp = NULL; /* wait forever */ + } + } + + numevents = aeApiPoll(eventLoop, tvp); + for (j = 0; j < numevents; j++) { + aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; + int mask = eventLoop->fired[j].mask; + int fd = eventLoop->fired[j].fd; + int rfired = 0; + + /* note the fe->mask & mask & ... code: maybe an already processed + * event removed an element that fired and we still didn't + * processed, so we check if the event is still valid. */ + if (fe->mask & mask & AE_READABLE) { + rfired = 1; + fe->rfileProc(eventLoop,fd,fe->clientData,mask); + } + if (fe->mask & mask & AE_WRITABLE) { + if (!rfired || fe->wfileProc != fe->rfileProc) + fe->wfileProc(eventLoop,fd,fe->clientData,mask); + } + processed++; + } + } + /* Check time events */ + if (flags & AE_TIME_EVENTS) + processed += processTimeEvents(eventLoop); + + return processed; /* return the number of processed file/time events */ +} + +/* Wait for milliseconds until the given file descriptor becomes + * writable/readable/exception */ +int aeWait(int fd, int mask, long long milliseconds) { + struct pollfd pfd; + int retmask = 0, retval; + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + if (mask & AE_READABLE) pfd.events |= POLLIN; + if (mask & AE_WRITABLE) pfd.events |= POLLOUT; + + if ((retval = poll(&pfd, 1, milliseconds))== 1) { + if (pfd.revents & POLLIN) retmask |= AE_READABLE; + if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; + if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; + if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; + return retmask; + } else { + return retval; + } +} + +void aeMain(aeEventLoop *eventLoop) { + eventLoop->stop = 0; + while (!eventLoop->stop) { + if (eventLoop->beforesleep != NULL) + eventLoop->beforesleep(eventLoop); + aeProcessEvents(eventLoop, AE_ALL_EVENTS); + } +} + +char *aeGetApiName(void) { + return aeApiName(); +} + +void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { + eventLoop->beforesleep = beforesleep; +} diff --git a/tools/wrk/src/ae.h b/tools/wrk/src/ae.h new file mode 100644 index 0000000000..4d89502429 --- /dev/null +++ b/tools/wrk/src/ae.h @@ -0,0 +1,118 @@ +/* A simple event-driven programming library. Originally I wrote this code + * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated + * it in form of a library for easy reuse. + * + * Copyright (c) 2006-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __AE_H__ +#define __AE_H__ + +#define AE_OK 0 +#define AE_ERR -1 + +#define AE_NONE 0 +#define AE_READABLE 1 +#define AE_WRITABLE 2 + +#define AE_FILE_EVENTS 1 +#define AE_TIME_EVENTS 2 +#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS) +#define AE_DONT_WAIT 4 + +#define AE_NOMORE -1 + +/* Macros */ +#define AE_NOTUSED(V) ((void) V) + +struct aeEventLoop; + +/* Types and data structures */ +typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); +typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); +typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); +typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); + +/* File event structure */ +typedef struct aeFileEvent { + int mask; /* one of AE_(READABLE|WRITABLE) */ + aeFileProc *rfileProc; + aeFileProc *wfileProc; + void *clientData; +} aeFileEvent; + +/* Time event structure */ +typedef struct aeTimeEvent { + long long id; /* time event identifier. */ + long when_sec; /* seconds */ + long when_ms; /* milliseconds */ + aeTimeProc *timeProc; + aeEventFinalizerProc *finalizerProc; + void *clientData; + struct aeTimeEvent *next; +} aeTimeEvent; + +/* A fired event */ +typedef struct aeFiredEvent { + int fd; + int mask; +} aeFiredEvent; + +/* State of an event based program */ +typedef struct aeEventLoop { + int maxfd; /* highest file descriptor currently registered */ + int setsize; /* max number of file descriptors tracked */ + long long timeEventNextId; + time_t lastTime; /* Used to detect system clock skew */ + aeFileEvent *events; /* Registered events */ + aeFiredEvent *fired; /* Fired events */ + aeTimeEvent *timeEventHead; + int stop; + void *apidata; /* This is used for polling API specific data */ + aeBeforeSleepProc *beforesleep; +} aeEventLoop; + +/* Prototypes */ +aeEventLoop *aeCreateEventLoop(int setsize); +void aeDeleteEventLoop(aeEventLoop *eventLoop); +void aeStop(aeEventLoop *eventLoop); +int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, + aeFileProc *proc, void *clientData); +void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); +int aeGetFileEvents(aeEventLoop *eventLoop, int fd); +long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, + aeTimeProc *proc, void *clientData, + aeEventFinalizerProc *finalizerProc); +int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); +int aeProcessEvents(aeEventLoop *eventLoop, int flags); +int aeWait(int fd, int mask, long long milliseconds); +void aeMain(aeEventLoop *eventLoop); +char *aeGetApiName(void); +void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); + +#endif diff --git a/tools/wrk/src/ae_epoll.c b/tools/wrk/src/ae_epoll.c new file mode 100644 index 0000000000..4823c281ea --- /dev/null +++ b/tools/wrk/src/ae_epoll.c @@ -0,0 +1,130 @@ +/* Linux epoll(2) based ae.c module + * + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include + +typedef struct aeApiState { + int epfd; + struct epoll_event *events; +} aeApiState; + +static int aeApiCreate(aeEventLoop *eventLoop) { + aeApiState *state = zmalloc(sizeof(aeApiState)); + + if (!state) return -1; + state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize); + if (!state->events) { + zfree(state); + return -1; + } + state->epfd = epoll_create(1024); /* 1024 is just an hint for the kernel */ + if (state->epfd == -1) { + zfree(state->events); + zfree(state); + return -1; + } + eventLoop->apidata = state; + return 0; +} + +static void aeApiFree(aeEventLoop *eventLoop) { + aeApiState *state = eventLoop->apidata; + + close(state->epfd); + zfree(state->events); + zfree(state); +} + +static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + struct epoll_event ee; + /* If the fd was already monitored for some event, we need a MOD + * operation. Otherwise we need an ADD operation. */ + int op = eventLoop->events[fd].mask == AE_NONE ? + EPOLL_CTL_ADD : EPOLL_CTL_MOD; + + ee.events = 0; + mask |= eventLoop->events[fd].mask; /* Merge old events */ + if (mask & AE_READABLE) ee.events |= EPOLLIN; + if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; + ee.data.u64 = 0; /* avoid valgrind warning */ + ee.data.fd = fd; + if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1; + return 0; +} + +static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) { + aeApiState *state = eventLoop->apidata; + struct epoll_event ee; + int mask = eventLoop->events[fd].mask & (~delmask); + + ee.events = 0; + if (mask & AE_READABLE) ee.events |= EPOLLIN; + if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; + ee.data.u64 = 0; /* avoid valgrind warning */ + ee.data.fd = fd; + if (mask != AE_NONE) { + epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee); + } else { + /* Note, Kernel < 2.6.9 requires a non null event pointer even for + * EPOLL_CTL_DEL. */ + epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee); + } +} + +static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { + aeApiState *state = eventLoop->apidata; + int retval, numevents = 0; + + retval = epoll_wait(state->epfd,state->events,eventLoop->setsize, + tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1); + if (retval > 0) { + int j; + + numevents = retval; + for (j = 0; j < numevents; j++) { + int mask = 0; + struct epoll_event *e = state->events+j; + + if (e->events & EPOLLIN) mask |= AE_READABLE; + if (e->events & EPOLLOUT) mask |= AE_WRITABLE; + if (e->events & EPOLLERR) mask |= AE_WRITABLE; + if (e->events & EPOLLHUP) mask |= AE_WRITABLE; + eventLoop->fired[j].fd = e->data.fd; + eventLoop->fired[j].mask = mask; + } + } + return numevents; +} + +static char *aeApiName(void) { + return "epoll"; +} diff --git a/tools/wrk/src/ae_evport.c b/tools/wrk/src/ae_evport.c new file mode 100644 index 0000000000..94413c132d --- /dev/null +++ b/tools/wrk/src/ae_evport.c @@ -0,0 +1,315 @@ +/* ae.c module for illumos event ports. + * + * Copyright (c) 2012, Joyent, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include +#include + +#include +#include + +#include + +static int evport_debug = 0; + +/* + * This file implements the ae API using event ports, present on Solaris-based + * systems since Solaris 10. Using the event port interface, we associate file + * descriptors with the port. Each association also includes the set of poll(2) + * events that the consumer is interested in (e.g., POLLIN and POLLOUT). + * + * There's one tricky piece to this implementation: when we return events via + * aeApiPoll, the corresponding file descriptors become dissociated from the + * port. This is necessary because poll events are level-triggered, so if the + * fd didn't become dissociated, it would immediately fire another event since + * the underlying state hasn't changed yet. We must re-associate the file + * descriptor, but only after we know that our caller has actually read from it. + * The ae API does not tell us exactly when that happens, but we do know that + * it must happen by the time aeApiPoll is called again. Our solution is to + * keep track of the last fds returned by aeApiPoll and re-associate them next + * time aeApiPoll is invoked. + * + * To summarize, in this module, each fd association is EITHER (a) represented + * only via the in-kernel association OR (b) represented by pending_fds and + * pending_masks. (b) is only true for the last fds we returned from aeApiPoll, + * and only until we enter aeApiPoll again (at which point we restore the + * in-kernel association). + */ +#define MAX_EVENT_BATCHSZ 512 + +typedef struct aeApiState { + int portfd; /* event port */ + int npending; /* # of pending fds */ + int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */ + int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */ +} aeApiState; + +static int aeApiCreate(aeEventLoop *eventLoop) { + int i; + aeApiState *state = zmalloc(sizeof(aeApiState)); + if (!state) return -1; + + state->portfd = port_create(); + if (state->portfd == -1) { + zfree(state); + return -1; + } + + state->npending = 0; + + for (i = 0; i < MAX_EVENT_BATCHSZ; i++) { + state->pending_fds[i] = -1; + state->pending_masks[i] = AE_NONE; + } + + eventLoop->apidata = state; + return 0; +} + +static void aeApiFree(aeEventLoop *eventLoop) { + aeApiState *state = eventLoop->apidata; + + close(state->portfd); + zfree(state); +} + +static int aeApiLookupPending(aeApiState *state, int fd) { + int i; + + for (i = 0; i < state->npending; i++) { + if (state->pending_fds[i] == fd) + return (i); + } + + return (-1); +} + +/* + * Helper function to invoke port_associate for the given fd and mask. + */ +static int aeApiAssociate(const char *where, int portfd, int fd, int mask) { + int events = 0; + int rv, err; + + if (mask & AE_READABLE) + events |= POLLIN; + if (mask & AE_WRITABLE) + events |= POLLOUT; + + if (evport_debug) + fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events); + + rv = port_associate(portfd, PORT_SOURCE_FD, fd, events, + (void *)(uintptr_t)mask); + err = errno; + + if (evport_debug) + fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err)); + + if (rv == -1) { + fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err)); + + if (err == EAGAIN) + fprintf(stderr, "aeApiAssociate: event port limit exceeded."); + } + + return rv; +} + +static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + int fullmask, pfd; + + if (evport_debug) + fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask); + + /* + * Since port_associate's "events" argument replaces any existing events, we + * must be sure to include whatever events are already associated when + * we call port_associate() again. + */ + fullmask = mask | eventLoop->events[fd].mask; + pfd = aeApiLookupPending(state, fd); + + if (pfd != -1) { + /* + * This fd was recently returned from aeApiPoll. It should be safe to + * assume that the consumer has processed that poll event, but we play + * it safer by simply updating pending_mask. The fd will be + * re-associated as usual when aeApiPoll is called again. + */ + if (evport_debug) + fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd); + state->pending_masks[pfd] |= fullmask; + return 0; + } + + return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask)); +} + +static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + int fullmask, pfd; + + if (evport_debug) + fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask); + + pfd = aeApiLookupPending(state, fd); + + if (pfd != -1) { + if (evport_debug) + fprintf(stderr, "deleting event from pending fd %d\n", fd); + + /* + * This fd was just returned from aeApiPoll, so it's not currently + * associated with the port. All we need to do is update + * pending_mask appropriately. + */ + state->pending_masks[pfd] &= ~mask; + + if (state->pending_masks[pfd] == AE_NONE) + state->pending_fds[pfd] = -1; + + return; + } + + /* + * The fd is currently associated with the port. Like with the add case + * above, we must look at the full mask for the file descriptor before + * updating that association. We don't have a good way of knowing what the + * events are without looking into the eventLoop state directly. We rely on + * the fact that our caller has already updated the mask in the eventLoop. + */ + + fullmask = eventLoop->events[fd].mask; + if (fullmask == AE_NONE) { + /* + * We're removing *all* events, so use port_dissociate to remove the + * association completely. Failure here indicates a bug. + */ + if (evport_debug) + fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd); + + if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) { + perror("aeApiDelEvent: port_dissociate"); + abort(); /* will not return */ + } + } else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd, + fullmask) != 0) { + /* + * ENOMEM is a potentially transient condition, but the kernel won't + * generally return it unless things are really bad. EAGAIN indicates + * we've reached an resource limit, for which it doesn't make sense to + * retry (counter-intuitively). All other errors indicate a bug. In any + * of these cases, the best we can do is to abort. + */ + abort(); /* will not return */ + } +} + +static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { + aeApiState *state = eventLoop->apidata; + struct timespec timeout, *tsp; + int mask, i; + uint_t nevents; + port_event_t event[MAX_EVENT_BATCHSZ]; + + /* + * If we've returned fd events before, we must re-associate them with the + * port now, before calling port_get(). See the block comment at the top of + * this file for an explanation of why. + */ + for (i = 0; i < state->npending; i++) { + if (state->pending_fds[i] == -1) + /* This fd has since been deleted. */ + continue; + + if (aeApiAssociate("aeApiPoll", state->portfd, + state->pending_fds[i], state->pending_masks[i]) != 0) { + /* See aeApiDelEvent for why this case is fatal. */ + abort(); + } + + state->pending_masks[i] = AE_NONE; + state->pending_fds[i] = -1; + } + + state->npending = 0; + + if (tvp != NULL) { + timeout.tv_sec = tvp->tv_sec; + timeout.tv_nsec = tvp->tv_usec * 1000; + tsp = &timeout; + } else { + tsp = NULL; + } + + /* + * port_getn can return with errno == ETIME having returned some events (!). + * So if we get ETIME, we check nevents, too. + */ + nevents = 1; + if (port_getn(state->portfd, event, MAX_EVENT_BATCHSZ, &nevents, + tsp) == -1 && (errno != ETIME || nevents == 0)) { + if (errno == ETIME || errno == EINTR) + return 0; + + /* Any other error indicates a bug. */ + perror("aeApiPoll: port_get"); + abort(); + } + + state->npending = nevents; + + for (i = 0; i < nevents; i++) { + mask = 0; + if (event[i].portev_events & POLLIN) + mask |= AE_READABLE; + if (event[i].portev_events & POLLOUT) + mask |= AE_WRITABLE; + + eventLoop->fired[i].fd = event[i].portev_object; + eventLoop->fired[i].mask = mask; + + if (evport_debug) + fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n", + (int)event[i].portev_object, mask); + + state->pending_fds[i] = event[i].portev_object; + state->pending_masks[i] = (uintptr_t)event[i].portev_user; + } + + return nevents; +} + +static char *aeApiName(void) { + return "evport"; +} diff --git a/tools/wrk/src/ae_kqueue.c b/tools/wrk/src/ae_kqueue.c new file mode 100644 index 0000000000..458772f7e4 --- /dev/null +++ b/tools/wrk/src/ae_kqueue.c @@ -0,0 +1,132 @@ +/* Kqueue(2)-based ae.c module + * + * Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include + +typedef struct aeApiState { + int kqfd; + struct kevent *events; +} aeApiState; + +static int aeApiCreate(aeEventLoop *eventLoop) { + aeApiState *state = zmalloc(sizeof(aeApiState)); + + if (!state) return -1; + state->events = zmalloc(sizeof(struct kevent)*eventLoop->setsize); + if (!state->events) { + zfree(state); + return -1; + } + state->kqfd = kqueue(); + if (state->kqfd == -1) { + zfree(state->events); + zfree(state); + return -1; + } + eventLoop->apidata = state; + + return 0; +} + +static void aeApiFree(aeEventLoop *eventLoop) { + aeApiState *state = eventLoop->apidata; + + close(state->kqfd); + zfree(state->events); + zfree(state); +} + +static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + struct kevent ke; + + if (mask & AE_READABLE) { + EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); + if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; + } + if (mask & AE_WRITABLE) { + EV_SET(&ke, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; + } + return 0; +} + +static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + struct kevent ke; + + if (mask & AE_READABLE) { + EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + kevent(state->kqfd, &ke, 1, NULL, 0, NULL); + } + if (mask & AE_WRITABLE) { + EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + kevent(state->kqfd, &ke, 1, NULL, 0, NULL); + } +} + +static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { + aeApiState *state = eventLoop->apidata; + int retval, numevents = 0; + + if (tvp != NULL) { + struct timespec timeout; + timeout.tv_sec = tvp->tv_sec; + timeout.tv_nsec = tvp->tv_usec * 1000; + retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, + &timeout); + } else { + retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, + NULL); + } + + if (retval > 0) { + int j; + + numevents = retval; + for(j = 0; j < numevents; j++) { + int mask = 0; + struct kevent *e = state->events+j; + + if (e->filter == EVFILT_READ) mask |= AE_READABLE; + if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE; + eventLoop->fired[j].fd = e->ident; + eventLoop->fired[j].mask = mask; + } + } + return numevents; +} + +static char *aeApiName(void) { + return "kqueue"; +} diff --git a/tools/wrk/src/ae_select.c b/tools/wrk/src/ae_select.c new file mode 100644 index 0000000000..f732e8e1e2 --- /dev/null +++ b/tools/wrk/src/ae_select.c @@ -0,0 +1,99 @@ +/* Select()-based ae.c module. + * + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include + +typedef struct aeApiState { + fd_set rfds, wfds; + /* We need to have a copy of the fd sets as it's not safe to reuse + * FD sets after select(). */ + fd_set _rfds, _wfds; +} aeApiState; + +static int aeApiCreate(aeEventLoop *eventLoop) { + aeApiState *state = zmalloc(sizeof(aeApiState)); + + if (!state) return -1; + FD_ZERO(&state->rfds); + FD_ZERO(&state->wfds); + eventLoop->apidata = state; + return 0; +} + +static void aeApiFree(aeEventLoop *eventLoop) { + zfree(eventLoop->apidata); +} + +static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + + if (mask & AE_READABLE) FD_SET(fd,&state->rfds); + if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds); + return 0; +} + +static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { + aeApiState *state = eventLoop->apidata; + + if (mask & AE_READABLE) FD_CLR(fd,&state->rfds); + if (mask & AE_WRITABLE) FD_CLR(fd,&state->wfds); +} + +static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { + aeApiState *state = eventLoop->apidata; + int retval, j, numevents = 0; + + memcpy(&state->_rfds,&state->rfds,sizeof(fd_set)); + memcpy(&state->_wfds,&state->wfds,sizeof(fd_set)); + + retval = select(eventLoop->maxfd+1, + &state->_rfds,&state->_wfds,NULL,tvp); + if (retval > 0) { + for (j = 0; j <= eventLoop->maxfd; j++) { + int mask = 0; + aeFileEvent *fe = &eventLoop->events[j]; + + if (fe->mask == AE_NONE) continue; + if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds)) + mask |= AE_READABLE; + if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds)) + mask |= AE_WRITABLE; + eventLoop->fired[numevents].fd = j; + eventLoop->fired[numevents].mask = mask; + numevents++; + } + } + return numevents; +} + +static char *aeApiName(void) { + return "select"; +} diff --git a/tools/wrk/src/aprintf.c b/tools/wrk/src/aprintf.c new file mode 100644 index 0000000000..1dcbe23938 --- /dev/null +++ b/tools/wrk/src/aprintf.c @@ -0,0 +1,27 @@ +// Copyright (C) 2012 - Will Glozer. All rights reserved. + +#include +#include +#include +#include + +char *aprintf(char **s, const char *fmt, ...) { + char *c = NULL; + int n, len; + va_list ap; + + va_start(ap, fmt); + n = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + + len = *s ? strlen(*s) : 0; + + if ((*s = realloc(*s, (len + n) * sizeof(char)))) { + c = *s + len; + va_start(ap, fmt); + vsnprintf(c, n, fmt, ap); + va_end(ap); + } + + return c; +} diff --git a/tools/wrk/src/aprintf.h b/tools/wrk/src/aprintf.h new file mode 100644 index 0000000000..97bcc6eb7a --- /dev/null +++ b/tools/wrk/src/aprintf.h @@ -0,0 +1,6 @@ +#ifndef APRINTF_H +#define APRINTF_H + +char *aprintf(char **, const char *, ...); + +#endif /* APRINTF_H */ diff --git a/tools/wrk/src/config.h b/tools/wrk/src/config.h new file mode 100644 index 0000000000..82ed65036f --- /dev/null +++ b/tools/wrk/src/config.h @@ -0,0 +1,13 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#if defined(__FreeBSD__) || defined(__APPLE__) +#define HAVE_KQUEUE +#elif defined(__sun) +#define HAVE_EVPORT +#elif defined(__linux__) +#define HAVE_EPOLL +#define _POSIX_C_SOURCE 200809L +#endif + +#endif /* CONFIG_H */ diff --git a/tools/wrk/src/http_parser.c b/tools/wrk/src/http_parser.c new file mode 100644 index 0000000000..f2ca661ba6 --- /dev/null +++ b/tools/wrk/src/http_parser.c @@ -0,0 +1,2058 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + + +#if HTTP_PARSER_DEBUG +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ + parser->error_lineno = __LINE__; \ +} while (0) +#else +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { "DELETE" + , "GET" + , "HEAD" + , "POST" + , "PUT" + , "CONNECT" + , "OPTIONS" + , "TRACE" + , "COPY" + , "LOCK" + , "MKCOL" + , "MOVE" + , "PROPFIND" + , "PROPPATCH" + , "UNLOCK" + , "REPORT" + , "MKACTIVITY" + , "CHECKOUT" + , "MERGE" + , "M-SEARCH" + , "NOTIFY" + , "SUBSCRIBE" + , "UNSUBSCRIBE" + , "PATCH" + , "PURGE" + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +static const uint8_t normal_url_char[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, 1, 1, 0, 1, 1, 1, 1, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1, 1, 1, 1, 1, 1, 1, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1, 1, 1, 1, 1, 1, 1, 0, }; + + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_host_start + , s_req_host_v6_start + , s_req_host_v6 + , s_req_host_v6_end + , s_req_host + , s_req_port_start + , s_req_port + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)]) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (normal_url_char[(unsigned char) (c)] || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + assert(!isspace(ch)); + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_host_start; + } + + break; + + case s_req_host_start: + if (ch == '[') { + return s_req_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_req_host; + } + + break; + + case s_req_host: + if (IS_HOST_CHAR(ch)) { + return s_req_host; + } + + /* FALLTHROUGH */ + case s_req_host_v6_end: + switch (ch) { + case ':': + return s_req_port_start; + + case '/': + return s_req_path; + + case '?': + return s_req_query_string_start; + } + + break; + + case s_req_host_v6: + if (ch == ']') { + return s_req_host_v6_end; + } + + /* FALLTHROUGH */ + case s_req_host_v6_start: + if (IS_HEX(ch) || ch == ':') { + return s_req_host_v6; + } + break; + + case s_req_port: + switch (ch) { + case '/': + return s_req_path; + + case '?': + return s_req_query_string_start; + } + + /* FALLTHROUGH */ + case s_req_port_start: + if (IS_NUM(ch)) { + return s_req_port; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (parser->state) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (parser->state == s_header_field) + header_field_mark = data; + if (parser->state == s_header_value) + header_value_mark = data; + switch (parser->state) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_host_start: + case s_req_host_v6_start: + case s_req_host_v6: + case s_req_host_v6_end: + case s_req_host: + case s_req_port_start: + case s_req_port: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(parser->state)) { + ++parser->nread; + /* Buffer overflow attack */ + if (parser->nread > HTTP_MAX_HEADER_SIZE) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } + } + + reexecute_byte: + switch (parser->state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (ch == CR || ch == LF) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + parser->state = s_res_or_resp_H; + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + parser->state = s_start_req; + goto reexecute_byte; + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + parser->state = s_res_HT; + } else { + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + parser->state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + parser->state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '0' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + parser->state = s_res_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + parser->state = s_res_first_status_code; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + parser->state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + parser->state = s_res_status; + break; + case CR: + parser->state = s_res_line_almost_done; + break; + case LF: + parser->state = s_header_field_start; + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this */ + if (ch == CR) { + parser->state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + parser->state = s_header_field_start; + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; break; + case 'S': parser->method = HTTP_SUBSCRIBE; break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + parser->state = s_req_method; + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + parser->state = s_req_spaces_before_url; + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (parser->index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (parser->index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + goto error; + } + } else if (parser->method == HTTP_MKCOL) { + if (parser->index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (parser->index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (parser->index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else { + goto error; + } + } else if (parser->index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; /* or HTTP_PURGE */ + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + goto error; + } + } else if (parser->index == 2) { + if (parser->method == HTTP_PUT) { + if (ch == 'R') parser->method = HTTP_PURGE; + } else if (parser->method == HTTP_UNLOCK) { + if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE; + } + } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + parser->state = s_req_host_start; + } + + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_host_start: + case s_req_host_v6_start: + case s_req_host_v6: + case s_req_port_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_host: + case s_req_host_v6_end: + case s_req_port: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + parser->state = s_req_http_start; + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + parser->state = (ch == CR) ? + s_req_line_almost_done : + s_header_field_start; + CALLBACK_DATA(url); + break; + default: + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + parser->state = s_req_http_H; + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + parser->state = s_req_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + parser->state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + parser->state = s_header_field_start; + break; + } + + case s_header_field_start: + { + if (ch == CR) { + parser->state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + parser->state = s_headers_almost_done; + goto reexecute_byte; + } + + c = TOKEN(ch); + + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + parser->state = s_header_field; + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + parser->state = s_header_value_start; + CALLBACK_DATA(header_field); + break; + } + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_field); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_start: + { + if (ch == ' ' || ch == '\t') break; + + MARK(header_value); + + parser->state = s_header_value; + parser->index = 0; + + if (ch == CR) { + parser->header_state = h_general; + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(header_value); + break; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else { + parser->header_state = h_general; + } + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_almost_done; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + parser->header_state = h_transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + parser->header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CLOSE)-2) { + parser->header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + parser->state = s_header_value; + parser->header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + { + STRICT_CHECK(ch != LF); + + parser->state = s_header_value_lws; + + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') + parser->state = s_header_value_start; + else + { + parser->state = s_header_field_start; + goto reexecute_byte; + } + break; + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + } + + parser->state = s_headers_done; + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + return p - data; /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return p - data; + } + + goto reexecute_byte; + } + + case s_headers_done: + { + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + parser->state = s_chunk_size_start; + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + parser->state = s_body_identity; + } else { + if (parser->type == HTTP_REQUEST || + !http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + parser->state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_message_done; + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + goto reexecute_byte; + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + parser->state = s_chunk_size; + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + parser->state = s_chunk_parameters; + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + parser->state = s_header_field_start; + } else { + parser->state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_chunk_data_almost_done; + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + parser->state = s_chunk_data_done; + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + parser->state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + + return len; + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + return (p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * http_method_str (enum http_method m) +{ + return method_strings[m]; +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + + u->port = u->field_set = 0; + s = is_connect ? s_req_host_start : s_req_spaces_before_url; + uf = old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_host_start: + case s_req_host_v6_start: + case s_req_host_v6_end: + case s_req_port_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_host: + case s_req_host_v6: + uf = UF_HOST; + break; + + case s_req_port: + uf = UF_PORT; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_req_host_v6_start: + case s_req_host_v6: + case s_req_host_v6_end: + case s_req_host: + case s_req_port_start: + return 1; + default: + break; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} diff --git a/tools/wrk/src/http_parser.h b/tools/wrk/src/http_parser.h new file mode 100644 index 0000000000..ae62661a78 --- /dev/null +++ b/tools/wrk/src/http_parser.h @@ -0,0 +1,317 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +#define HTTP_PARSER_VERSION_MAJOR 1 +#define HTTP_PARSER_VERSION_MINOR 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +typedef unsigned int size_t; +typedef int ssize_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to + * the error reporting facility. + */ +#ifndef HTTP_PARSER_DEBUG +# define HTTP_PARSER_DEBUG 0 +#endif + + +/* Maximium header size allowed */ +#define HTTP_MAX_HEADER_SIZE (80*1024) + + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be call arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_path" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE) \ + XX(1, GET) \ + XX(2, HEAD) \ + XX(3, POST) \ + XX(4, PUT) \ + /* pathological */ \ + XX(5, CONNECT) \ + XX(6, OPTIONS) \ + XX(7, TRACE) \ + /* webdav */ \ + XX(8, COPY) \ + XX(9, LOCK) \ + XX(10, MKCOL) \ + XX(11, MOVE) \ + XX(12, PROPFIND) \ + XX(13, PROPPATCH) \ + XX(14, UNLOCK) \ + /* subversion */ \ + XX(15, REPORT) \ + XX(16, MKACTIVITY) \ + XX(17, CHECKOUT) \ + XX(18, MERGE) \ + /* upnp */ \ + XX(19, MSEARCH) \ + XX(20, NOTIFY) \ + XX(21, SUBSCRIBE) \ + XX(22, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(23, PATCH) \ + XX(24, PURGE) \ + +enum http_method + { +#define XX(num, name) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef X + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + +/* Get the line number that generated the current error */ +#if HTTP_PARSER_DEBUG +#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno) +#else +#define HTTP_PARSER_ERRNO_LINE(p) 0 +#endif + + +struct http_parser { + /** PRIVATE **/ + unsigned char type : 2; /* enum http_parser_type */ + unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned char state; /* enum state from http_parser.c */ + unsigned char header_state; /* enum header_state from http_parser.c */ + unsigned char index; /* index into current matcher */ + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned short status_code; /* responses only */ + unsigned char method; /* requests only */ + unsigned char http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned char upgrade : 1; + +#if HTTP_PARSER_DEBUG + uint32_t error_lineno; +#endif + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_MAX = 6 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns true, then this will be should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/tools/wrk/src/stats.c b/tools/wrk/src/stats.c new file mode 100644 index 0000000000..a114917bff --- /dev/null +++ b/tools/wrk/src/stats.c @@ -0,0 +1,73 @@ +// Copyright (C) 2012 - Will Glozer. All rights reserved. + +#include +#include +#include + +#include "stats.h" +#include "zmalloc.h" + +stats *stats_alloc(uint64_t samples) { + stats *stats = zcalloc(sizeof(stats) + sizeof(uint64_t) * samples); + stats->samples = samples; + return stats; +} + +void stats_free(stats *stats) { + zfree(stats); +} + +void stats_record(stats *stats, uint64_t x) { + stats->data[stats->index++] = x; + if (stats->limit < stats->samples) stats->limit++; + if (stats->index == stats->samples) stats->index = 0; +} + +uint64_t stats_min(stats *stats) { + uint64_t min = 0; + for (uint64_t i = 0; i < stats->limit; i++) { + uint64_t x = stats->data[i]; + if (x < min || min == 0) min = x; + } + return min; +} + +uint64_t stats_max(stats *stats) { + uint64_t max = 0; + for (uint64_t i = 0; i < stats->limit; i++) { + uint64_t x = stats->data[i]; + if (x > max || max == 0) max = x; + } + return max; +} + +long double stats_mean(stats *stats) { + uint64_t sum = 0; + if (stats->limit == 0) return 0.0; + for (uint64_t i = 0; i < stats->limit; i++) { + sum += stats->data[i]; + } + return sum / (long double) stats->limit; +} + +long double stats_stdev(stats *stats, long double mean) { + long double sum = 0.0; + if (stats->limit < 2) return 0.0; + for (uint64_t i = 0; i < stats->limit; i++) { + sum += powl(stats->data[i] - mean, 2); + } + return sqrtl(sum / (stats->limit - 1)); +} + +long double stats_within_stdev(stats *stats, long double mean, long double stdev, uint64_t n) { + long double upper = mean + (stdev * n); + long double lower = mean - (stdev * n); + uint64_t sum = 0; + + for (uint64_t i = 0; i < stats->limit; i++) { + uint64_t x = stats->data[i]; + if (x >= lower && x <= upper) sum++; + } + + return (sum / (long double) stats->limit) * 100; +} diff --git a/tools/wrk/src/stats.h b/tools/wrk/src/stats.h new file mode 100644 index 0000000000..04a9393132 --- /dev/null +++ b/tools/wrk/src/stats.h @@ -0,0 +1,21 @@ +#ifndef STATS_H +#define STATS_H + +typedef struct { + uint64_t samples; + uint64_t index; + uint64_t limit; + uint64_t data[]; +} stats; + +stats *stats_alloc(uint64_t); +void stats_free(stats *); +void stats_record(stats *, uint64_t); +uint64_t stats_min(stats *); +uint64_t stats_max(stats *); +long double stats_mean(stats *); +long double stats_stdev(stats *stats, long double); +long double stats_within_stdev(stats *, long double, long double, uint64_t); + +#endif /* STATS_H */ + diff --git a/tools/wrk/src/tinymt64.c b/tools/wrk/src/tinymt64.c new file mode 100644 index 0000000000..fa5a06c1ef --- /dev/null +++ b/tools/wrk/src/tinymt64.c @@ -0,0 +1,129 @@ +/** + * @file tinymt64.c + * + * @brief 64-bit Tiny Mersenne Twister only 127 bit internal state + * + * @author Mutsuo Saito (Hiroshima University) + * @author Makoto Matsumoto (The University of Tokyo) + * + * Copyright (C) 2011 Mutsuo Saito, Makoto Matsumoto, + * Hiroshima University and The University of Tokyo. + * All rights reserved. + * + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt + */ +#include "tinymt64.h" + +#define MIN_LOOP 8 + +/** + * This function represents a function used in the initialization + * by init_by_array + * @param[in] x 64-bit integer + * @return 64-bit integer + */ +static uint64_t ini_func1(uint64_t x) { + return (x ^ (x >> 59)) * UINT64_C(2173292883993); +} + +/** + * This function represents a function used in the initialization + * by init_by_array + * @param[in] x 64-bit integer + * @return 64-bit integer + */ +static uint64_t ini_func2(uint64_t x) { + return (x ^ (x >> 59)) * UINT64_C(58885565329898161); +} + +/** + * This function certificate the period of 2^127-1. + * @param random tinymt state vector. + */ +static void period_certification(tinymt64_t * random) { + if ((random->status[0] & TINYMT64_MASK) == 0 && + random->status[1] == 0) { + random->status[0] = 'T'; + random->status[1] = 'M'; + } +} + +/** + * This function initializes the internal state array with a 64-bit + * unsigned integer seed. + * @param random tinymt state vector. + * @param seed a 64-bit unsigned integer used as a seed. + */ +void tinymt64_init(tinymt64_t * random, uint64_t seed) { + random->status[0] = seed ^ ((uint64_t)random->mat1 << 32); + random->status[1] = random->mat2 ^ random->tmat; + for (int i = 1; i < MIN_LOOP; i++) { + random->status[i & 1] ^= i + UINT64_C(6364136223846793005) + * (random->status[(i - 1) & 1] + ^ (random->status[(i - 1) & 1] >> 62)); + } + period_certification(random); +} + +/** + * This function initializes the internal state array, + * with an array of 64-bit unsigned integers used as seeds + * @param random tinymt state vector. + * @param init_key the array of 64-bit integers, used as a seed. + * @param key_length the length of init_key. + */ +void tinymt64_init_by_array(tinymt64_t * random, const uint64_t init_key[], + int key_length) { + const int lag = 1; + const int mid = 1; + const int size = 4; + int i, j; + int count; + uint64_t r; + uint64_t st[4]; + + st[0] = 0; + st[1] = random->mat1; + st[2] = random->mat2; + st[3] = random->tmat; + if (key_length + 1 > MIN_LOOP) { + count = key_length + 1; + } else { + count = MIN_LOOP; + } + r = ini_func1(st[0] ^ st[mid % size] + ^ st[(size - 1) % size]); + st[mid % size] += r; + r += key_length; + st[(mid + lag) % size] += r; + st[0] = r; + count--; + for (i = 1, j = 0; (j < count) && (j < key_length); j++) { + r = ini_func1(st[i] ^ st[(i + mid) % size] ^ st[(i + size - 1) % size]); + st[(i + mid) % size] += r; + r += init_key[j] + i; + st[(i + mid + lag) % size] += r; + st[i] = r; + i = (i + 1) % size; + } + for (; j < count; j++) { + r = ini_func1(st[i] ^ st[(i + mid) % size] ^ st[(i + size - 1) % size]); + st[(i + mid) % size] += r; + r += i; + st[(i + mid + lag) % size] += r; + st[i] = r; + i = (i + 1) % size; + } + for (j = 0; j < size; j++) { + r = ini_func2(st[i] + st[(i + mid) % size] + st[(i + size - 1) % size]); + st[(i + mid) % size] ^= r; + r -= i; + st[(i + mid + lag) % size] ^= r; + st[i] = r; + i = (i + 1) % size; + } + random->status[0] = st[0] ^ st[1]; + random->status[1] = st[2] ^ st[3]; + period_certification(random); +} diff --git a/tools/wrk/src/tinymt64.h b/tools/wrk/src/tinymt64.h new file mode 100644 index 0000000000..015afff322 --- /dev/null +++ b/tools/wrk/src/tinymt64.h @@ -0,0 +1,210 @@ +#ifndef TINYMT64_H +#define TINYMT64_H +/** + * @file tinymt64.h + * + * @brief Tiny Mersenne Twister only 127 bit internal state + * + * @author Mutsuo Saito (Hiroshima University) + * @author Makoto Matsumoto (The University of Tokyo) + * + * Copyright (C) 2011 Mutsuo Saito, Makoto Matsumoto, + * Hiroshima University and The University of Tokyo. + * All rights reserved. + * + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt + */ + +#include +#include + +#define TINYMT64_MEXP 127 +#define TINYMT64_SH0 12 +#define TINYMT64_SH1 11 +#define TINYMT64_SH8 8 +#define TINYMT64_MASK UINT64_C(0x7fffffffffffffff) +#define TINYMT64_MUL (1.0 / 18446744073709551616.0) + +/* + * tinymt64 internal state vector and parameters + */ +struct TINYMT64_T { + uint64_t status[2]; + uint32_t mat1; + uint32_t mat2; + uint64_t tmat; +}; + +typedef struct TINYMT64_T tinymt64_t; + +void tinymt64_init(tinymt64_t * random, uint64_t seed); +void tinymt64_init_by_array(tinymt64_t * random, const uint64_t init_key[], + int key_length); + +#if defined(__GNUC__) +/** + * This function always returns 127 + * @param random not used + * @return always 127 + */ +inline static int tinymt64_get_mexp( + tinymt64_t * random __attribute__((unused))) { + return TINYMT64_MEXP; +} +#else +inline static int tinymt64_get_mexp(tinymt64_t * random) { + return TINYMT64_MEXP; +} +#endif + +/** + * This function changes internal state of tinymt64. + * Users should not call this function directly. + * @param random tinymt internal status + */ +inline static void tinymt64_next_state(tinymt64_t * random) { + uint64_t x; + + random->status[0] &= TINYMT64_MASK; + x = random->status[0] ^ random->status[1]; + x ^= x << TINYMT64_SH0; + x ^= x >> 32; + x ^= x << 32; + x ^= x << TINYMT64_SH1; + random->status[0] = random->status[1]; + random->status[1] = x; + random->status[0] ^= -((int64_t)(x & 1)) & random->mat1; + random->status[1] ^= -((int64_t)(x & 1)) & (((uint64_t)random->mat2) << 32); +} + +/** + * This function outputs 64-bit unsigned integer from internal state. + * Users should not call this function directly. + * @param random tinymt internal status + * @return 64-bit unsigned pseudorandom number + */ +inline static uint64_t tinymt64_temper(tinymt64_t * random) { + uint64_t x; +#if defined(LINEARITY_CHECK) + x = random->status[0] ^ random->status[1]; +#else + x = random->status[0] + random->status[1]; +#endif + x ^= random->status[0] >> TINYMT64_SH8; + x ^= -((int64_t)(x & 1)) & random->tmat; + return x; +} + +/** + * This function outputs floating point number from internal state. + * Users should not call this function directly. + * @param random tinymt internal status + * @return floating point number r (1.0 <= r < 2.0) + */ +inline static double tinymt64_temper_conv(tinymt64_t * random) { + uint64_t x; + union { + uint64_t u; + double d; + } conv; +#if defined(LINEARITY_CHECK) + x = random->status[0] ^ random->status[1]; +#else + x = random->status[0] + random->status[1]; +#endif + x ^= random->status[0] >> TINYMT64_SH8; + conv.u = ((x ^ (-((int64_t)(x & 1)) & random->tmat)) >> 12) + | UINT64_C(0x3ff0000000000000); + return conv.d; +} + +/** + * This function outputs floating point number from internal state. + * Users should not call this function directly. + * @param random tinymt internal status + * @return floating point number r (1.0 < r < 2.0) + */ +inline static double tinymt64_temper_conv_open(tinymt64_t * random) { + uint64_t x; + union { + uint64_t u; + double d; + } conv; +#if defined(LINEARITY_CHECK) + x = random->status[0] ^ random->status[1]; +#else + x = random->status[0] + random->status[1]; +#endif + x ^= random->status[0] >> TINYMT64_SH8; + conv.u = ((x ^ (-((int64_t)(x & 1)) & random->tmat)) >> 12) + | UINT64_C(0x3ff0000000000001); + return conv.d; +} + +/** + * This function outputs 64-bit unsigned integer from internal state. + * @param random tinymt internal status + * @return 64-bit unsigned integer r (0 <= r < 2^64) + */ +inline static uint64_t tinymt64_generate_uint64(tinymt64_t * random) { + tinymt64_next_state(random); + return tinymt64_temper(random); +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using multiplying by 1 / 2^64. + * @param random tinymt internal status + * @return floating point number r (0.0 <= r < 1.0) + */ +inline static double tinymt64_generate_double(tinymt64_t * random) { + tinymt64_next_state(random); + return tinymt64_temper(random) * TINYMT64_MUL; +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using union trick. + * @param random tinymt internal status + * @return floating point number r (0.0 <= r < 1.0) + */ +inline static double tinymt64_generate_double01(tinymt64_t * random) { + tinymt64_next_state(random); + return tinymt64_temper_conv(random) - 1.0; +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using union trick. + * @param random tinymt internal status + * @return floating point number r (1.0 <= r < 2.0) + */ +inline static double tinymt64_generate_double12(tinymt64_t * random) { + tinymt64_next_state(random); + return tinymt64_temper_conv(random); +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using union trick. + * @param random tinymt internal status + * @return floating point number r (0.0 < r <= 1.0) + */ +inline static double tinymt64_generate_doubleOC(tinymt64_t * random) { + tinymt64_next_state(random); + return 2.0 - tinymt64_temper_conv(random); +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using union trick. + * @param random tinymt internal status + * @return floating point number r (0.0 < r < 1.0) + */ +inline static double tinymt64_generate_doubleOO(tinymt64_t * random) { + tinymt64_next_state(random); + return tinymt64_temper_conv_open(random) - 1.0; +} + +#endif diff --git a/tools/wrk/src/units.c b/tools/wrk/src/units.c new file mode 100644 index 0000000000..b9fc3c0fdf --- /dev/null +++ b/tools/wrk/src/units.c @@ -0,0 +1,96 @@ +// Copyright (C) 2012 - Will Glozer. All rights reserved. + +#include +#include +#include +#include + +#include "units.h" +#include "aprintf.h" + +typedef struct { + int scale; + char *base; + char *units[]; +} units; + +units time_units_us = { + .scale = 1000, + .base = "us", + .units = { "ms", "s", NULL } +}; + +units time_units_s = { + .scale = 60, + .base = "s", + .units = { "m", "h", NULL } +}; + +units binary_units = { + .scale = 1024, + .base = "", + .units = { "K", "M", "G", "T", "P", NULL } +}; + +units metric_units = { + .scale = 1000, + .base = "", + .units = { "k", "M", "G", "T", "P", NULL } +}; + +static char *format_units(long double n, units *m, int p) { + long double amt = n, scale; + char *unit = m->base; + char *msg = NULL; + + scale = m->scale * 0.85; + + for (int i = 0; m->units[i+1] && amt >= scale; i++) { + amt /= m->scale; + unit = m->units[i]; + } + + aprintf(&msg, "%.*Lf%s", p, amt, unit); + + return msg; +} + +static int scan_units(char *s, uint64_t *n, units *m) { + uint64_t base, scale = 1; + char unit[3] = { 0, 0, 0 }; + int i, c; + + if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1; + + if (c == 2) { + for (i = 0; m->units[i] != NULL; i++) { + scale *= m->scale; + if (!strncasecmp(unit, m->units[i], sizeof(unit))) break; + } + if (m->units[i] == NULL) return -1; + } + + *n = base * scale; + return 0; +} + +char *format_binary(long double n) { + return format_units(n, &binary_units, 2); +} + +char *format_metric(long double n) { + return format_units(n, &metric_units, 2); +} + +char *format_time_us(long double n) { + units *units = &time_units_us; + if (n >= 1000000.0) { + n /= 1000000.0; + units = &time_units_s; + } + return format_units(n, units, 2); +} + +int scan_metric(char *s, uint64_t *n) { + return scan_units(s, n, &metric_units); +} diff --git a/tools/wrk/src/units.h b/tools/wrk/src/units.h new file mode 100644 index 0000000000..3c16def4b3 --- /dev/null +++ b/tools/wrk/src/units.h @@ -0,0 +1,10 @@ +#ifndef UNITS_H +#define UNITS_H + +char *format_binary(long double); +char *format_metric(long double); +char *format_time_us(long double); + +int scan_metric(char *, uint64_t *); + +#endif /* UNITS_H */ diff --git a/tools/wrk/src/wrk.c b/tools/wrk/src/wrk.c new file mode 100644 index 0000000000..ffc07057c0 --- /dev/null +++ b/tools/wrk/src/wrk.c @@ -0,0 +1,482 @@ +// Copyright (C) 2012 - Will Glozer. All rights reserved. + +#include "wrk.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aprintf.h" +#include "stats.h" +#include "units.h" +#include "zmalloc.h" +#include "tinymt64.h" + +extern char *optarg; +extern int optind, opterr; + +static struct config { + struct addrinfo addr; + uint64_t threads; + uint64_t connections; + uint64_t requests; + uint64_t timeout; +} cfg; + +static struct { + size_t size; + char *buf; +} request; + +static struct { + stats *latency; + stats *requests; + pthread_mutex_t mutex; +} statistics; + +static const struct http_parser_settings parser_settings = { + .on_message_complete = request_complete +}; + +static void usage() { + printf("Usage: wrk \n" + " Options: \n" + " -c, --connections Connections to keep open \n" + " -r, --requests Total requests to make \n" + " -t, --threads Number of threads to use \n" + " \n" + " -H, --header Add header to request \n" + " -v, --version Print version details \n" + " \n" + " Numeric arguments may include a SI unit (2k, 2M, 2G)\n"); +} + +int main(int argc, char **argv) { + struct addrinfo *addrs, *addr; + struct http_parser_url parser_url; + char *url, **headers; + int rc; + + headers = zmalloc((argc / 2) * sizeof(char *)); + + if (parse_args(&cfg, &url, headers, argc, argv)) { + usage(); + exit(1); + } + + if (http_parser_parse_url(url, strlen(url), 0, &parser_url)) { + fprintf(stderr, "invalid URL: %s\n", url); + exit(1); + } + + char *host = extract_url_part(url, &parser_url, UF_HOST); + char *port = extract_url_part(url, &parser_url, UF_PORT); + char *service = port ? port : extract_url_part(url, &parser_url, UF_SCHEMA); + char *path = &url[parser_url.field_data[UF_PATH].off]; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM + }; + + if ((rc = getaddrinfo(host, service, &hints, &addrs)) != 0) { + const char *msg = gai_strerror(rc); + fprintf(stderr, "unable to resolve %s:%s %s\n", host, service, msg); + exit(1); + } + + for (addr = addrs; addr != NULL; addr = addr->ai_next) { + int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (fd == -1) continue; + if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH || errno == ECONNREFUSED) { + close(fd); + continue; + } + } + close(fd); + break; + } + + if (addr == NULL) { + char *msg = strerror(errno); + fprintf(stderr, "unable to connect to %s:%s %s\n", host, service, msg); + exit(1); + } + + cfg.addr = *addr; + request.buf = format_request(host, port, path, headers); + request.size = strlen(request.buf); + + pthread_mutex_init(&statistics.mutex, NULL); + statistics.latency = stats_alloc(SAMPLES); + statistics.requests = stats_alloc(SAMPLES); + + thread *threads = zcalloc(cfg.threads * sizeof(thread)); + uint64_t connections = cfg.connections / cfg.threads; + uint64_t requests = cfg.requests / cfg.threads; + + for (uint64_t i = 0; i < cfg.threads; i++) { + thread *t = &threads[i]; + t->connections = connections; + t->requests = requests; + + if (pthread_create(&t->thread, NULL, &thread_main, t)) { + char *msg = strerror(errno); + fprintf(stderr, "unable to create thread %zu %s\n", i, msg); + exit(2); + } + } + + printf("Making %"PRIu64" requests to %s\n", cfg.requests, url); + printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); + + uint64_t start = time_us(); + uint64_t complete = 0; + uint64_t bytes = 0; + errors errors = { 0 }; + + for (uint64_t i = 0; i < cfg.threads; i++) { + thread *t = &threads[i]; + pthread_join(t->thread, NULL); + + complete += t->complete; + bytes += t->bytes; + + errors.connect += t->errors.connect; + errors.read += t->errors.read; + errors.write += t->errors.write; + errors.timeout += t->errors.timeout; + errors.status += t->errors.status; + } + + uint64_t runtime_us = time_us() - start; + long double runtime_s = runtime_us / 1000000.0; + long double req_per_s = complete / runtime_s; + long double bytes_per_s = bytes / runtime_s; + + print_stats_header(); + print_stats("Latency", statistics.latency, format_time_us); + print_stats("Req/Sec", statistics.requests, format_metric); + + char *runtime_msg = format_time_us(runtime_us); + + printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); + if (errors.connect || errors.read || errors.write || errors.timeout) { + printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", + errors.connect, errors.read, errors.write, errors.timeout); + } + + if (errors.status) { + printf(" Non-2xx or 3xx responses: %d\n", errors.status); + } + + printf("Requests/sec: %9.2Lf\n", req_per_s); + printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s)); + + return 0; +} + +void *thread_main(void *arg) { + thread *thread = arg; + + aeEventLoop *loop = aeCreateEventLoop(10 + cfg.connections * 3); + thread->cs = zmalloc(thread->connections * sizeof(connection)); + thread->loop = loop; + tinymt64_init(&thread->rand, time_us()); + + connection *c = thread->cs; + + for (uint64_t i = 0; i < thread->connections; i++, c++) { + c->thread = thread; + c->latency = 0; + connect_socket(thread, c); + } + + aeCreateTimeEvent(loop, SAMPLE_INTERVAL_MS, sample_rate, thread, NULL); + aeCreateTimeEvent(loop, TIMEOUT_INTERVAL_MS, check_timeouts, thread, NULL); + + thread->start = time_us(); + aeMain(loop); + + aeDeleteEventLoop(loop); + zfree(thread->cs); + + return NULL; +} + +static int connect_socket(thread *thread, connection *c) { + struct addrinfo addr = cfg.addr; + struct aeEventLoop *loop = thread->loop; + int fd, flags; + + fd = socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol); + + flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + if (connect(fd, addr.ai_addr, addr.ai_addrlen) == -1) { + if (errno != EINPROGRESS) { + thread->errors.connect++; + goto error; + } + } + + flags = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)); + + if (aeCreateFileEvent(loop, fd, AE_WRITABLE, socket_writeable, c) != AE_OK) { + goto error; + } + + http_parser_init(&c->parser, HTTP_RESPONSE); + c->parser.data = c; + c->fd = fd; + + return fd; + + error: + close(fd); + return -1; +} + +static int reconnect_socket(thread *thread, connection *c) { + aeDeleteFileEvent(thread->loop, c->fd, AE_WRITABLE | AE_READABLE); + close(c->fd); + return connect_socket(thread, c); +} + +static int sample_rate(aeEventLoop *loop, long long id, void *data) { + thread *thread = data; + + uint64_t n = rand64(&thread->rand, thread->connections); + uint64_t elapsed_ms = (time_us() - thread->start) / 1000; + connection *c = thread->cs + n; + uint64_t requests = (thread->complete / elapsed_ms) * 1000; + + pthread_mutex_lock(&statistics.mutex); + stats_record(statistics.latency, c->latency); + stats_record(statistics.requests, requests); + pthread_mutex_unlock(&statistics.mutex); + + return SAMPLE_INTERVAL_MS + rand64(&thread->rand, SAMPLE_INTERVAL_MS); +} + +static int request_complete(http_parser *parser) { + connection *c = parser->data; + thread *thread = c->thread; + + if (parser->status_code > 399) { + thread->errors.status++; + } + + if (++thread->complete >= thread->requests) { + aeStop(thread->loop); + goto done; + } + + c->latency = time_us() - c->start; + if (!http_should_keep_alive(parser)) goto reconnect; + + http_parser_init(parser, HTTP_RESPONSE); + aeDeleteFileEvent(thread->loop, c->fd, AE_READABLE); + aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c); + + goto done; + + reconnect: + reconnect_socket(thread, c); + + done: + return 0; +} + +static int check_timeouts(aeEventLoop *loop, long long id, void *data) { + thread *thread = data; + connection *c = thread->cs; + + uint64_t maxAge = time_us() - (cfg.timeout * 1000); + + for (uint64_t i = 0; i < thread->connections; i++, c++) { + if (maxAge > c->start) { + thread->errors.timeout++; + } + } + + return TIMEOUT_INTERVAL_MS; +} + +static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) { + connection *c = data; + + if (write(fd, request.buf, request.size) < request.size) goto error; + c->start = time_us(); + aeDeleteFileEvent(loop, fd, AE_WRITABLE); + aeCreateFileEvent(loop, fd, AE_READABLE, socket_readable, c); + + return; + + error: + c->thread->errors.write++; + reconnect_socket(c->thread, c); +} + +static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) { + connection *c = data; + ssize_t n; + + if ((n = read(fd, c->buf, sizeof(c->buf))) == -1) goto error; + if (http_parser_execute(&c->parser, &parser_settings, c->buf, n) != n) goto error; + c->thread->bytes += n; + + return; + + error: + c->thread->errors.read++; + reconnect_socket(c->thread, c); +} + +static uint64_t time_us() { + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec * 1000000) + t.tv_usec; +} + +static uint64_t rand64(tinymt64_t *state, uint64_t n) { + uint64_t x, max = ~UINT64_C(0); + max -= max % n; + do { + x = tinymt64_generate_uint64(state); + } while (x >= max); + return x % n; +} + +static char *extract_url_part(char *url, struct http_parser_url *parser_url, enum http_parser_url_fields field) { + char *part = NULL; + + if (parser_url->field_set & (1 << field)) { + uint16_t off = parser_url->field_data[field].off; + uint16_t len = parser_url->field_data[field].len; + part = zcalloc(len + 1 * sizeof(char)); + memcpy(part, &url[off], len); + } + + return part; +} + +static char *format_request(char *host, char *port, char *path, char **headers) { + char *req = NULL; + + aprintf(&req, "GET %s HTTP/1.1\r\n", path); + aprintf(&req, "Host: %s", host); + if (port) aprintf(&req, ":%s", port); + aprintf(&req, "\r\n"); + + for (char **h = headers; *h != NULL; h++) { + aprintf(&req, "%s\r\n", *h); + } + + aprintf(&req, "\r\n"); + return req; +} + +static struct option longopts[] = { + { "connections", required_argument, NULL, 'c' }, + { "requests", required_argument, NULL, 'r' }, + { "threads", required_argument, NULL, 't' }, + { "header", required_argument, NULL, 'H' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } +}; + +static int parse_args(struct config *cfg, char **url, char **headers, int argc, char **argv) { + char c, **header = headers; + + memset(cfg, 0, sizeof(struct config)); + cfg->threads = 2; + cfg->connections = 10; + cfg->requests = 100; + cfg->timeout = SOCKET_TIMEOUT_MS; + + while ((c = getopt_long(argc, argv, "t:c:r:H:v?", longopts, NULL)) != -1) { + switch (c) { + case 't': + if (scan_metric(optarg, &cfg->threads)) return -1; + break; + case 'c': + if (scan_metric(optarg, &cfg->connections)) return -1; + break; + case 'r': + if (scan_metric(optarg, &cfg->requests)) return -1; + break; + case 'H': + *header++ = optarg; + break; + case 'v': + printf("wrk %s [%s] ", VERSION, aeGetApiName()); + printf("Copyright (C) 2012 Will Glozer\n"); + break; + case 'h': + case '?': + case ':': + default: + return -1; + } + } + + if (optind == argc || !cfg->threads || !cfg->requests) return -1; + + if (!cfg->connections || cfg->connections < cfg->threads) { + fprintf(stderr, "number of connections must be >= threads\n"); + return -1; + } + + *url = argv[optind]; + *header = NULL; + + return 0; +} + +static void print_stats_header() { + printf(" Thread Stats%6s%11s%8s%12s\n", "Avg", "Stdev", "Max", "+/- Stdev"); +} + +static void print_units(long double n, char *(*fmt)(long double), int width) { + char *msg = fmt(n); + int len = strlen(msg), pad = 2; + + if (isalpha(msg[len-1])) pad--; + if (isalpha(msg[len-2])) pad--; + width -= pad; + + printf("%*.*s%.*s", width, width, msg, pad, " "); + + free(msg); +} + +static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) { + long double mean = stats_mean(stats); + long double max = stats_max(stats); + long double stdev = stats_stdev(stats, mean); + + printf(" %-10s", name); + print_units(mean, fmt, 8); + print_units(stdev, fmt, 10); + print_units(max, fmt, 9); + printf("%8.2Lf%%\n", stats_within_stdev(stats, mean, stdev, 1)); +} diff --git a/tools/wrk/src/wrk.h b/tools/wrk/src/wrk.h new file mode 100644 index 0000000000..e445b5bf6c --- /dev/null +++ b/tools/wrk/src/wrk.h @@ -0,0 +1,75 @@ +#ifndef WRK_H +#define WRK_H + +#include "config.h" +#include +#include +#include + +#include "stats.h" +#include "ae.h" +#include "http_parser.h" +#include "tinymt64.h" + +#define VERSION "1.0.0" +#define RECVBUF 8192 +#define SAMPLES 100000 + +#define SOCKET_TIMEOUT_MS 2000 +#define SAMPLE_INTERVAL_MS 100 +#define TIMEOUT_INTERVAL_MS 2000 + +typedef struct { + uint32_t connect; + uint32_t read; + uint32_t write; + uint32_t status; + uint32_t timeout; +} errors; + +typedef struct { + pthread_t thread; + aeEventLoop *loop; + uint64_t connections; + uint64_t requests; + uint64_t complete; + uint64_t bytes; + uint64_t start; + tinymt64_t rand; + errors errors; + struct connection *cs; +} thread; + +typedef struct connection { + thread *thread; + http_parser parser; + int fd; + uint64_t start; + uint64_t latency; + char buf[RECVBUF]; +} connection; + +struct config; + +static void *thread_main(void *); +static int connect_socket(thread *, connection *); +static int reconnect_socket(thread *, connection *); + +static int sample_rate(aeEventLoop *, long long, void *); +static int check_timeouts(aeEventLoop *, long long, void *); + +static void socket_writeable(aeEventLoop *, int, void *, int); +static void socket_readable(aeEventLoop *, int, void *, int); +static int request_complete(http_parser *); + +static uint64_t time_us(); +static uint64_t rand64(tinymt64_t *, uint64_t); + +static char *extract_url_part(char *, struct http_parser_url *, enum http_parser_url_fields); +static char *format_request(char *, char *, char *, char **); + +static int parse_args(struct config *, char **, char **, int, char **); +static void print_stats_header(); +static void print_stats(char *, stats *, char *(*)(long double)); + +#endif /* WRK_H */ diff --git a/tools/wrk/src/zmalloc.c b/tools/wrk/src/zmalloc.c new file mode 100644 index 0000000000..89f80d8336 --- /dev/null +++ b/tools/wrk/src/zmalloc.c @@ -0,0 +1,287 @@ +/* zmalloc - total amount of allocated memory aware version of malloc() + * + * Copyright (c) 2009-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include "config.h" +#include "zmalloc.h" + +#ifdef HAVE_MALLOC_SIZE +#define PREFIX_SIZE (0) +#else +#if defined(__sun) || defined(__sparc) || defined(__sparc__) +#define PREFIX_SIZE (sizeof(long long)) +#else +#define PREFIX_SIZE (sizeof(size_t)) +#endif +#endif + +/* Explicitly override malloc/free etc when using tcmalloc. */ +#if defined(USE_TCMALLOC) +#define malloc(size) tc_malloc(size) +#define calloc(count,size) tc_calloc(count,size) +#define realloc(ptr,size) tc_realloc(ptr,size) +#define free(ptr) tc_free(ptr) +#elif defined(USE_JEMALLOC) +#define malloc(size) je_malloc(size) +#define calloc(count,size) je_calloc(count,size) +#define realloc(ptr,size) je_realloc(ptr,size) +#define free(ptr) je_free(ptr) +#endif + +#define update_zmalloc_stat_alloc(__n,__size) do { \ + size_t _n = (__n); \ + if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ + if (zmalloc_thread_safe) { \ + pthread_mutex_lock(&used_memory_mutex); \ + used_memory += _n; \ + pthread_mutex_unlock(&used_memory_mutex); \ + } else { \ + used_memory += _n; \ + } \ +} while(0) + +#define update_zmalloc_stat_free(__n) do { \ + size_t _n = (__n); \ + if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ + if (zmalloc_thread_safe) { \ + pthread_mutex_lock(&used_memory_mutex); \ + used_memory -= _n; \ + pthread_mutex_unlock(&used_memory_mutex); \ + } else { \ + used_memory -= _n; \ + } \ +} while(0) + +static size_t used_memory = 0; +static int zmalloc_thread_safe = 0; +pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void zmalloc_oom(size_t size) { + fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", + size); + fflush(stderr); + abort(); +} + +void *zmalloc(size_t size) { + void *ptr = malloc(size+PREFIX_SIZE); + + if (!ptr) zmalloc_oom(size); +#ifdef HAVE_MALLOC_SIZE + update_zmalloc_stat_alloc(zmalloc_size(ptr),size); + return ptr; +#else + *((size_t*)ptr) = size; + update_zmalloc_stat_alloc(size+PREFIX_SIZE,size); + return (char*)ptr+PREFIX_SIZE; +#endif +} + +void *zcalloc(size_t size) { + void *ptr = calloc(1, size+PREFIX_SIZE); + + if (!ptr) zmalloc_oom(size); +#ifdef HAVE_MALLOC_SIZE + update_zmalloc_stat_alloc(zmalloc_size(ptr),size); + return ptr; +#else + *((size_t*)ptr) = size; + update_zmalloc_stat_alloc(size+PREFIX_SIZE,size); + return (char*)ptr+PREFIX_SIZE; +#endif +} + +void *zrealloc(void *ptr, size_t size) { +#ifndef HAVE_MALLOC_SIZE + void *realptr; +#endif + size_t oldsize; + void *newptr; + + if (ptr == NULL) return zmalloc(size); +#ifdef HAVE_MALLOC_SIZE + oldsize = zmalloc_size(ptr); + newptr = realloc(ptr,size); + if (!newptr) zmalloc_oom(size); + + update_zmalloc_stat_free(oldsize); + update_zmalloc_stat_alloc(zmalloc_size(newptr),size); + return newptr; +#else + realptr = (char*)ptr-PREFIX_SIZE; + oldsize = *((size_t*)realptr); + newptr = realloc(realptr,size+PREFIX_SIZE); + if (!newptr) zmalloc_oom(size); + + *((size_t*)newptr) = size; + update_zmalloc_stat_free(oldsize); + update_zmalloc_stat_alloc(size,size); + return (char*)newptr+PREFIX_SIZE; +#endif +} + +/* Provide zmalloc_size() for systems where this function is not provided by + * malloc itself, given that in that case we store an header with this + * information as the first bytes of every allocation. */ +#ifndef HAVE_MALLOC_SIZE +size_t zmalloc_size(void *ptr) { + void *realptr = (char*)ptr-PREFIX_SIZE; + size_t size = *((size_t*)realptr); + /* Assume at least that all the allocations are padded at sizeof(long) by + * the underlying allocator. */ + if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1)); + return size+PREFIX_SIZE; +} +#endif + +void zfree(void *ptr) { +#ifndef HAVE_MALLOC_SIZE + void *realptr; + size_t oldsize; +#endif + + if (ptr == NULL) return; +#ifdef HAVE_MALLOC_SIZE + update_zmalloc_stat_free(zmalloc_size(ptr)); + free(ptr); +#else + realptr = (char*)ptr-PREFIX_SIZE; + oldsize = *((size_t*)realptr); + update_zmalloc_stat_free(oldsize+PREFIX_SIZE); + free(realptr); +#endif +} + +char *zstrdup(const char *s) { + size_t l = strlen(s)+1; + char *p = zmalloc(l); + + memcpy(p,s,l); + return p; +} + +size_t zmalloc_used_memory(void) { + size_t um; + + if (zmalloc_thread_safe) pthread_mutex_lock(&used_memory_mutex); + um = used_memory; + if (zmalloc_thread_safe) pthread_mutex_unlock(&used_memory_mutex); + return um; +} + +void zmalloc_enable_thread_safeness(void) { + zmalloc_thread_safe = 1; +} + +/* Get the RSS information in an OS-specific way. + * + * WARNING: the function zmalloc_get_rss() is not designed to be fast + * and may not be called in the busy loops where Redis tries to release + * memory expiring or swapping out objects. + * + * For this kind of "fast RSS reporting" usages use instead the + * function RedisEstimateRSS() that is a much faster (and less precise) + * version of the funciton. */ + +#if defined(HAVE_PROCFS) +#include +#include +#include +#include + +size_t zmalloc_get_rss(void) { + int page = sysconf(_SC_PAGESIZE); + size_t rss; + char buf[4096]; + char filename[256]; + int fd, count; + char *p, *x; + + snprintf(filename,256,"/proc/%d/stat",getpid()); + if ((fd = open(filename,O_RDONLY)) == -1) return 0; + if (read(fd,buf,4096) <= 0) { + close(fd); + return 0; + } + close(fd); + + p = buf; + count = 23; /* RSS is the 24th field in /proc//stat */ + while(p && count--) { + p = strchr(p,' '); + if (p) p++; + } + if (!p) return 0; + x = strchr(p,' '); + if (!x) return 0; + *x = '\0'; + + rss = strtoll(p,NULL,10); + rss *= page; + return rss; +} +#elif defined(HAVE_TASKINFO) +#include +#include +#include +#include +#include +#include +#include + +size_t zmalloc_get_rss(void) { + task_t task = MACH_PORT_NULL; + struct task_basic_info t_info; + mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; + + if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS) + return 0; + task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); + + return t_info.resident_size; +} +#else +size_t zmalloc_get_rss(void) { + /* If we can't get the RSS in an OS-specific way for this system just + * return the memory usage we estimated in zmalloc().. + * + * Fragmentation will appear to be always 1 (no fragmentation) + * of course... */ + return zmalloc_used_memory(); +} +#endif + +/* Fragmentation = RSS / allocated-bytes */ +float zmalloc_get_fragmentation_ratio(void) { + return (float)zmalloc_get_rss()/zmalloc_used_memory(); +} diff --git a/tools/wrk/src/zmalloc.h b/tools/wrk/src/zmalloc.h new file mode 100644 index 0000000000..995814c86b --- /dev/null +++ b/tools/wrk/src/zmalloc.h @@ -0,0 +1,83 @@ +/* zmalloc - total amount of allocated memory aware version of malloc() + * + * Copyright (c) 2009-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ZMALLOC_H +#define __ZMALLOC_H + +/* Double expansion needed for stringification of macro values. */ +#define __xstr(s) __str(s) +#define __str(s) #s + +#if defined(USE_TCMALLOC) +#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR)) +#include +#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6 +#define HAVE_MALLOC_SIZE 1 +#define zmalloc_size(p) tc_malloc_size(p) +#else +#error "Newer version of tcmalloc required" +#endif + +#elif defined(USE_JEMALLOC) +#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX)) +#define JEMALLOC_MANGLE +#include +#if JEMALLOC_VERSION_MAJOR >= 2 && JEMALLOC_VERSION_MINOR >= 1 +#define HAVE_MALLOC_SIZE 1 +#define zmalloc_size(p) JEMALLOC_P(malloc_usable_size)(p) +#else +#error "Newer version of jemalloc required" +#endif + +#elif defined(__APPLE__) +#include +#define HAVE_MALLOC_SIZE 1 +#define zmalloc_size(p) malloc_size(p) +#endif + +#ifndef ZMALLOC_LIB +#define ZMALLOC_LIB "libc" +#endif + +void *zmalloc(size_t size); +void *zcalloc(size_t size); +void *zrealloc(void *ptr, size_t size); +void zfree(void *ptr); +char *zstrdup(const char *s); +size_t zmalloc_used_memory(void); +void zmalloc_enable_thread_safeness(void); +float zmalloc_get_fragmentation_ratio(void); +size_t zmalloc_get_rss(void); + +#ifndef HAVE_MALLOC_SIZE +size_t zmalloc_size(void *ptr); +#endif + +#endif /* __ZMALLOC_H */ From ef08f0fbb19ba1e45721b759f37efd6574c400a3 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 12:20:36 -0800 Subject: [PATCH 40/45] bench: Use wrk for http benchmarking Remove ab, since it's no longer used. --- Makefile | 6 +++++- benchmark/common.js | 27 ++++++++++++++------------- benchmark/http/cluster.js | 7 ++++--- benchmark/http/http_simple.js | 11 +++-------- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 887b285454..3128cfa78c 100644 --- a/Makefile +++ b/Makefile @@ -313,6 +313,10 @@ dist-upload: $(TARBALL) $(PKG) scp $(TARBALL) node@nodejs.org:~/web/nodejs.org/dist/$(VERSION)/$(TARBALL) scp $(PKG) node@nodejs.org:~/web/nodejs.org/dist/$(VERSION)/$(TARNAME).pkg +wrkclean: + $(MAKE) -C tools/wrk/ clean + rm tools/wrk/wrk + wrk: tools/wrk/wrk tools/wrk/wrk: $(MAKE) -C tools/wrk/ @@ -323,7 +327,7 @@ bench-net: all bench-tls: all @$(NODE) benchmark/common.js tls -bench-http: all +bench-http: wrk all @$(NODE) benchmark/common.js http bench-fs: all diff --git a/benchmark/common.js b/benchmark/common.js index 289f17d128..3c478bd019 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -11,7 +11,6 @@ if (module === require.main) { process.exit(1); } - var path = require('path'); var fs = require('fs'); var dir = path.join(__dirname, type); var tests = fs.readdirSync(dir); @@ -59,16 +58,18 @@ function Benchmark(fn, options) { }); } -// run ab against a server. -Benchmark.prototype.ab = function(path, args, cb) { - var url = 'http://127.0.0.1:' + exports.PORT + path; - args.push(url); - +// benchmark an http server. +Benchmark.prototype.http = function(p, args, cb) { var self = this; - var out = ''; + var wrk = path.resolve(__dirname, '..', 'tools', 'wrk', 'wrk'); + var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/; var spawn = require('child_process').spawn; - // console.error('ab %s', args.join(' ')); - var child = spawn('ab', args); + 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'); @@ -81,14 +82,14 @@ Benchmark.prototype.ab = function(path, args, cb) { cb(code); if (code) { - console.error('ab failed with ' + code); + console.error('wrk failed with ' + code); process.exit(code) } - var m = out.match(/Requests per second: +([0-9\.]+)/); + var m = out.match(regexp); var qps = m && +m[1]; if (!qps) { - process.stderr.write(out + '\n'); - console.error('ab produced strange output'); + console.error('%j', out); + console.error('wrk produced strange output'); process.exit(1); } self.report(+qps); diff --git a/benchmark/http/cluster.js b/benchmark/http/cluster.js index a8c7abe689..12bb8d5946 100644 --- a/benchmark/http/cluster.js +++ b/benchmark/http/cluster.js @@ -7,7 +7,7 @@ if (cluster.isMaster) { // unicode confuses ab on os x. type: ['bytes', 'buffer'], length: [4, 1024, 102400], - c: [50, 150] + c: [50, 500] }); } else { require('../http_simple.js'); @@ -27,11 +27,12 @@ function main(conf) { setTimeout(function() { var path = '/' + conf.type + '/' + conf.length; var args = ['-r', '-t', 5, '-c', conf.c, '-k']; + var args = ['-r', 5000, '-t', 8, '-c', conf.c]; - bench.ab(path, args, function() { + bench.http(path, args, function() { w1.destroy(); w2.destroy(); }); - }, 2000); + }, 100); }); } diff --git a/benchmark/http/http_simple.js b/benchmark/http/http_simple.js index 2cef41f46f..04a2a2911f 100644 --- a/benchmark/http/http_simple.js +++ b/benchmark/http/http_simple.js @@ -5,7 +5,7 @@ var bench = common.createBenchmark(main, { // unicode confuses ab on os x. type: ['bytes', 'buffer'], length: [4, 1024, 102400], - c: [50, 150] + c: [50, 500] }); function main(conf) { @@ -15,14 +15,9 @@ function main(conf) { var server = spawn(process.execPath, [simple]); setTimeout(function() { var path = '/' + conf.type + '/' + conf.length; //+ '/' + conf.chunks; - var args = ['-r', '-t', 5]; + var args = ['-r', 5000, '-t', 8, '-c', conf.c]; - if (+conf.c !== 1) - args.push('-c', conf.c); - - args.push('-k'); - - bench.ab(path, args, function() { + bench.http(path, args, function() { server.kill(); }); }, 2000); From 06fbcca6bb14093ca3bed6bd558d900ad8cead62 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 14:16:11 -0800 Subject: [PATCH 41/45] bench: Remove _bench_timer (no loner used) --- benchmark/_bench_timer.js | 88 --------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 benchmark/_bench_timer.js diff --git a/benchmark/_bench_timer.js b/benchmark/_bench_timer.js deleted file mode 100644 index 43460945fb..0000000000 --- a/benchmark/_bench_timer.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This is a simple addition to allow for higher resolution timers. - * It can be used to track time for both synchronous or asynchronous - * calls. For synchronous calls pass a callback function like so: - * - * var timer = require('./_bench_timer'); - * - * timer('myTest', function() { - * for (var i = 0; i < 1e6; i++) - * // ... run something here - * } - * }); - * - * For asynchronous timers just pass the name. Then run it again with - * the same name to finish it: - * - * timer('checkAsync'); - * setTimeout(function() { - * timer('checkAsync'); - * }, 300); - * - * When this happens all currently queued benchmarks will be paused - * until the asynchronous benchmark has completed. - * - * If the benchmark has been run with --expose_gc then the garbage - * collector will be run between each test. - * - * The setTimeout delay can also be changed by passing a value to - * timer.delay. - */ - - -var store = {}; -var order = []; -var maxLength = 0; -var processing = false; -var asyncQueue = 0; -var GCd = typeof gc !== 'function' ? false : true; - -function timer(name, fn) { - if (maxLength < name.length) - maxLength = name.length; - if (!fn) { - processing = false; - if (!store[name]) { - asyncQueue++; - store[name] = process.hrtime(); - return; - } - displayTime(name, process.hrtime(store[name])); - asyncQueue--; - } else { - store[name] = fn; - order.push(name); - } - if (!processing && asyncQueue <= 0) { - processing = true; - setTimeout(run, timer.delay); - } -} - -timer.delay = 100; - -function run() { - if (asyncQueue > 0 || order.length <= 0) - return; - if (GCd) gc(); - setTimeout(function() { - var name = order.shift(); - var fn = store[name]; - var ini = process.hrtime(); - fn(); - ini = process.hrtime(ini); - displayTime(name, ini); - run(); - }, timer.delay); -} - -function displayTime(name, ini) { - name += ': '; - while (name.length < maxLength + 2) - name += ' '; - console.log(name + '%s \u00b5s', - (~~((ini[0] * 1e6) + (ini[1] / 1e3))) - .toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")); -} - -module.exports = timer; From 1eb6a92984d8de8cddc69d7628d1fca23d3f0277 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 13 Feb 2013 14:18:27 -0800 Subject: [PATCH 42/45] bench: Only run http,net,fs,tls by default --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3128cfa78c..4e8655425d 100644 --- a/Makefile +++ b/Makefile @@ -343,7 +343,9 @@ bench-array: all bench-buffer: all @$(NODE) benchmark/common.js buffers -bench: bench-misc bench-net bench-http bench-fs bench-array bench-buffer bench-tls +bench-all: bench bench-misc bench-array bench-buffer + +bench: bench-net bench-http bench-fs bench-tls bench-http-simple: benchmark/http_simple_bench.sh @@ -364,4 +366,4 @@ cpplint: lint: jslint cpplint -.PHONY: lint cpplint jslint bench clean docopen docclean doc dist distclean check uninstall install install-includes install-bin all staticlib dynamiclib test test-all website-upload pkg blog blogclean tar binary release-only +.PHONY: lint cpplint jslint bench clean docopen docclean doc dist distclean check uninstall install install-includes install-bin all staticlib dynamiclib test test-all website-upload pkg blog blogclean tar binary release-only bench-http-simple bench-idle bench-all bench bench-misc bench-array bench-buffer bench-net bench-http bench-fs bench-tls From 2ed56e52354d871622e6b9ca5bf44fa7409b511b Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 19 Feb 2013 14:59:43 -0800 Subject: [PATCH 43/45] bench: Consistency in benchmark filenames --- benchmark/arrays/{var_int.js => var-int.js} | 0 benchmark/arrays/{zero_float.js => zero-float.js} | 0 benchmark/arrays/{zero_int.js => zero-int.js} | 0 benchmark/buffers/{buffer_creation.js => buffer-creation.js} | 0 benchmark/buffers/{buffer_read.js => buffer-read.js} | 0 benchmark/buffers/{buffer_write.js => buffer-write.js} | 0 benchmark/buffers/{dataview_set.js => dataview-set.js} | 0 benchmark/http/{http_simple.js => simple.js} | 0 benchmark/misc/{string_creation.js => string-creation.js} | 0 benchmark/misc/{v8_bench.js => v8-bench.js} | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename benchmark/arrays/{var_int.js => var-int.js} (100%) rename benchmark/arrays/{zero_float.js => zero-float.js} (100%) rename benchmark/arrays/{zero_int.js => zero-int.js} (100%) rename benchmark/buffers/{buffer_creation.js => buffer-creation.js} (100%) rename benchmark/buffers/{buffer_read.js => buffer-read.js} (100%) rename benchmark/buffers/{buffer_write.js => buffer-write.js} (100%) rename benchmark/buffers/{dataview_set.js => dataview-set.js} (100%) rename benchmark/http/{http_simple.js => simple.js} (100%) rename benchmark/misc/{string_creation.js => string-creation.js} (100%) rename benchmark/misc/{v8_bench.js => v8-bench.js} (100%) diff --git a/benchmark/arrays/var_int.js b/benchmark/arrays/var-int.js similarity index 100% rename from benchmark/arrays/var_int.js rename to benchmark/arrays/var-int.js diff --git a/benchmark/arrays/zero_float.js b/benchmark/arrays/zero-float.js similarity index 100% rename from benchmark/arrays/zero_float.js rename to benchmark/arrays/zero-float.js diff --git a/benchmark/arrays/zero_int.js b/benchmark/arrays/zero-int.js similarity index 100% rename from benchmark/arrays/zero_int.js rename to benchmark/arrays/zero-int.js diff --git a/benchmark/buffers/buffer_creation.js b/benchmark/buffers/buffer-creation.js similarity index 100% rename from benchmark/buffers/buffer_creation.js rename to benchmark/buffers/buffer-creation.js diff --git a/benchmark/buffers/buffer_read.js b/benchmark/buffers/buffer-read.js similarity index 100% rename from benchmark/buffers/buffer_read.js rename to benchmark/buffers/buffer-read.js diff --git a/benchmark/buffers/buffer_write.js b/benchmark/buffers/buffer-write.js similarity index 100% rename from benchmark/buffers/buffer_write.js rename to benchmark/buffers/buffer-write.js diff --git a/benchmark/buffers/dataview_set.js b/benchmark/buffers/dataview-set.js similarity index 100% rename from benchmark/buffers/dataview_set.js rename to benchmark/buffers/dataview-set.js diff --git a/benchmark/http/http_simple.js b/benchmark/http/simple.js similarity index 100% rename from benchmark/http/http_simple.js rename to benchmark/http/simple.js diff --git a/benchmark/misc/string_creation.js b/benchmark/misc/string-creation.js similarity index 100% rename from benchmark/misc/string_creation.js rename to benchmark/misc/string-creation.js diff --git a/benchmark/misc/v8_bench.js b/benchmark/misc/v8-bench.js similarity index 100% rename from benchmark/misc/v8_bench.js rename to benchmark/misc/v8-bench.js From 4b80f217cd91a5b29089d94398059b85b1ef8a93 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 19 Feb 2013 15:03:41 -0800 Subject: [PATCH 44/45] bench: Simplify duration arguments to benchmarks For throughput benchmarks, run with just 5s durations rather than 1s and 3s. For startup benchmark, run with just a single 1s duration, since it's very consistent anyway. --- benchmark/fs/readfile.js | 2 +- benchmark/fs/write-stream-throughput.js | 2 +- benchmark/misc/startup.js | 2 +- benchmark/net/net-c2s.js | 2 +- benchmark/net/net-pipe.js | 2 +- benchmark/net/net-s2c.js | 2 +- benchmark/net/tcp-raw-c2s.js | 2 +- benchmark/net/tcp-raw-pipe.js | 2 +- benchmark/net/tcp-raw-s2c.js | 2 +- benchmark/tls/throughput.js | 2 +- benchmark/tls/tls-connect.js | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/benchmark/fs/readfile.js b/benchmark/fs/readfile.js index 38548ef647..ac3241901b 100644 --- a/benchmark/fs/readfile.js +++ b/benchmark/fs/readfile.js @@ -8,7 +8,7 @@ var filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); var fs = require('fs'); var bench = common.createBenchmark(main, { - dur: [1, 3], + dur: [5], len: [1024, 16 * 1024 * 1024], concurrent: [1, 10] }); diff --git a/benchmark/fs/write-stream-throughput.js b/benchmark/fs/write-stream-throughput.js index d37299ab38..57ce4c4fec 100644 --- a/benchmark/fs/write-stream-throughput.js +++ b/benchmark/fs/write-stream-throughput.js @@ -6,7 +6,7 @@ var filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); var fs = require('fs'); var bench = common.createBenchmark(main, { - dur: [1, 3], + dur: [5], type: ['buf', 'asc', 'utf'], size: [2, 1024, 65535, 1024 * 1024] }); diff --git a/benchmark/misc/startup.js b/benchmark/misc/startup.js index 25b3f56fef..aa5b4420f6 100644 --- a/benchmark/misc/startup.js +++ b/benchmark/misc/startup.js @@ -7,7 +7,7 @@ var i = 0; var start; var bench = common.createBenchmark(startNode, { - dur: [1, 3] + dur: [1] }); function startNode(conf) { diff --git a/benchmark/net/net-c2s.js b/benchmark/net/net-c2s.js index 0467643f3a..49de7c77c9 100644 --- a/benchmark/net/net-c2s.js +++ b/benchmark/net/net-c2s.js @@ -6,7 +6,7 @@ var PORT = common.PORT; var bench = common.createBenchmark(main, { len: [102400, 1024 * 1024 * 16], type: ['utf', 'asc', 'buf'], - dur: [1, 3], + dur: [5], }); var dur; diff --git a/benchmark/net/net-pipe.js b/benchmark/net/net-pipe.js index d3908e8439..2a5ed8e483 100644 --- a/benchmark/net/net-pipe.js +++ b/benchmark/net/net-pipe.js @@ -6,7 +6,7 @@ var PORT = common.PORT; var bench = common.createBenchmark(main, { len: [102400, 1024 * 1024 * 16], type: ['utf', 'asc', 'buf'], - dur: [1, 3], + dur: [5], }); var dur; diff --git a/benchmark/net/net-s2c.js b/benchmark/net/net-s2c.js index 96cb67d242..79e22494a7 100644 --- a/benchmark/net/net-s2c.js +++ b/benchmark/net/net-s2c.js @@ -6,7 +6,7 @@ var PORT = common.PORT; var bench = common.createBenchmark(main, { len: [102400, 1024 * 1024 * 16], type: ['utf', 'asc', 'buf'], - dur: [1, 3], + dur: [5] }); var dur; diff --git a/benchmark/net/tcp-raw-c2s.js b/benchmark/net/tcp-raw-c2s.js index 2664e25fe0..e5b3662a8b 100644 --- a/benchmark/net/tcp-raw-c2s.js +++ b/benchmark/net/tcp-raw-c2s.js @@ -9,7 +9,7 @@ var common = require('../common.js'); var bench = common.createBenchmark(main, { len: [102400, 1024 * 1024 * 16], type: ['utf', 'asc', 'buf'], - dur: [1, 3], + dur: [5] }); var TCP = process.binding('tcp_wrap').TCP; diff --git a/benchmark/net/tcp-raw-pipe.js b/benchmark/net/tcp-raw-pipe.js index d55c2c5d7e..4e53c1f0ca 100644 --- a/benchmark/net/tcp-raw-pipe.js +++ b/benchmark/net/tcp-raw-pipe.js @@ -9,7 +9,7 @@ var common = require('../common.js'); var bench = common.createBenchmark(main, { len: [102400, 1024 * 1024 * 16], type: ['utf', 'asc', 'buf'], - dur: [1, 3], + dur: [5] }); var TCP = process.binding('tcp_wrap').TCP; diff --git a/benchmark/net/tcp-raw-s2c.js b/benchmark/net/tcp-raw-s2c.js index 94a90f29de..93a917e06f 100644 --- a/benchmark/net/tcp-raw-s2c.js +++ b/benchmark/net/tcp-raw-s2c.js @@ -9,7 +9,7 @@ var common = require('../common.js'); var bench = common.createBenchmark(main, { len: [102400, 1024 * 1024 * 16], type: ['utf', 'asc', 'buf'], - dur: [1, 3], + dur: [5] }); var TCP = process.binding('tcp_wrap').TCP; diff --git a/benchmark/tls/throughput.js b/benchmark/tls/throughput.js index 3896382492..88118afbc0 100644 --- a/benchmark/tls/throughput.js +++ b/benchmark/tls/throughput.js @@ -1,6 +1,6 @@ var common = require('../common.js'); var bench = common.createBenchmark(main, { - dur: [1, 3], + dur: [5], type: ['buf', 'asc', 'utf'], size: [2, 1024, 1024 * 1024] }); diff --git a/benchmark/tls/tls-connect.js b/benchmark/tls/tls-connect.js index b3bd3e3c7a..0da448ee85 100644 --- a/benchmark/tls/tls-connect.js +++ b/benchmark/tls/tls-connect.js @@ -6,7 +6,7 @@ var assert = require('assert'), var common = require('../common.js'); var bench = common.createBenchmark(main, { concurrency: [1, 10], - dur: [1, 3] + dur: [5] }); var clientConn = 0; From bd4d585b7a9d9ecab5e8c85c797220f87b64a46e Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 19 Feb 2013 16:57:59 -0800 Subject: [PATCH 45/45] bench: Add bench-crypto --- Makefile | 3 + benchmark/crypto/cipher-stream.js | 103 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 benchmark/crypto/cipher-stream.js diff --git a/Makefile b/Makefile index 4e8655425d..a3eae2806e 100644 --- a/Makefile +++ b/Makefile @@ -324,6 +324,9 @@ tools/wrk/wrk: bench-net: all @$(NODE) benchmark/common.js net +bench-crypto: all + @$(NODE) benchmark/common.js crypto + bench-tls: all @$(NODE) benchmark/common.js tls diff --git a/benchmark/crypto/cipher-stream.js b/benchmark/crypto/cipher-stream.js new file mode 100644 index 0000000000..2a48a7e3f3 --- /dev/null +++ b/benchmark/crypto/cipher-stream.js @@ -0,0 +1,103 @@ +var common = require('../common.js'); + +var bench = common.createBenchmark(main, { + writes: [500], + cipher: [ 'AES192', 'AES256' ], + type: ['asc', 'utf', 'buf'], + len: [2, 1024, 102400, 1024 * 1024], + api: ['legacy', 'stream'] +}); + +function main(conf) { + var api = conf.api; + if (api === 'stream' && process.version.match(/^v0\.[0-8]\./)) { + console.error('Crypto streams not available until v0.10'); + // use the legacy, just so that we can compare them. + api = 'legacy'; + } + + var dur = conf.dur; + + var crypto = require('crypto'); + var assert = require('assert'); + var alice = crypto.getDiffieHellman('modp5'); + var bob = crypto.getDiffieHellman('modp5'); + + alice.generateKeys(); + bob.generateKeys(); + + + var pubEnc = /^v0\.[0-8]/.test(process.version) ? 'binary' : null; + var alice_secret = alice.computeSecret(bob.getPublicKey(), pubEnc, 'hex'); + var bob_secret = bob.computeSecret(alice.getPublicKey(), pubEnc, 'hex'); + + // alice_secret and bob_secret should be the same + assert(alice_secret == bob_secret); + + var alice_cipher = crypto.createCipher(conf.cipher, alice_secret); + var bob_cipher = crypto.createDecipher(conf.cipher, bob_secret); + + var message; + var encoding; + switch (conf.type) { + case 'asc': + message = new Array(conf.len + 1).join('a'); + encoding = 'ascii'; + break; + case 'utf': + message = new Array(conf.len / 2 + 1).join('ü'); + encoding = 'utf8'; + break; + case 'buf': + message = new Buffer(conf.len); + message.fill('b'); + break; + default: + throw new Error('unknown message type: ' + conf.type); + } + + var fn = api === 'stream' ? streamWrite : legacyWrite; + + // write data as fast as possible to alice, and have bob decrypt. + // use old API for comparison to v0.8 + bench.start(); + fn(alice_cipher, bob_cipher, message, encoding, conf.writes); +} + +function streamWrite(alice, bob, message, encoding, writes) { + var written = 0; + bob.on('data', function(c) { + written += c.length; + }); + + bob.on('end', function() { + // Gbits + var bits = written * 8; + var gbits = written / (1024 * 1024 * 1024); + bench.end(gbits); + }); + + alice.pipe(bob); + + while (writes-- > 0) + alice.write(message, encoding); + + alice.end(); +} + +function legacyWrite(alice, bob, message, encoding, writes) { + var written = 0; + for (var i = 0; i < writes; i++) { + var enc = alice.update(message, encoding); + var dec = bob.update(enc); + written += dec.length; + } + var enc = alice.final(); + var dec = bob.update(enc); + written += dec.length; + dec = bob.final(); + written += dec.length; + var bits = written * 8; + var gbits = written / (1024 * 1024 * 1024); + bench.end(gbits); +}