summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile43
-rw-r--r--benchmark/_bench_timer.js88
-rw-r--r--benchmark/arrays/var-int.js20
-rw-r--r--benchmark/arrays/var_int.js15
-rw-r--r--benchmark/arrays/zero-float.js20
-rw-r--r--benchmark/arrays/zero-int.js20
-rw-r--r--benchmark/arrays/zero_float.js15
-rw-r--r--benchmark/arrays/zero_int.js15
-rw-r--r--benchmark/buffer_creation.js6
-rw-r--r--benchmark/buffer_read.js97
-rw-r--r--benchmark/buffer_write.js103
-rw-r--r--benchmark/buffers/buffer-base64-encode.js (renamed from benchmark/next-tick-2.js)29
-rw-r--r--benchmark/buffers/buffer-creation.js19
-rw-r--r--benchmark/buffers/buffer-read.js28
-rw-r--r--benchmark/buffers/buffer-write.js63
-rw-r--r--benchmark/buffers/dataview-set.js57
-rw-r--r--benchmark/client_latency.js80
-rw-r--r--benchmark/common.js196
-rw-r--r--benchmark/compare.js138
-rw-r--r--benchmark/crypto/cipher-stream.js103
-rw-r--r--benchmark/dataview_set.js104
-rw-r--r--benchmark/fast_buffer2.js42
-rw-r--r--benchmark/fast_buffer2_creation.js6
-rw-r--r--benchmark/fast_buffer_creation.js4
-rw-r--r--benchmark/fs-readfile.js72
-rw-r--r--benchmark/fs/read-stream-throughput.js87
-rw-r--r--benchmark/fs/readfile.js48
-rw-r--r--benchmark/fs/write-stream-throughput.js78
-rw-r--r--benchmark/function_call/bench.js43
-rw-r--r--benchmark/function_call/wscript15
-rw-r--r--benchmark/http/cluster.js38
-rw-r--r--benchmark/http/simple.js24
-rw-r--r--benchmark/http_simple.js14
-rw-r--r--benchmark/io.c3
-rw-r--r--benchmark/io.js109
-rw-r--r--benchmark/misc/function_call/.gitignore1
-rw-r--r--benchmark/misc/function_call/Makefile2
-rw-r--r--benchmark/misc/function_call/binding.cc (renamed from benchmark/function_call/binding.cc)5
-rw-r--r--benchmark/misc/function_call/binding.gyp8
-rw-r--r--benchmark/misc/function_call/index.js42
-rw-r--r--benchmark/misc/next-tick-breadth.js21
-rw-r--r--benchmark/misc/next-tick-depth.js (renamed from benchmark/buffer-base64-encode.js)25
-rw-r--r--benchmark/misc/spawn-echo.js25
-rw-r--r--benchmark/misc/startup.js40
-rw-r--r--benchmark/misc/string-creation.js16
-rw-r--r--benchmark/misc/timers.js40
-rw-r--r--benchmark/misc/url.js (renamed from benchmark/url.js)14
-rw-r--r--benchmark/misc/v8-bench.js (renamed from benchmark/v8_bench.js)7
-rw-r--r--benchmark/net-pipe.js114
-rw-r--r--benchmark/net/net-c2s.js112
-rw-r--r--benchmark/net/net-pipe.js115
-rw-r--r--benchmark/net/net-s2c.js112
-rw-r--r--benchmark/net/tcp-raw-c2s.js136
-rw-r--r--benchmark/net/tcp-raw-pipe.js149
-rw-r--r--benchmark/net/tcp-raw-s2c.js136
-rw-r--r--benchmark/next-tick.js17
-rw-r--r--benchmark/process_loop.js19
-rw-r--r--benchmark/run.js31
-rw-r--r--benchmark/settimeout.js15
-rw-r--r--benchmark/startup.js26
-rw-r--r--benchmark/string_creation.js6
-rw-r--r--benchmark/throughput-child.js24
-rw-r--r--benchmark/throughput.js21
-rw-r--r--benchmark/timers.js5
-rw-r--r--benchmark/tls-connect.js86
-rw-r--r--benchmark/tls-fragmentation.js63
-rw-r--r--benchmark/tls/throughput.js74
-rw-r--r--benchmark/tls/tls-connect.js63
-rw-r--r--tools/wrk/.gitignore2
-rw-r--r--tools/wrk/LICENSE177
-rw-r--r--tools/wrk/Makefile35
-rw-r--r--tools/wrk/NOTICE115
-rw-r--r--tools/wrk/README37
-rw-r--r--tools/wrk/src/ae.c435
-rw-r--r--tools/wrk/src/ae.h118
-rw-r--r--tools/wrk/src/ae_epoll.c130
-rw-r--r--tools/wrk/src/ae_evport.c315
-rw-r--r--tools/wrk/src/ae_kqueue.c132
-rw-r--r--tools/wrk/src/ae_select.c99
-rw-r--r--tools/wrk/src/aprintf.c27
-rw-r--r--tools/wrk/src/aprintf.h6
-rw-r--r--tools/wrk/src/config.h13
-rw-r--r--tools/wrk/src/http_parser.c2058
-rw-r--r--tools/wrk/src/http_parser.h317
-rw-r--r--tools/wrk/src/stats.c73
-rw-r--r--tools/wrk/src/stats.h21
-rw-r--r--tools/wrk/src/tinymt64.c129
-rw-r--r--tools/wrk/src/tinymt64.h210
-rw-r--r--tools/wrk/src/units.c96
-rw-r--r--tools/wrk/src/units.h10
-rw-r--r--tools/wrk/src/wrk.c482
-rw-r--r--tools/wrk/src/wrk.h75
-rw-r--r--tools/wrk/src/zmalloc.c287
-rw-r--r--tools/wrk/src/zmalloc.h83
95 files changed, 7605 insertions, 1291 deletions
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 779547bbcb..a3eae2806e 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,44 @@ 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:
+wrkclean:
+ $(MAKE) -C tools/wrk/ clean
+ rm tools/wrk/wrk
+
+wrk: tools/wrk/wrk
+tools/wrk/wrk:
+ $(MAKE) -C tools/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
+
+bench-http: wrk 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-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
bench-idle:
@@ -330,4 +369,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
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;
diff --git a/benchmark/arrays/var-int.js b/benchmark/arrays/var-int.js
new file mode 100644
index 0000000000..47a7e62dc7
--- /dev/null
+++ b/benchmark/arrays/var-int.js
@@ -0,0 +1,20 @@
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+ type: 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '),
+ n: [25]
+});
+
+function main(conf) {
+ var type = conf.type;
+ var clazz = global[type];
+ var n = +conf.n;
+
+ 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/var_int.js b/benchmark/arrays/var_int.js
deleted file mode 100644
index 17e12989f5..0000000000
--- a/benchmark/arrays/var_int.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var types = 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' ');
-
-var type = types[types.indexOf(process.argv[2])];
-if (!type)
- type = types[0];
-
-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;
- }
-}
diff --git a/benchmark/arrays/zero-float.js b/benchmark/arrays/zero-float.js
new file mode 100644
index 0000000000..a6624205bf
--- /dev/null
+++ b/benchmark/arrays/zero-float.js
@@ -0,0 +1,20 @@
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+ type: 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '),
+ n: [25]
+});
+
+function main(conf) {
+ var type = conf.type;
+ var clazz = global[type];
+ var n = +conf.n;
+
+ 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
new file mode 100644
index 0000000000..29a2d58b66
--- /dev/null
+++ b/benchmark/arrays/zero-int.js
@@ -0,0 +1,20 @@
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+ type: 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' '),
+ n: [25]
+});
+
+function main(conf) {
+ var type = conf.type;
+ var clazz = global[type];
+ var n = +conf.n;
+
+ 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);
+}
diff --git a/benchmark/arrays/zero_float.js b/benchmark/arrays/zero_float.js
deleted file mode 100644
index bdb8765d0a..0000000000
--- a/benchmark/arrays/zero_float.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var types = 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' ');
-
-var type = types[types.indexOf(process.argv[2])];
-if (!type)
- type = types[0];
-
-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;
- }
-}
diff --git a/benchmark/arrays/zero_int.js b/benchmark/arrays/zero_int.js
deleted file mode 100644
index 17dac62c14..0000000000
--- a/benchmark/arrays/zero_int.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var types = 'Array Buffer Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(' ');
-
-var type = types[types.indexOf(process.argv[2])];
-if (!type)
- type = types[0];
-
-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;
- }
-}
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/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/next-tick-2.js b/benchmark/buffers/buffer-base64-encode.js
index 44a2b41b54..f2b8e9a482 100644
--- a/benchmark/next-tick-2.js
+++ b/benchmark/buffers/buffer-base64-encode.js
@@ -19,23 +19,18 @@
// 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');
-function onNextTick() {
- if (--left) {
- process.nextTick(onNextTick);
- } else {
- finalize();
- }
-}
+var bench = common.createBenchmark(main, {});
-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 = 64 * 1024 * 1024;
+ var b = Buffer(N);
+ var s = '';
+ for (var i = 0; i < 256; ++i) s += String.fromCharCode(i);
-start = (new Date()).getTime();
-process.nextTick(onNextTick);
+ 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);
+}
diff --git a/benchmark/buffers/buffer-creation.js b/benchmark/buffers/buffer-creation.js
new file mode 100644
index 0000000000..bc0c557118
--- /dev/null
+++ b/benchmark/buffers/buffer-creation.js
@@ -0,0 +1,19 @@
+SlowBuffer = require('buffer').SlowBuffer;
+
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+ type: ['fast', 'slow'],
+ len: [10, 1024],
+ n: [1024]
+});
+
+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 clazz(len);
+ }
+ bench.end(n);
+}
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);
+}
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/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);
- });
-}
diff --git a/benchmark/common.js b/benchmark/common.js
new file mode 100644
index 0000000000..3c478bd019
--- /dev/null
+++ b/benchmark/common.js
@@ -0,0 +1,196 @@
+var assert = require('assert');
+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 <type>');
+ process.exit(1);
+ }
+
+ var fs = require('fs');
+ var dir = path.join(__dirname, type);
+ var tests = fs.readdirSync(dir);
+ var spawn = require('child_process').spawn;
+
+ runBenchmarks();
+
+ function runBenchmarks() {
+ var test = tests.shift();
+ if (!test)
+ return;
+
+ if (test.match(/^[\._]/))
+ return process.nextTick(runBenchmarks);
+
+ console.error(type + '/' + test);
+ test = path.resolve(dir, test);
+
+ var 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);
+};
+
+function Benchmark(fn, options) {
+ this.fn = fn;
+ this.options = options;
+ this.config = parseOpts(options);
+ this._name = require.main.filename.split(/benchmark[\/\\]/).pop();
+ this._start = [0,0];
+ this._started = false;
+ var self = this;
+ process.nextTick(function() {
+ self._run();
+ });
+}
+
+// benchmark an http server.
+Benchmark.prototype.http = function(p, args, cb) {
+ var self = this;
+ var wrk = path.resolve(__dirname, '..', 'tools', 'wrk', 'wrk');
+ var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/;
+ var spawn = require('child_process').spawn;
+ var url = 'http://127.0.0.1:' + exports.PORT + p;
+
+ args = args.concat(url);
+
+ var out = '';
+ var child = spawn(wrk, args);
+
+ child.stdout.setEncoding('utf8');
+
+ child.stdout.on('data', function(chunk) {
+ out += chunk;
+ });
+
+ child.on('close', function(code) {
+ if (cb)
+ cb(code);
+
+ if (code) {
+ console.error('wrk failed with ' + code);
+ process.exit(code)
+ }
+ var m = out.match(regexp);
+ var qps = m && +m[1];
+ if (!qps) {
+ console.error('%j', out);
+ console.error('wrk produced strange output');
+ process.exit(1);
+ }
+ self.report(+qps);
+ });
+};
+
+Benchmark.prototype._run = function() {
+ if (this.config)
+ return this.fn(this.config);
+
+ // one more more options weren't set.
+ // run with all combinations
+ var main = require.main.filename;
+ var settings = [];
+ var queueLen = 1;
+ var options = this.options;
+
+ var queue = Object.keys(options).reduce(function(set, key) {
+ var vals = options[key];
+ assert(Array.isArray(vals));
+
+ // match each item in the set with each item in the list
+ var newSet = new Array(set.length * vals.length);
+ var j = 0;
+ set.forEach(function(s) {
+ vals.forEach(function(val) {
+ newSet[j++] = s.concat(key + '=' + val);
+ });
+ });
+ return newSet;
+ }, [[main]]);
+
+ var spawn = require('child_process').spawn;
+ var node = process.execPath;
+ var i = 0;
+ function run() {
+ var argv = queue[i++];
+ if (!argv)
+ return;
+ var child = spawn(node, argv, { stdio: 'inherit' });
+ child.on('close', function(code, signal) {
+ if (code)
+ console.error('child process exited with code ' + code);
+ else
+ run();
+ });
+ }
+ run();
+};
+
+function parseOpts(options) {
+ // verify that there's an option provided for each of the options
+ // if they're not *all* specified, then we return null.
+ var keys = Object.keys(options);
+ var num = keys.length;
+ var conf = {};
+ for (var i = 2; i < process.argv.length; i++) {
+ var m = process.argv[i].match(/^(.+)=(.+)$/);
+ if (!m || !m[1] || !m[2] || !options[m[1]])
+ return null;
+ else {
+ conf[m[1]] = isFinite(m[2]) ? +m[2] : m[2]
+ num--;
+ }
+ }
+ // still go ahead and set whatever WAS set, if it was.
+ if (num !== 0) {
+ Object.keys(conf).forEach(function(k) {
+ options[k] = [conf[k]];
+ });
+ }
+ return num === 0 ? conf : null;
+};
+
+Benchmark.prototype.start = function() {
+ if (this._started)
+ throw new Error('Called start more than once in a single benchmark');
+ this._started = true;
+ this._start = process.hrtime();
+};
+
+Benchmark.prototype.end = function(operations) {
+ var elapsed = process.hrtime(this._start);
+ if (!this._started)
+ throw new Error('called end without start');
+ if (typeof operations !== 'number')
+ throw new Error('called end() without specifying operation count');
+ var time = elapsed[0] + elapsed[1]/1e9;
+ var rate = operations/time;
+ this.report(rate);
+};
+
+Benchmark.prototype.report = function(value) {
+ var heading = this.getHeading();
+ console.log('%s: %s', heading, value.toPrecision(5));
+ process.exit(0);
+};
+
+Benchmark.prototype.getHeading = function() {
+ var conf = this.config;
+ return this._name + ' ' + Object.keys(conf).map(function(key) {
+ return key + '=' + conf[key];
+ }).join(' ');
+}
diff --git a/benchmark/compare.js b/benchmark/compare.js
new file mode 100644
index 0000000000..8e1ab0963a
--- /dev/null
+++ b/benchmark/compare.js
@@ -0,0 +1,138 @@
+var usage = 'node benchmark/compare.js <node-binary1> <node-binary2> [--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) {
+ case '--red': case '-r':
+ show = show === 'green' ? 'both' : 'red';
+ break;
+ 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 = '<pre style="background-color:#333;color:#eee">';
+ var green = '<span style="background-color:#0f0;color:#000">';
+ var red = '<span style="background-color:#f00">';
+ var reset = '</span>';
+ var end = '</pre>';
+}
+
+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 maxLen = -Infinity;
+ var util = require('util');
+ console.log(start);
+
+ Object.keys(results).map(function(bench) {
+ var res = results[bench];
+ var n0 = res[nodes[0]];
+ var n1 = res[nodes[1]];
+
+ var pct = ((n0 - n1) / n1 * 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);
+ });
+ console.log(end);
+}
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);
+}
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);
- }
-});
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;
-}
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;
-}
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/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();
+ }
+}
diff --git a/benchmark/fs/readfile.js b/benchmark/fs/readfile.js
new file mode 100644
index 0000000000..ac3241901b
--- /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: [5],
+ 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();
+}
diff --git a/benchmark/fs/write-stream-throughput.js b/benchmark/fs/write-stream-throughput.js
new file mode 100644
index 0000000000..57ce4c4fec
--- /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: [5],
+ 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');
+ }
+}
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/http/cluster.js b/benchmark/http/cluster.js
new file mode 100644
index 0000000000..12bb8d5946
--- /dev/null
+++ b/benchmark/http/cluster.js
@@ -0,0 +1,38 @@
+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, 500]
+ });
+} 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'];
+ var args = ['-r', 5000, '-t', 8, '-c', conf.c];
+
+ bench.http(path, args, function() {
+ w1.destroy();
+ w2.destroy();
+ });
+ }, 100);
+ });
+}
diff --git a/benchmark/http/simple.js b/benchmark/http/simple.js
new file mode 100644
index 0000000000..04a2a2911f
--- /dev/null
+++ b/benchmark/http/simple.js
@@ -0,0 +1,24 @@
+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, 500]
+});
+
+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', 5000, '-t', 8, '-c', conf.c];
+
+ bench.http(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+'/');
});
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)
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 <buf.length ; i += 1) {
- buf[i] = 33;
- }
- return buf;
-}
-
-function once(emitter, name, cb) {
- function incb() {
- cb.apply(undefined, arguments);
- emitter.removeListener(name, incb);
- }
- emitter.addListener(name, incb);
-}
-
-c = 0
-
-function writetest(size, bsize) {
- var s = fs.createWriteStream(path, {'flags': 'w', 'mode': 0644});
- var remaining = size;
- var buf = bufit(bsize);
-
- function dowrite() {
- var rv = s.write(buf);
- remaining -= buf.length;
- if (remaining > 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();
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
index 75882c1ef8..6ea928464c 100644
--- a/benchmark/function_call/binding.cc
+++ b/benchmark/misc/function_call/binding.cc
@@ -1,6 +1,5 @@
#include <v8.h>
#include <node.h>
-#include <time.h>
using namespace v8;
@@ -8,12 +7,12 @@ static int c = 0;
static Handle<Value> Hello(const Arguments& args) {
HandleScope scope;
- //time_t tv = time(NULL);
return scope.Close(Integer::New(c++));
}
extern "C" void init (Handle<Object> 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..fc8542a068
--- /dev/null
+++ b/benchmark/misc/function_call/index.js
@@ -0,0 +1,42 @@
+// 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');
+
+// 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;
+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);
+}
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/buffer-base64-encode.js b/benchmark/misc/next-tick-depth.js
index 7ead3a575d..b8ae27879e 100644
--- a/benchmark/buffer-base64-encode.js
+++ b/benchmark/misc/next-tick-depth.js
@@ -19,9 +19,22 @@
// 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, {
+ millions: [2]
+});
+
+process.maxTickDepth = Infinity;
+
+function main(conf) {
+ var n = +conf.millions * 1e6;
+
+ bench.start();
+ process.nextTick(onNextTick);
+ function onNextTick() {
+ if (--n)
+ process.nextTick(onNextTick);
+ else
+ bench.end(+conf.millions);
+ }
+}
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/misc/startup.js b/benchmark/misc/startup.js
new file mode 100644
index 0000000000..aa5b4420f6
--- /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]
+});
+
+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/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/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/url.js b/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));
}
diff --git a/benchmark/v8_bench.js b/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');
diff --git a/benchmark/net-pipe.js b/benchmark/net-pipe.js
deleted file mode 100644
index e981cc3b4e..0000000000
--- a/benchmark/net-pipe.js
+++ /dev/null
@@ -1,114 +0,0 @@
-// test the speed of .pipe() with sockets
-
-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')
- 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() {};
-
-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);
- 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);
- if (!res)
- dest.once('drain', this.flow);
- else
- process.nextTick(this.flow);
-};
-
-
-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);
- });
-});
-
diff --git a/benchmark/net/net-c2s.js b/benchmark/net/net-c2s.js
new file mode 100644
index 0000000000..49de7c77c9
--- /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: [5],
+});
+
+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
new file mode 100644
index 0000000000..2a5ed8e483
--- /dev/null
+++ b/benchmark/net/net-pipe.js
@@ -0,0 +1,115 @@
+// 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: [5],
+});
+
+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(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..79e22494a7
--- /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: [5]
+});
+
+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..e5b3662a8b
--- /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: [5]
+});
+
+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..4e53c1f0ca
--- /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: [5]
+});
+
+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..93a917e06f
--- /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: [5]
+});
+
+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);
+ };
+}
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);
-}
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);
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);
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();
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();
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";
-}
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');
-});
-
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);
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-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..88118afbc0
--- /dev/null
+++ b/benchmark/tls/throughput.js
@@ -0,0 +1,74 @@
+var common = require('../common.js');
+var bench = common.createBenchmark(main, {
+ dur: [5],
+ 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();
+ }
+}
+
diff --git a/benchmark/tls/tls-connect.js b/benchmark/tls/tls-connect.js
new file mode 100644
index 0000000000..0da448ee85
--- /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: [5]
+});
+
+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));
+}
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 <antirez at gmail dot com>
+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 <antirez at gmail dot 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <poll.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#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 <antirez at gmail dot 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.
+ */
+
+#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 <antirez at gmail dot 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 <sys/epoll.h>
+
+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 <assert.h>
+#include <errno.h>
+#include <port.h>
+#include <poll.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+
+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 <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+
+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 <antirez at gmail dot 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 <string.h>
+
+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 <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+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 <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#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*<any CHAR except CTLs or separators>
+ * 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 <sys/types.h>
+#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 <stdint.h>
+#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 <inttypes.h>
+#include <stdlib.h>
+#include <math.h>
+
+#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 <stdint.h>
+#include <inttypes.h>
+
+#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 <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <inttypes.h>
+
+#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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <math.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#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 <options> <url> \n"
+ " Options: \n"
+ " -c, --connections <n> Connections to keep open \n"
+ " -r, --requests <n> Total requests to make \n"
+ " -t, --threads <n> Number of threads to use \n"
+ " \n"
+ " -H, --header <h> 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 <pthread.h>
+#include <inttypes.h>
+#include <sys/types.h>
+
+#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 <antirez at gmail dot 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+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/<pid>/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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <mach/task.h>
+#include <mach/mach_init.h>
+
+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 <antirez at gmail dot 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.
+ */
+
+#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 <google/tcmalloc.h>
+#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 <jemalloc/jemalloc.h>
+#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 <malloc/malloc.h>
+#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 */