mirror of
https://github.com/nodejs/node-v0.x-archive.git
synced 2026-04-28 03:01:10 -04:00
Merge branch 'benchmark-refactor-2'
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -55,3 +55,5 @@ deps/openssl/openssl.xml
|
||||
# build/release artifacts
|
||||
/*.tar.gz
|
||||
/SHASUMS.txt*
|
||||
|
||||
/tools/wrk/wrk
|
||||
|
||||
43
Makefile
43
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
|
||||
|
||||
@@ -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;
|
||||
20
benchmark/arrays/var-int.js
Normal file
20
benchmark/arrays/var-int.js
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
20
benchmark/arrays/zero-float.js
Normal file
20
benchmark/arrays/zero-float.js
Normal file
@@ -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);
|
||||
}
|
||||
20
benchmark/arrays/zero-int.js
Normal file
20
benchmark/arrays/zero-int.js
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
SlowBuffer = require('buffer').SlowBuffer;
|
||||
|
||||
for (var i = 0; i < 1e6; i++) {
|
||||
b = new SlowBuffer(10);
|
||||
b[1] = 2
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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 main(conf) {
|
||||
var N = 64 * 1024 * 1024;
|
||||
var b = Buffer(N);
|
||||
var s = '';
|
||||
for (var i = 0; i < 256; ++i) s += String.fromCharCode(i);
|
||||
|
||||
bench.start();
|
||||
for (var i = 0; i < N; i += 256) b.write(s, i, 256, 'ascii');
|
||||
for (var i = 0; i < 32; ++i) b.toString('base64');
|
||||
bench.end(64);
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
var duration = (new Date()).getTime() - start,
|
||||
ticksPerSec = count / duration * 1000;
|
||||
console.log("nextTick callbacks per second: " + Math.round(ticksPerSec));
|
||||
}
|
||||
|
||||
start = (new Date()).getTime();
|
||||
process.nextTick(onNextTick);
|
||||
19
benchmark/buffers/buffer-creation.js
Normal file
19
benchmark/buffers/buffer-creation.js
Normal file
@@ -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);
|
||||
}
|
||||
28
benchmark/buffers/buffer-read.js
Normal file
28
benchmark/buffers/buffer-read.js
Normal file
@@ -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);
|
||||
}
|
||||
63
benchmark/buffers/buffer-write.js
Normal file
63
benchmark/buffers/buffer-write.js
Normal file
@@ -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);
|
||||
}
|
||||
57
benchmark/buffers/dataview-set.js
Normal file
57
benchmark/buffers/dataview-set.js
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
196
benchmark/common.js
Normal file
196
benchmark/common.js
Normal file
@@ -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(' ');
|
||||
}
|
||||
138
benchmark/compare.js
Normal file
138
benchmark/compare.js
Normal file
@@ -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);
|
||||
}
|
||||
103
benchmark/crypto/cipher-stream.js
Normal file
103
benchmark/crypto/cipher-stream.js
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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++
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
FastBuffer = require('./fast_buffer2').FastBuffer;
|
||||
for (var i = 0; i < 1e6; i++) {
|
||||
b = new FastBuffer(10);
|
||||
b[1] = 2;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
for (var i = 0; i < 1e6; i++) {
|
||||
b = new Buffer(10);
|
||||
b[1] = 2;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
87
benchmark/fs/read-stream-throughput.js
Normal file
87
benchmark/fs/read-stream-throughput.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
48
benchmark/fs/readfile.js
Normal file
48
benchmark/fs/readfile.js
Normal file
@@ -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();
|
||||
}
|
||||
78
benchmark/fs/write-stream-throughput.js
Normal file
78
benchmark/fs/write-stream-throughput.js
Normal file
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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'
|
||||
38
benchmark/http/cluster.js
Normal file
38
benchmark/http/cluster.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
24
benchmark/http/simple.js
Normal file
24
benchmark/http/simple.js
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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+'/');
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
109
benchmark/io.js
109
benchmark/io.js
@@ -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();
|
||||
1
benchmark/misc/function_call/.gitignore
vendored
Normal file
1
benchmark/misc/function_call/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build/
|
||||
2
benchmark/misc/function_call/Makefile
Normal file
2
benchmark/misc/function_call/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
binding:
|
||||
node-gyp rebuild --nodedir=../../..
|
||||
@@ -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);
|
||||
8
benchmark/misc/function_call/binding.gyp
Normal file
8
benchmark/misc/function_call/binding.gyp
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': [ 'binding.cc' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
42
benchmark/misc/function_call/index.js
Normal file
42
benchmark/misc/function_call/index.js
Normal file
@@ -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);
|
||||
}
|
||||
21
benchmark/misc/next-tick-breadth.js
Normal file
21
benchmark/misc/next-tick-breadth.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
25
benchmark/misc/spawn-echo.js
Normal file
25
benchmark/misc/spawn-echo.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
40
benchmark/misc/startup.js
Normal file
40
benchmark/misc/startup.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
16
benchmark/misc/string-creation.js
Normal file
16
benchmark/misc/string-creation.js
Normal file
@@ -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);
|
||||
}
|
||||
40
benchmark/misc/timers.js
Normal file
40
benchmark/misc/timers.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
112
benchmark/net/net-c2s.js
Normal file
112
benchmark/net/net-c2s.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
115
benchmark/net/net-pipe.js
Normal file
115
benchmark/net/net-pipe.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
112
benchmark/net/net-s2c.js
Normal file
112
benchmark/net/net-s2c.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
136
benchmark/net/tcp-raw-c2s.js
Normal file
136
benchmark/net/tcp-raw-c2s.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
149
benchmark/net/tcp-raw-pipe.js
Normal file
149
benchmark/net/tcp-raw-pipe.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
136
benchmark/net/tcp-raw-s2c.js
Normal file
136
benchmark/net/tcp-raw-s2c.js
Normal file
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
|
||||
for (var i = 0; i < 9e7; i++) {
|
||||
s = '01234567890';
|
||||
s[1] = "a";
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
function next (i) {
|
||||
if (i <= 0) return;
|
||||
setTimeout(function () { next(i-1); }, 1);
|
||||
}
|
||||
next(700);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
74
benchmark/tls/throughput.js
Normal file
74
benchmark/tls/throughput.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
63
benchmark/tls/tls-connect.js
Normal file
63
benchmark/tls/tls-connect.js
Normal file
@@ -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));
|
||||
}
|
||||
2
tools/wrk/.gitignore
vendored
Normal file
2
tools/wrk/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
obj/*
|
||||
/wrk
|
||||
177
tools/wrk/LICENSE
Normal file
177
tools/wrk/LICENSE
Normal file
@@ -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
|
||||
35
tools/wrk/Makefile
Normal file
35
tools/wrk/Makefile
Normal file
@@ -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
|
||||
115
tools/wrk/NOTICE
Normal file
115
tools/wrk/NOTICE
Normal file
@@ -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.
|
||||
37
tools/wrk/README
Normal file
37
tools/wrk/README
Normal file
@@ -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.
|
||||
435
tools/wrk/src/ae.c
Normal file
435
tools/wrk/src/ae.c
Normal file
@@ -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;
|
||||
}
|
||||
118
tools/wrk/src/ae.h
Normal file
118
tools/wrk/src/ae.h
Normal file
@@ -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
|
||||
130
tools/wrk/src/ae_epoll.c
Normal file
130
tools/wrk/src/ae_epoll.c
Normal file
@@ -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";
|
||||
}
|
||||
315
tools/wrk/src/ae_evport.c
Normal file
315
tools/wrk/src/ae_evport.c
Normal file
@@ -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";
|
||||
}
|
||||
132
tools/wrk/src/ae_kqueue.c
Normal file
132
tools/wrk/src/ae_kqueue.c
Normal file
@@ -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";
|
||||
}
|
||||
99
tools/wrk/src/ae_select.c
Normal file
99
tools/wrk/src/ae_select.c
Normal file
@@ -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";
|
||||
}
|
||||
27
tools/wrk/src/aprintf.c
Normal file
27
tools/wrk/src/aprintf.c
Normal file
@@ -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;
|
||||
}
|
||||
6
tools/wrk/src/aprintf.h
Normal file
6
tools/wrk/src/aprintf.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef APRINTF_H
|
||||
#define APRINTF_H
|
||||
|
||||
char *aprintf(char **, const char *, ...);
|
||||
|
||||
#endif /* APRINTF_H */
|
||||
13
tools/wrk/src/config.h
Normal file
13
tools/wrk/src/config.h
Normal file
@@ -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 */
|
||||
2058
tools/wrk/src/http_parser.c
Normal file
2058
tools/wrk/src/http_parser.c
Normal file
File diff suppressed because it is too large
Load Diff
317
tools/wrk/src/http_parser.h
Normal file
317
tools/wrk/src/http_parser.h
Normal file
@@ -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
|
||||
73
tools/wrk/src/stats.c
Normal file
73
tools/wrk/src/stats.c
Normal file
@@ -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;
|
||||
}
|
||||
21
tools/wrk/src/stats.h
Normal file
21
tools/wrk/src/stats.h
Normal file
@@ -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 */
|
||||
|
||||
129
tools/wrk/src/tinymt64.c
Normal file
129
tools/wrk/src/tinymt64.c
Normal file
@@ -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);
|
||||
}
|
||||
210
tools/wrk/src/tinymt64.h
Normal file
210
tools/wrk/src/tinymt64.h
Normal file
@@ -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
|
||||
96
tools/wrk/src/units.c
Normal file
96
tools/wrk/src/units.c
Normal file
@@ -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);
|
||||
}
|
||||
10
tools/wrk/src/units.h
Normal file
10
tools/wrk/src/units.h
Normal file
@@ -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 */
|
||||
482
tools/wrk/src/wrk.c
Normal file
482
tools/wrk/src/wrk.c
Normal file
@@ -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));
|
||||
}
|
||||
75
tools/wrk/src/wrk.h
Normal file
75
tools/wrk/src/wrk.h
Normal file
@@ -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 */
|
||||
287
tools/wrk/src/zmalloc.c
Normal file
287
tools/wrk/src/zmalloc.c
Normal file
@@ -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();
|
||||
}
|
||||
83
tools/wrk/src/zmalloc.h
Normal file
83
tools/wrk/src/zmalloc.h
Normal file
@@ -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 */
|
||||
Reference in New Issue
Block a user