From 744e1d9bb8fdc5e552ca93bf97afc4fafc15fe88 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 5 Nov 2015 11:26:33 +0800 Subject: [PATCH 01/11] add soket.io support --- CHANGELOG.md | 4 + README.md | 2 +- bin/socketio | 232 +++++++++++++++++++++++++++++++++++++++++++++ bin/thor | 142 ++++++++++++++------------- metrics.js | 4 +- mjolnir.js | 103 ++++++++++++++++---- package.json | 6 +- worker-socketio.js | 172 +++++++++++++++++++++++++++++++++ 8 files changed, 574 insertions(+), 91 deletions(-) create mode 100644 bin/socketio create mode 100644 worker-socketio.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 145fab5..abcc934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.0.1 + +Add socket.io support + ### 1.0.0 Initial release of `thor`. diff --git a/README.md b/README.md index 6b97fe5..f68f3bd 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Once you have Node.js installed, you can use the bundled package manager `npm` t install this module: ``` -npm install -g thor +npm install -g https://github.com/iorichina/thor.git ``` The `-g` command flag tells `npm` to install the module globally on your system. diff --git a/bin/socketio b/bin/socketio new file mode 100644 index 0000000..a0feda7 --- /dev/null +++ b/bin/socketio @@ -0,0 +1,232 @@ +#!/usr/bin/env node +'use strict'; + +var Metrics = require('../metrics') + , async = require('async') + , path = require('path') + , os = require('os'); + +// +// Setup the Command-Line Interface. +// +var cli = require('commander'); + +cli.usage('[options] ws://localhost[@@vip]') + .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) + .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) + .option('-M, --messages ', 'messages to be send per connection', parseInt, 0) + .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) + .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) + .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) + .option('-G, --generator ', 'custom message generators') + .option('-M, --masked', 'send the messaged with a mask') + .option('-b, --binary', 'send binary messages instead of utf-8') + .option('-T, --runtime ', 'timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c', parseInt, -1) + .version(require('../package.json').version) + .parse(process.argv); + +// +// Check if all required arguments are supplied, if we don't have a valid url we +// should bail out +// +if (!cli.args.length) return [ + 'Thor:' + , 'Odin is disapointed you... pithy human! You forgot to supply the urls.' +].forEach(function stderr(line) { + console.error(line); +}); + +// +// By Odin's beard, unleash thunder! +// +var cluster = require('cluster') + , worker_num = cli.workers || 1 + , worker_num = worker_num > cli.amount * cli.args.length ? cli.amount * cli.args.length : worker_num + , forked_worker_num = worker_num + , connect_ids = Object.create(null) + , connections = 0 + , connecteds = {} + , received = 0 + , robin = []; + +cluster.setupMaster({ + exec: path.resolve(__dirname, '../worker-socketio.js') + , silent: false + , args: [ + cli.generator + ? path.resolve(process.cwd(), cli.generator) + : path.resolve(__dirname, '../generator.js'), + cli.protocol, + !!cli.masked, + !!cli.binary + ] +}); + +while (worker_num--) cluster.fork(); + +Object.keys(cluster.workers).forEach(function each(id) { + var worker = cluster.workers[id]; + + worker.on('message', function message(data) { + + var datas = []; + if (data.collection) { + datas = data.datas; + }else{ + datas = [data]; + } + for (var i = 0; i < datas.length; i++) { + var data = datas[i]; + + switch (data.type) { + case 'connected': + connecteds[data.worker_id] = data.connected; + var all_connected = 0, + all_workers = 0; + Object.keys(connecteds).forEach(function each(id) { + all_workers++; + all_connected += connecteds[id]; + console.log('[Master] %s connected, msg received %s in workers %s', connecteds[id], data.received_msg, id); + }); + // Output the connection progress + console.log('[Master] %s connected, in %s workers' + "\n", all_connected, all_workers); + break; + + case 'opened': + worker.emit('opened::'+ data.id); + + // Output the connection progress + if (++connections % 100 === 0) { + console.log(' Opened %s connections', connections); + } + + break; + + case 'open': + metrics.handshaken(data); + // worker.emit('open::'+ data.id); + + // Output the connection progress + /*if (++connections % 1000 === 0) { + console.log(' Opened %s connections', connections); + }*/ + + break; + + case 'close': + delete connect_ids[data.id]; + + metrics.close(data); + // worker.emit('close::'+ data.id); + break; + + case 'error': + delete connect_ids[data.id]; + + metrics.error(data); + // worker.emit('error::'+ data.id); + break; + + case 'message': + received++; + metrics.message(data); + // worker.emit('message::'+ data.id); + + } + + } + + // + // Check if we have processed all connections so we can quit cleanly. + // + if (!Object.keys(connect_ids).length) process.exit(); + }); + + // Add our worker to our round robin queue so we can balance all our requests + // across the different workers that we spawned. + robin.push(worker); +}); + +// +// Up our WebSocket socket connections. +// +[ + '' + , 'Thor: version: '+ cli._version + , '' + , 'God of Thunder, son of Odin and smasher of WebSockets!' + , '' + , 'Thou shall:' + , '- Spawn '+ forked_worker_num +' workers.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' + , '' + , 'The answers you seek shall be yours, once I claim what is mine.' + , '' +].forEach(function stdout(line) { + console.log(line); +}); + +// +// Metrics collection. +// +var metrics = new Metrics(cli.amount * cli.args.length); + +// Iterate over all the urls so we can target multiple locations at once, which +// is helpfull if you are testing multiple loadbalancer endpoints for example. +async.forEach(cli.args, function forEach(url, done) { + var i = cli.amount + , completed = 0; + + console.log('Connecting to %s', url); + + url = url.split('@@'); + var localaddr = url.length > 1 ? url[1] : null; + url = url[0]; + // + // Create a simple WebSocket connection generator. + // + var queue = async.queue(function working(connect_id, fn) { + var worker = robin.shift(); + + // Register the id, so we can keep track of the connections that we still + // need to process. + connect_ids[connect_id] = 1; + + // Process the connections + worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: connect_id, runtime: cli.runtime, localaddr: localaddr, send_opened: (cli.concurrent && cli.concurrent < cli.amount) }); + + // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections + // we have to set this event to roll process to next connect + if (cli.concurrent && cli.concurrent < cli.amount) { + worker.once('opened::'+ connect_id, fn); + }; + + // Add the worker back at the end of the round robin queue. + robin.push(worker); + }, cli.concurrent || Infinity); + + // When all the events are processed successfully we should call.. back ;P + queue.drain = done; + + // Add all connections to the processing queue; //.push(connect_id) + while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); +}, function established(err) { + metrics.established(); +}); + +console.log(''); + +// ctrl + c +process.on('SIGINT', function end() { + robin.forEach(function nuke(worker) { + try { worker.send({ shutdown: true }); } + catch (e) {} + }) +}); + +process.on('exit', function summary() { + console.log('-----------------'); + + metrics.established().stop().summary(); +}); diff --git a/bin/thor b/bin/thor index 9db08f2..ebe1e6a 100755 --- a/bin/thor +++ b/bin/thor @@ -2,7 +2,6 @@ 'use strict'; var Metrics = require('../metrics') - , colors = require('colors') , async = require('async') , path = require('path') , os = require('os'); @@ -14,14 +13,15 @@ var cli = require('commander'); cli.usage('[options] ws://localhost') .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) - .option('-C, --concurrent ', 'how many concurrent-connections per second', parseInt, 0) - .option('-M, --messages ', 'messages to be send per connection', parseInt, 1) + .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) + .option('-M, --messages ', 'messages to be send per connection', parseInt, 0) .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) .option('-G, --generator ', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') + .option('-T, --runtime ', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) .version(require('../package.json').version) .parse(process.argv); @@ -31,7 +31,7 @@ cli.usage('[options] ws://localhost') // if (!cli.args.length) return [ 'Thor:' - , 'Odin is disappointed in you... pity human! You forgot to supply the urls.' + , 'Odin is disapointed you... pithy human! You forgot to supply the urls.' ].forEach(function stderr(line) { console.error(line); }); @@ -42,7 +42,6 @@ if (!cli.args.length) return [ var cluster = require('cluster') , workers = cli.workers || 1 , ids = Object.create(null) - , concurrents = Object.create(null) , connections = 0 , received = 0 , robin = []; @@ -66,34 +65,62 @@ Object.keys(cluster.workers).forEach(function each(id) { var worker = cluster.workers[id]; worker.on('message', function message(data) { - if ('concurrent' in data) concurrents[data.id] = data.concurrent; + + // console.info('catch worker message ' + (data.datas.length)); + var datas = []; + if (data.collection) { + datas = data.datas; + }else{ + datas = [data]; + } + for (var i = 0; i < datas.length; i++) { + var data = datas[i]; switch (data.type) { + case 'opened': + worker.emit('opened::'+ data.id); + + // Output the connection progress + if (++connections % 100 === 0) { + console.log(' Opened %s connections', connections); + } + + break; + case 'open': metrics.handshaken(data); - worker.emit('open::'+ data.id); + // worker.emit('open::'+ data.id); // Output the connection progress - ++connections; + /*if (++connections % 1000 === 0) { + console.log(' Opened %s connections', connections); + }*/ + break; case 'close': delete ids[data.id]; metrics.close(data); + // worker.emit('close::'+ data.id); break; case 'error': delete ids[data.id]; metrics.error(data); + // worker.emit('error::'+ data.id); break; case 'message': received++; metrics.message(data); + // worker.emit('message::'+ data.id); + } + } + // // Check if we have processed all connections so we can quit cleanly. // @@ -105,52 +132,6 @@ Object.keys(cluster.workers).forEach(function each(id) { robin.push(worker); }); -// -// Output live, real-time stats. -// -function live() { - var frames = live.frames - , len = frames.length - , interval = 100 - , i = 0; - - live.interval = setInterval(function tick() { - var active = Object.keys(concurrents).reduce(function (count, id) { - return count + (concurrents[id] || 0); - }, 0); - - process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ - 'Created '.white + connections.toString().green, - 'Active '.white + active.toString().green - ].join(', ')); - }, interval); -} - -/** - * Live frames. - * - * @type {Array} - * @api private - */ -live.frames = [ - ' \u001b[96m◜ \u001b[90m' - , ' \u001b[96m◠ \u001b[90m' - , ' \u001b[96m◝ \u001b[90m' - , ' \u001b[96m◞ \u001b[90m' - , ' \u001b[96m◡ \u001b[90m' - , ' \u001b[96m◟ \u001b[90m' -]; - -/** - * Stop the live stats from running. - * - * @api private - */ -live.stop = function stop() { - process.stdout.write('\u001b[2K'); - clearInterval(live.interval); -}; - // // Up our WebSocket socket connections. // @@ -184,6 +165,9 @@ async.forEach(cli.args, function forEach(url, done) { console.log('Connecting to %s', url); + url = url.split('@@'); + var localaddr = url.length > 1 ? url[1] : null; + url = url[0]; // // Create a simple WebSocket connection generator. // @@ -195,8 +179,13 @@ async.forEach(cli.args, function forEach(url, done) { ids[id] = 1; // Process the connections - worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id }); - worker.once('open::'+ id, fn); + worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id, runtime: cli.runtime, localaddr: localaddr, send_opened: (cli.concurrent && cli.concurrent < cli.amount) }); + + // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections + if (cli.concurrent && cli.concurrent < cli.amount) { + worker.once('opened::'+ id, fn); + }; + // worker.once('open::'+ id, fn); // Add the worker back at the end of the round robin queue. robin.push(worker); @@ -206,25 +195,48 @@ async.forEach(cli.args, function forEach(url, done) { queue.drain = done; // Add all connections to the processing queue; - while (i--) queue.push(url +'::'+ i); + while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); }, function established(err) { metrics.established(); }); -// -// We are setup, everything is running -// console.log(''); -live(); +// console.log("worker num:"+robin.length); -process.once('SIGINT', function end() { +process.on('SIGINT', function end() { robin.forEach(function nuke(worker) { try { worker.send({ shutdown: true }); } catch (e) {} - }); + }) }); -process.once('exit', function summary() { - live.stop(); +process.on('exit', function summary() { + console.log('-----------------'); +/* +// +// Up our WebSocket socket connections. +// +[ + '' + , 'Thor: version: '+ cli._version + , '' + , 'God of Thunder, son of Odin and smasher of WebSockets!' + , '' + , 'Thou shall:' + , '- Spawn '+ cli.workers +' workers.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' + , '' + , 'The answers you seek shall be yours, once I claim what is mine.' + , '' +].forEach(function stdout(line) { + console.log(line); +}); +*/ + // console.log('---------process exit in 2 seconds--------'); + /*setTimeout(function () { + console.log(''); + metrics.established().stop().summary(); + }, 3000);*/ metrics.established().stop().summary(); }); diff --git a/metrics.js b/metrics.js index f0d5c0d..6dfb849 100644 --- a/metrics.js +++ b/metrics.js @@ -15,7 +15,7 @@ function Metrics(requests) { this.requests = requests; // The total amount of requests send this.connections = 0; // Connections established - this.disconnects = 0; // Closed connections + this.disconnections = 0; // Closed connections this.failures = 0; // Connections that received an error this.errors = Object.create(null); // Collection of different errors @@ -138,7 +138,7 @@ Metrics.prototype.summary = function summary() { results.writeRow(['Online', this.timing.established + ' milliseconds']); results.writeRow(['Time taken', this.timing.duration + ' milliseconds']); results.writeRow(['Connected', this.connections]); - results.writeRow(['Disconnected', this.disconnects]); + results.writeRow(['Disconnected', this.disconnections]); results.writeRow(['Failed', this.failures]); results.writeRow(['Total transferred', this.send.bytes(2)]); diff --git a/mjolnir.js b/mjolnir.js index c22f517..f24d1cc 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -1,8 +1,7 @@ 'use strict'; var Socket = require('ws') - , connections = {} - , concurrent = 0; + , connections = {}; // // Get the session document that is used to generate the data. @@ -16,6 +15,9 @@ var masked = process.argv[4] === 'true' , binary = process.argv[5] === 'true' , protocol = +process.argv[3] || 13; +// 收集后一次性send给master +var metrics_datas = {collection:true, datas:[]}; + process.on('message', function message(task) { var now = Date.now(); @@ -40,54 +42,110 @@ process.on('message', function message(task) { // End of the line, we are gonna start generating new connections. if (!task.url) return; - var socket = new Socket(task.url, { + var sock_opts = { protocolVersion: protocol - }); + }; - socket.on('open', function open() { - process.send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }); - write(socket, task, task.id); + if (task.localaddr) { + sock_opts.localAddress = task.localaddr; + }; + var socket = new Socket(task.url, sock_opts); + socket.last = Date.now(); + var inteval = null; + socket.on('open', function open() { + var send_data = { type: 'open', duration: Date.now() - now, id: task.id }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + // write(socket, task, task.id); + // + if (task.send_opened) { + process.send({ type: 'opened', duration: Date.now() - now, id: task.id }); + }; + + inteval = setInterval(function ping(id, socket) { + if(socket && (typeof socket.ping == 'function')) { + socket.ping(); + }else{ + clearInterval(inteval); + } + }, 25000, task.id, socket); // As the `close` event is fired after the internal `_socket` is cleaned up // we need to do some hacky shit in order to tack the bytes send. + // + // process.send({ type: 'showopened', opened: Object.keys(connections).length }); }); socket.on('message', function message(data) { - process.send({ - type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, + var send_data = { + type: 'message', latency: Date.now() - socket.last, id: task.id - }); + }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + console.log('['+task.id.substr(task.id.indexOf('::'))+']socket on message@'+socket.last, "\n", data, "\n"); // Only write as long as we are allowed to send messages - if (--task.messages) { + if (--task.messages && task.messages > 0) { write(socket, task, task.id); } else { - socket.close(); + // socket.close(); } }); - socket.on('close', function close() { + socket.on('close', function close(log) { var internal = socket._socket || {}; + // console.info('['+task.id+']socket on close'); + // console.log(socket); - process.send({ - type: 'close', id: task.id, concurrent: --concurrent, + var send_data = { + type: 'close', id: task.id, read: internal.bytesRead || 0, send: internal.bytesWritten || 0 - }); + }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + if (inteval) { + clearInterval(inteval); + }; delete connections[task.id]; + // console.log('close ', Object.keys(connections).length); + if (Object.keys(connections) <= 0) { + // 一次性发送 + process.send(metrics_datas); + }; }); socket.on('error', function error(err) { - process.send({ type: 'error', message: err.message, id: task.id, concurrent: --concurrent }); + console.error('['+task.id+']socket on error-------', "\n", err, "\n", '-------error'); + var send_data = { type: 'error', message: err.message, id: task.id }; + // process.send(send_data); + metrics_datas.datas.push(send_data); socket.close(); + socket.emit('close'); delete connections[task.id]; }); // Adding a new socket to our socket collection. - ++concurrent; connections[task.id] = socket; + + // timeout to close socket + if (task.runtime && task.runtime > 0) { + setTimeout(function timeoutToCloseSocket(id, socket) { + // console.log('timeout to close socket:'+id); + socket.close(); + }, task.runtime * 1000, task.id, socket); + } +}); + +process.on('SIGINT', function () { + // console.log('process.SIGINT') +}); +process.on('exit', function () { + // console.log('process.exit') + // process.send(metrics_datas); }); /** @@ -100,15 +158,18 @@ process.on('message', function message(task) { * @api private */ function write(socket, task, id, fn) { - session[binary ? 'binary' : 'utf8'](task.size, function message(err, data) { - var start = socket.last = Date.now(); + var start = socket.last = Date.now(); + console.info("\n" + 'no no no! no write please~! ' + "\n" + 'Do that if and only if u know the server can parse ur msg, or server will cut ur connection.' + "\n"); + session[binary ? 'binary' : 'utf8'](task.size, function message(err, data) { socket.send(data, { binary: binary, mask: masked }, function sending(err) { if (err) { - process.send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }); + var send_data = { type: 'error', message: err.message }; + // process.send(send_data); + metrics_datas.datas.push(send_data); socket.close(); delete connections[id]; diff --git a/package.json b/package.json index bdfe97b..b7f3657 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/observing/thor.git" + "url": "https://github.com/iorichina/thor.git" }, "keywords": [ "WebSocket", @@ -18,6 +18,7 @@ "author": "Arnout Kazemier ", "license": "MIT", "dependencies": { + "engine.io-client": "git://github.com/iorichina/engine.io-client.git#1.5.4", "commander": "1.1.x", "async": "0.2.x", "tab": "0.1.x", @@ -27,6 +28,7 @@ "sugar": "1.3.8" }, "bin": { - "thor": "./bin/thor" + "thor": "./bin/thor", + "thor-socketio": "./bin/socketio" } } diff --git a/worker-socketio.js b/worker-socketio.js new file mode 100644 index 0000000..4b590b1 --- /dev/null +++ b/worker-socketio.js @@ -0,0 +1,172 @@ +'use strict'; + +var Socket = require('socket.io-client') + , connections = {} + , connected = 0 + , received_msg = 0; + +// +// Get the session document that is used to generate the data. +// +var session = require(process.argv[2]); + +// 收集后一次性send给master +var metrics_datas = {collection:true, datas:[]}; + +var sendToMaster = setInterval(function () { + process.send({ type: 'connected', connected: connected, received_msg: received_msg, worker_id: process.pid }); +}, 60000); + +process.on('message', function message(task) { + var now = Date.now(); + + // + // Shut down every single socket. + // + if (task.shutdown) { + console.log('shutdown', process.pid); + Object.keys(connections).forEach(function shutdown(id) { + connections[id] && connections[id].disconnect(); + }); + + setInterval(function(){ + if (connected <= 0) { + clearInterval(sendToMaster); + // 一次性发送 + process.send(metrics_datas, null, function(err){ + metrics_datas.datas = []; + process.exit(); + }); + }; + }, 30000); + } + + // End of the line, we are gonna start generating new connections. + if (!task.url) return; + + var sock_opts = { + forceNew:true, + transports:['websocket'] + }; + + if (task.localaddr) { + sock_opts.localAddress = task.localaddr; + }; + var socket = new Socket(task.url, sock_opts); + socket.last = Date.now(); + + socket.on('connect', function open() { + connected ++; + // console.info(task.id + " opened", connections); + var send_data = { type: 'open', duration: Date.now() - now, id: task.id }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + // write(socket, task, task.id); + // + if (task.send_opened) { + process.send({ type: 'opened', duration: Date.now() - now, id: task.id }); + }; + /*if (connected % 1000 === 0) { + console.info('worker ', process.pid, ' has connection: ', connected); + };*/ + + // As the `close` event is fired after the internal `_socket` is cleaned up + // we need to do some hacky shit in order to tack the bytes send. + }); + + function message(data) { + var send_data = { + type: 'message', latency: Date.now() - socket.last, + id: task.id + }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + received_msg++; + // console.log('['+task.id.substr(task.id.indexOf('::'))+']socket on message@'+socket.last, "\n", data, "\n"); + // Only write as long as we are allowed to send messages + if (--task.messages && task.messages > 0) { + write(socket, task, task.id); + } else { + // socket.disconnect(); + } + }; + socket.on('message', message); + socket.on('onMessage', message); + + socket.on('disconnect', function close() { + connected--; + var internal = {}; + // console.log(socket); + // var internal = socket.io.engine._socket || {}; + // console.info('['+task.id+']socket on close'); + + var send_data = { + type: 'close', id: task.id, + read: internal.bytesRead || 0, + send: internal.bytesWritten || 0 + }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + delete connections[task.id]; + // console.log('close ', Object.keys(connections).length); + if (Object.keys(connections) <= 0) { + clearInterval(sendToMaster); + // 一次性发送 + process.send(metrics_datas); + metrics_datas.datas = []; + }; + }); + + socket.on('error', function error(err) { + console.error('['+task.id+']socket on error-------', "\n", err, "\n", '-------error'); + var send_data = { type: 'error', message: err.message, id: task.id }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + socket.disconnect(); + socket.emit('disconnect'); + delete connections[task.id]; + }); + + // Adding a new socket to our socket collection. + connections[task.id] = socket; + + // timeout to close socket + if (task.runtime && task.runtime > 0) { + setTimeout(function timeoutToCloseSocket(id, socket) { + // console.log('timeout to close socket:'+id); + socket.disconnect(); + }, task.runtime * 1000, task.id, socket); + } +}); + +process.on('SIGINT', function () { +}); + +/** + * Helper function from writing messages to the socket. + * + * @param {WebSocket} socket WebSocket connection we should write to + * @param {Object} task The given task + * @param {String} id + * @param {Function} fn The callback + * @api private + */ +function write(socket, task, id, fn) { + var start = socket.last = Date.now(); + socket.send(task.size, function (err) { + if (err) { + var send_data = { type: 'error', message: err.message }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + socket.disconnect(); + delete connections[id]; + } + + if (fn) fn(err); + }); + +} From ddc5415dee04cb08272489850a4f1687633b749d Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 6 Nov 2015 12:21:08 +0800 Subject: [PATCH 02/11] thor:add ping(socket.io supported);socketio:optimize --- README.md | 332 ++++++++++++++++------------- bin/socketio | 68 +++--- bin/thor | 501 ++++++++++++++++++++++--------------------- generator.js | 92 ++++---- metrics.js | 520 ++++++++++++++++++++++----------------------- mjolnir.js | 355 +++++++++++++++---------------- package.json | 69 +++--- worker-socketio.js | 349 +++++++++++++++--------------- 8 files changed, 1164 insertions(+), 1122 deletions(-) diff --git a/README.md b/README.md index f68f3bd..5032d8e 100644 --- a/README.md +++ b/README.md @@ -1,151 +1,181 @@ -# Thor - -Thor is WebSocket benchmarking/load generator. There are a lot of benchmarking -tools for HTTP servers. You've got ab, siege, wrk and more. But all these tools -only work with plain ol HTTP and have no support for WebSockets - even if they did -they wouldn't be suitable, as they would be testing short running HTTP requests -instead of long running HTTP requests with a lot of messaging traffic. Thor -fixes all of this. - -### Dependencies - -Thor requires Node.js to be installed on your system. If you don't have Node.js -installed you can download it from http://nodejs.org or build it from the github -source repository: http://github.com/joyent/node. - -Once you have Node.js installed, you can use the bundled package manager `npm` to -install this module: - -``` -npm install -g https://github.com/iorichina/thor.git -``` - -The `-g` command flag tells `npm` to install the module globally on your system. - -### Usage - -``` -thor [options] -``` - -Thor can hit multiple URL's at once; this is useful if you are testing your -reverse proxies, load balancers or just simply multiple applications. The url -that you supply to `thor` should be written in a WebSocket compatible format -using the `ws` or `wss` protocols: - -``` -thor --amount 5000 ws://localhost:8080 wss://localhost:8081 -``` - -The snippet above will open up `5000` connections against the regular -`ws://localhost:8080` and also `5000` connections against the *secured* -`wss://localhost:8081` server, so a total of `10000` connections will be made. - -One thing to keep in mind is you probably need to bump the amount of file -descriptors on your local machine if you start testing WebSockets. Set the -`ulimit -n` on machine as high as possible. If you do not know how to do this, -Google it. - -#### Options - -``` - Usage: thor [options] ws://localhost - - Options: - - -h, --help output usage information - -A, --amount the amount of persistent connections to generate - -C, --concurrent how many concurrent-connections per second - -M, --messages messages to be send per connection - -P, --protocol WebSocket protocol version - -B, --buffer size of the messages that are send - -W, --workers workers to be spawned - -G, --generator custom message generators - -M, --masked send the messaged with a mask - -b, --binary send binary messages instead of utf-8 - -V, --version output the version number -``` - -Some small notes about the options: - -- `--protocol` is the protocol version number. If you want to use the *HyBi drafts - 07-12* use `8` as argument or if you want to use the *HyBi drafts 13-17* - drafts which are the default version use `13`. -- `--buffer` should be size of the message in bytes. -- `--workers` as Node.js is single threaded this sets the amount of sub - processes to handle all the heavy lifting. - -### Custom messages - -Some WebSocket servers have their own custom messaging protocol. In order to -work with those servers we introduced a concept called `generators` a generator -is a small JavaScript file that can output `utf8` and `binary` messages. It uses -a really simple generator by default. - -Checkout https://github.com/observing/thor/blob/master/generator.js for an -example of a generator. - -``` -thor --amount 1000 --generator ws://localhost:8080 -``` - -### Example - -``` -thor --amount 1000 --messages 100 ws://localhost:8080 -``` - -This will hit the WebSocket server that runs on localhost:8080 with 1000 -connections and sends 100 messages over each established connection. Once `thor` -is done with smashing your connections it will generate a detailed report: - -``` -Thor: version: 1.0.0 - -God of Thunder, son of Odin and smasher of WebSockets! - -Thou shall: -- Spawn 4 workers. -- Create all the concurrent/parallel connections. -- Smash 1000 connections with the mighty Mjölnir. - -The answers you seek shall be yours, once I claim what is mine. - -Connecting to ws://localhost:8080 - - Opened 100 connections - Opened 200 connections - Opened 300 connections - Opened 400 connections - Opened 500 connections - Opened 600 connections - Opened 700 connections - Opened 800 connections - Opened 900 connections - Opened 1000 connections - - -Online 15000 milliseconds -Time taken 31775 milliseconds -Connected 1000 -Disconnected 0 -Failed 0 -Total transferred 120.46MB -Total received 120.43MB - -Durations (ms): - - min mean stddev median max -Handshaking 217 5036 4094 3902 14451 -Latency 0 215 104 205 701 - -Percentile (ms): - - 50% 66% 75% 80% 90% 95% 98% 98% 100% -Handshaking 3902 6425 8273 9141 11409 12904 13382 13945 14451 -Latency 205 246 266 288 371 413 437 443 701 -``` - -### License - -MIT +# Thor + +Thor is WebSocket benchmarking/load generator. There are a lot of benchmarking +tools for HTTP servers. You've got ab, siege, wrk and more. But all these tools +only work with plain ol HTTP and have no support for WebSockets - even if they did +they wouldn't be suitable, as they would be testing short running HTTP requests +instead of long running HTTP requests with a lot of messaging traffic. Thor +fixes all of this. + +### Dependencies + +Thor requires Node.js to be installed on your system. If you don't have Node.js +installed you can download it from http://nodejs.org or build it from the github +source repository: http://github.com/joyent/node. + +Once you have Node.js installed, you can use the bundled package manager `npm` to +install this module: + +``` +npm install -g git://github.com/iorichina/thor.git +``` + +The `-g` command flag tells `npm` to install the module globally on your system. + +### Usage + +``` +thor [options] +socketio [options] [@@vip] [[@@vip]...] +``` + +Thor can hit multiple URL's at once; this is useful if you are testing your +reverse proxies, load balancers or just simply multiple applications. The url +that you supply to `thor` should be written in a WebSocket compatible format +using the `ws` or `wss` protocols: + +``` +thor --amount 5000 ws://localhost:8080 wss://localhost:8081 +``` +or use http/https if and only if ur using socket.io(nodejs/java/etc) in server +``` +socketio --amount 5000 http://localhost:8080 https://localhost:8081 +``` + +The snippet above will open up `5000` connections against the regular +`ws://localhost:8080` and also `5000` connections against the *secured* +`wss://localhost:8081` server, so a total of `10000` connections will be made. + +One thing to keep in mind is you probably need to bump the amount of file +descriptors on your local machine if you start testing WebSockets. Set the +`ulimit -n` on machine as high as possible. If you do not know how to do this, +Google it. + +And the other thing u have to know is that, one ip can only create max to 60 thousands connections, +which is limited by TCP/IP protocal. U can special the [@@one_of_the_machine_vip] after the url. + +#### thor Options + +``` + Usage: thor [options] ws://localhost + + Options: + + -h, --help output usage information + -A, --amount the amount of persistent connections to generate + -C, --concurrent how many concurrent-connections per second + -M, --messages number of messages to be send per connection + -P, --protocol WebSocket protocol version + -B, --buffer size of the messages that are send + -W, --workers workers to be spawned + -G, --generator custom message generators + -M, --masked send the messaged with a mask + -b, --binary send binary messages instead of utf-8 + -T, --runtime timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c + -V, --version output the version number +``` + +Some small notes about the options: + +- `--protocol` is the protocol version number. If you want to use the *HyBi drafts + 07-12* use `8` as argument or if you want to use the *HyBi drafts 13-17* + drafts which are the default version use `13`. +- `--buffer` should be size of the message in bytes. +- `--workers` as Node.js is single threaded this sets the amount of sub + processes to handle all the heavy lifting. + +#### socketio Options + +``` + Usage: socketio [options] http[s]://localhost[@@vip] + + Options: + + -h, --help output usage information + -A, --amount the amount of persistent connections to generate + -C, --concurrent [deprecated] how many concurrent-connections per second + -M, --messages number of messages to be send per connection + -P, --protocol WebSocket protocol version + -B, --buffer size of the messages that are send + -W, --workers workers to be spawned + -M, --masked send the messaged with a mask + -b, --binary send binary messages instead of utf-8 + -T, --runtime timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c + -V, --version output the version number +``` + + +### Custom messages + +Some WebSocket servers have their own custom messaging protocol. In order to +work with those servers we introduced a concept called `generators` a generator +is a small JavaScript file that can output `utf8` and `binary` messages. It uses +a really simple generator by default. + +Checkout https://github.com/observing/thor/blob/master/generator.js for an +example of a generator. + +``` +thor --amount 1000 --generator ws://localhost:8080 +``` + +### Example + +``` +thor --amount 1000 --messages 100 ws://localhost:8080 +``` + +This will hit the WebSocket server that runs on localhost:8080 with 1000 +connections and sends 100 messages over each established connection. Once `thor` +is done with smashing your connections it will generate a detailed report: + +``` +Thor: version: 1.0.0 + +God of Thunder, son of Odin and smasher of WebSockets! + +Thou shall: +- Spawn 4 workers. +- Create all the concurrent/parallel connections. +- Smash 1000 connections with the mighty Mjölnir. + +The answers you seek shall be yours, once I claim what is mine. + +Connecting to ws://localhost:8080 + + Opened 100 connections + Opened 200 connections + Opened 300 connections + Opened 400 connections + Opened 500 connections + Opened 600 connections + Opened 700 connections + Opened 800 connections + Opened 900 connections + Opened 1000 connections + + +Online 15000 milliseconds +Time taken 31775 milliseconds +Connected 1000 +Disconnected 0 +Failed 0 +Total transferred 120.46MB +Total received 120.43MB + +Durations (ms): + + min mean stddev median max +Handshaking 217 5036 4094 3902 14451 +Latency 0 215 104 205 701 + +Percentile (ms): + + 50% 66% 75% 80% 90% 95% 98% 98% 100% +Handshaking 3902 6425 8273 9141 11409 12904 13382 13945 14451 +Latency 205 246 266 288 371 413 437 443 701 +``` + +### License + +MIT diff --git a/bin/socketio b/bin/socketio index a0feda7..0e06d3a 100644 --- a/bin/socketio +++ b/bin/socketio @@ -14,11 +14,10 @@ var cli = require('commander'); cli.usage('[options] ws://localhost[@@vip]') .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) - .option('-M, --messages ', 'messages to be send per connection', parseInt, 0) + .option('-M, --messages ', 'number of messages to be send per connection', parseInt, 0) .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) - .option('-G, --generator ', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') .option('-T, --runtime ', 'timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c', parseInt, -1) @@ -30,8 +29,7 @@ cli.usage('[options] ws://localhost[@@vip]') // should bail out // if (!cli.args.length) return [ - 'Thor:' - , 'Odin is disapointed you... pithy human! You forgot to supply the urls.' + '愚かな人類よ! You forgot to supply the urls.' ].forEach(function stderr(line) { console.error(line); }); @@ -44,18 +42,16 @@ var cluster = require('cluster') , worker_num = worker_num > cli.amount * cli.args.length ? cli.amount * cli.args.length : worker_num , forked_worker_num = worker_num , connect_ids = Object.create(null) - , connections = 0 - , connecteds = {} - , received = 0 - , robin = []; + , connections = 0 // connected connections number of all worker + , worker_connections = {} // connected connections number of each worker + , received = 0 // total message received from server in all workers + , robin = []; // array store workers cluster.setupMaster({ exec: path.resolve(__dirname, '../worker-socketio.js') , silent: false , args: [ - cli.generator - ? path.resolve(process.cwd(), cli.generator) - : path.resolve(__dirname, '../generator.js'), + false, cli.protocol, !!cli.masked, !!cli.binary @@ -80,13 +76,13 @@ Object.keys(cluster.workers).forEach(function each(id) { switch (data.type) { case 'connected': - connecteds[data.worker_id] = data.connected; + worker_connections[data.worker_id] = data.connected; var all_connected = 0, all_workers = 0; - Object.keys(connecteds).forEach(function each(id) { + Object.keys(worker_connections).forEach(function each(id) { all_workers++; - all_connected += connecteds[id]; - console.log('[Master] %s connected, msg received %s in workers %s', connecteds[id], data.received_msg, id); + all_connected += worker_connections[id]; + console.log('[Master] %s connected, msg received %s in workers %s', worker_connections[id], data.msg_received, id); }); // Output the connection progress console.log('[Master] %s connected, in %s workers' + "\n", all_connected, all_workers); @@ -147,26 +143,6 @@ Object.keys(cluster.workers).forEach(function each(id) { robin.push(worker); }); -// -// Up our WebSocket socket connections. -// -[ - '' - , 'Thor: version: '+ cli._version - , '' - , 'God of Thunder, son of Odin and smasher of WebSockets!' - , '' - , 'Thou shall:' - , '- Spawn '+ forked_worker_num +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' - , '' -].forEach(function stdout(line) { - console.log(line); -}); - // // Metrics collection. // @@ -226,7 +202,27 @@ process.on('SIGINT', function end() { }); process.on('exit', function summary() { - console.log('-----------------'); + console.log('-------------------------------------'); + +// +// Up our WebSocket socket connections. +// +[ + '' + , 'Thor: version: '+ cli._version + , '' + , 'God of Thunder, son of Odin and smasher of WebSockets!' + , '' + , 'Thou shall:' + , '- Spawn '+ forked_worker_num +' workers.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' + , '' + , 'The answers you seek shall be yours, once I claim what is mine.' + , '' +].forEach(function stdout(line) { + console.log(line); +}); metrics.established().stop().summary(); }); diff --git a/bin/thor b/bin/thor index ebe1e6a..31e9e44 100755 --- a/bin/thor +++ b/bin/thor @@ -1,242 +1,259 @@ -#!/usr/bin/env node -'use strict'; - -var Metrics = require('../metrics') - , async = require('async') - , path = require('path') - , os = require('os'); - -// -// Setup the Command-Line Interface. -// -var cli = require('commander'); - -cli.usage('[options] ws://localhost') - .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) - .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) - .option('-M, --messages ', 'messages to be send per connection', parseInt, 0) - .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) - .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) - .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) - .option('-G, --generator ', 'custom message generators') - .option('-M, --masked', 'send the messaged with a mask') - .option('-b, --binary', 'send binary messages instead of utf-8') - .option('-T, --runtime ', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) - .version(require('../package.json').version) - .parse(process.argv); - -// -// Check if all required arguments are supplied, if we don't have a valid url we -// should bail out -// -if (!cli.args.length) return [ - 'Thor:' - , 'Odin is disapointed you... pithy human! You forgot to supply the urls.' -].forEach(function stderr(line) { - console.error(line); -}); - -// -// By Odin's beard, unleash thunder! -// -var cluster = require('cluster') - , workers = cli.workers || 1 - , ids = Object.create(null) - , connections = 0 - , received = 0 - , robin = []; - -cluster.setupMaster({ - exec: path.resolve(__dirname, '../mjolnir.js') - , silent: false - , args: [ - cli.generator - ? path.resolve(process.cwd(), cli.generator) - : path.resolve(__dirname, '../generator.js'), - cli.protocol, - !!cli.masked, - !!cli.binary - ] -}); - -while (workers--) cluster.fork(); - -Object.keys(cluster.workers).forEach(function each(id) { - var worker = cluster.workers[id]; - - worker.on('message', function message(data) { - - // console.info('catch worker message ' + (data.datas.length)); - var datas = []; - if (data.collection) { - datas = data.datas; - }else{ - datas = [data]; - } - for (var i = 0; i < datas.length; i++) { - var data = datas[i]; - - switch (data.type) { - case 'opened': - worker.emit('opened::'+ data.id); - - // Output the connection progress - if (++connections % 100 === 0) { - console.log(' Opened %s connections', connections); - } - - break; - - case 'open': - metrics.handshaken(data); - // worker.emit('open::'+ data.id); - - // Output the connection progress - /*if (++connections % 1000 === 0) { - console.log(' Opened %s connections', connections); - }*/ - - break; - - case 'close': - delete ids[data.id]; - - metrics.close(data); - // worker.emit('close::'+ data.id); - break; - - case 'error': - delete ids[data.id]; - - metrics.error(data); - // worker.emit('error::'+ data.id); - break; - - case 'message': - received++; - metrics.message(data); - // worker.emit('message::'+ data.id); - - } - - } - - // - // Check if we have processed all connections so we can quit cleanly. - // - if (!Object.keys(ids).length) process.exit(); - }); - - // Add our worker to our round robin queue so we can balance all our requests - // across the different workers that we spawned. - robin.push(worker); -}); - -// -// Up our WebSocket socket connections. -// -[ - '' - , 'Thor: version: '+ cli._version - , '' - , 'God of Thunder, son of Odin and smasher of WebSockets!' - , '' - , 'Thou shall:' - , '- Spawn '+ cli.workers +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' - , '' -].forEach(function stdout(line) { - console.log(line); -}); - -// -// Metrics collection. -// -var metrics = new Metrics(cli.amount * cli.args.length); - -// Iterate over all the urls so we can target multiple locations at once, which -// is helpfull if you are testing multiple loadbalancer endpoints for example. -async.forEach(cli.args, function forEach(url, done) { - var i = cli.amount - , completed = 0; - - console.log('Connecting to %s', url); - - url = url.split('@@'); - var localaddr = url.length > 1 ? url[1] : null; - url = url[0]; - // - // Create a simple WebSocket connection generator. - // - var queue = async.queue(function working(id, fn) { - var worker = robin.shift(); - - // Register the id, so we can keep track of the connections that we still - // need to process. - ids[id] = 1; - - // Process the connections - worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id, runtime: cli.runtime, localaddr: localaddr, send_opened: (cli.concurrent && cli.concurrent < cli.amount) }); - - // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections - if (cli.concurrent && cli.concurrent < cli.amount) { - worker.once('opened::'+ id, fn); - }; - // worker.once('open::'+ id, fn); - - // Add the worker back at the end of the round robin queue. - robin.push(worker); - }, cli.concurrent || Infinity); - - // When all the events are processed successfully we should call.. back ;P - queue.drain = done; - - // Add all connections to the processing queue; - while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); -}, function established(err) { - metrics.established(); -}); - -console.log(''); -// console.log("worker num:"+robin.length); - -process.on('SIGINT', function end() { - robin.forEach(function nuke(worker) { - try { worker.send({ shutdown: true }); } - catch (e) {} - }) -}); - -process.on('exit', function summary() { - console.log('-----------------'); -/* -// -// Up our WebSocket socket connections. -// -[ - '' - , 'Thor: version: '+ cli._version - , '' - , 'God of Thunder, son of Odin and smasher of WebSockets!' - , '' - , 'Thou shall:' - , '- Spawn '+ cli.workers +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' - , '' -].forEach(function stdout(line) { - console.log(line); -}); -*/ - // console.log('---------process exit in 2 seconds--------'); - /*setTimeout(function () { - console.log(''); - metrics.established().stop().summary(); - }, 3000);*/ - metrics.established().stop().summary(); -}); +#!/usr/bin/env node +'use strict'; + +var Metrics = require('../metrics') + , colors = require('colors') + , async = require('async') + , path = require('path') + , os = require('os'); + +// +// Setup the Command-Line Interface. +// +var cli = require('commander'); + +cli.usage('[options] ws://localhost') + .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) + .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) + .option('-M, --messages ', 'number of messages to be send per connection', parseInt, 0) + .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) + .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) + .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) + .option('-G, --generator ', 'custom message generators') + .option('-M, --masked', 'send the messaged with a mask') + .option('-b, --binary', 'send binary messages instead of utf-8') + .option('-PT, --pingInterval', 'seconds for doing ping to keep-alive', parseInt, 25) + .option('-PD, --pingData ', 'specify ping data to be send to the server') + .option('-RS, --realtimeStat', 'worker will send stat info to master in realtime') + .option('-RT, --runtime ', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) + .version(require('../package.json').version) + .parse(process.argv); + +// +// Check if all required arguments are supplied, if we don't have a valid url we +// should bail out +// +if (!cli.args.length) return [ + 'Thor:' + , 'Odin is disappointed in you... pity human! You forgot to supply the urls.' +].forEach(function stderr(line) { + console.error(line); +}); + +// +// By Odin's beard, unleash thunder! +// +var cluster = require('cluster') + , workers = cli.workers || 1 + , ids = Object.create(null) + , concurrents = Object.create(null) + , connections = 0 + , received = 0 + , robin = []; + +cluster.setupMaster({ + exec: path.resolve(__dirname, '../mjolnir.js') + , silent: false + , args: [ + cli.generator + ? path.resolve(process.cwd(), cli.generator) + : path.resolve(__dirname, '../generator.js'), + cli.protocol, + !!cli.masked, + !!cli.binary + ] +}); + +while (workers--) cluster.fork(); + +Object.keys(cluster.workers).forEach(function each(id) { + var worker = cluster.workers[id]; + + worker.on('message', function message(data) { + + var datas = []; + if (data.collection) { + datas = data.datas; + }else{ + datas = [data]; + } + for (var i = 0; i < datas.length; i++) { + var data = datas[i]; + if ('concurrent' in data) concurrents[data.id] = data.concurrent; + + switch (data.type) { + case 'open': + metrics.handshaken(data); + worker.emit('open::'+ data.id); + + // Output the connection progress + ++connections; + break; + + case 'close': + delete ids[data.id]; + + metrics.close(data); + break; + + case 'error': + delete ids[data.id]; + + metrics.error(data); + break; + + case 'message': + received++; + metrics.message(data); + } + + } + + // + // Check if we have processed all connections so we can quit cleanly. + // + if (!Object.keys(ids).length) process.exit(); + }); + + // Add our worker to our round robin queue so we can balance all our requests + // across the different workers that we spawned. + robin.push(worker); +}); + +// +// Output live, real-time stats. +// +function live() { + var frames = live.frames + , len = frames.length + , interval = 100 + , i = 0; + + live.interval = setInterval(function tick() { + var active = Object.keys(concurrents).reduce(function (count, id) { + return count + (concurrents[id] || 0); + }, 0); + + process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ + 'Created '.white + connections.toString().green, + 'Active '.white + active.toString().green + ].join(', ')); + }, interval); +} + +/** + * Live frames. + * + * @type {Array} + * @api private + */ +live.frames = [ + ' \u001b[96m◜ \u001b[90m' + , ' \u001b[96m◠ \u001b[90m' + , ' \u001b[96m◝ \u001b[90m' + , ' \u001b[96m◞ \u001b[90m' + , ' \u001b[96m◡ \u001b[90m' + , ' \u001b[96m◟ \u001b[90m' +]; + +/** + * Stop the live stats from running. + * + * @api private + */ +live.stop = function stop() { + process.stdout.write('\u001b[2K'); + clearInterval(live.interval); +}; + +// +// Up our WebSocket socket connections. +// +[ + '' + , 'Thor: version: '+ cli._version + , '' + , 'God of Thunder, son of Odin and smasher of WebSockets!' + , '' + , 'Thou shall:' + , '- Spawn '+ cli.workers +' workers.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' + , '' + , 'The answers you seek shall be yours, once I claim what is mine.' + , '' +].forEach(function stdout(line) { + console.log(line); +}); + +// +// Metrics collection. +// +var metrics = new Metrics(cli.amount * cli.args.length); + +// Iterate over all the urls so we can target multiple locations at once, which +// is helpfull if you are testing multiple loadbalancer endpoints for example. +async.forEach(cli.args, function forEach(url, done) { + var i = cli.amount + , completed = 0; + + console.log('Connecting to %s', url); + + url = url.split('@@'); + var localaddr = url.length > 1 ? url[1] : null; + url = url[0]; + // + // Create a simple WebSocket connection generator. + // + var queue = async.queue(function working(id, fn) { + var worker = robin.shift(); + + // Register the id, so we can keep track of the connections that we still + // need to process. + ids[id] = 1; + + // Process the connections + worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id + , localaddr: localaddr + , pingInterval: cli.pingInterval + , pingData: cli.pingData + , openedStat: (cli.concurrent && cli.concurrent < cli.amount) + , realtimeStat: cli.realtimeStat + , runtime: cli.runtime + }); + + // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections + if (cli.concurrent && cli.concurrent < cli.amount) { + worker.once('open::'+ id, fn); + }; + + // Add the worker back at the end of the round robin queue. + robin.push(worker); + }, cli.concurrent || Infinity); + + // When all the events are processed successfully we should call.. back ;P + queue.drain = done; + + // Add all connections to the processing queue; + while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); +}, function established(err) { + metrics.established(); +}); + +// +// We are setup, everything is running +// +console.log(''); +live(); + +process.once('SIGINT', function end() { + robin.forEach(function nuke(worker) { + try { worker.send({ shutdown: true }); } + catch (e) {} + }); +}); + +process.once('exit', function summary() { + live.stop(); + metrics.established().stop().summary(); +}); diff --git a/generator.js b/generator.js index b51819c..97ca9fc 100644 --- a/generator.js +++ b/generator.js @@ -1,46 +1,46 @@ -'use strict'; - -/** - * Generate a UTF-8 messages that we will be send to a connected client. - * - * @async - * @param {Number} size The specified in bytes for the message. - * @param {Function} fn The callback function for the data. - * @public - */ -exports.utf8 = function utf(size, fn) { - var key = 'utf8::'+ size - , cached = cache[key]; - - // We have a cached version of this size, return that instead. - if (cached) return fn(undefined, cached); - - cached = cache[key] = new Buffer(size).toString('utf-8'); - fn(undefined, cached); -}; - -/** - * Generate a binary message that we will be send to a connected client. - * - * @async - * @param {Number} size The specified in bytes for the message. - * @param {Function} fn The callback function for the data. - * @public - */ -exports.binary = function binary(size, fn) { - var key = 'binary::'+ size - , cached = cache[key]; - - // We have a cached version of this size, return that instead. - if (cached) return fn(undefined, cached); - - cached = cache[key] = new Buffer(size); - fn(undefined, cached); -}; - -// -// The following is not needed to create a session file. We don't want to -// re-create & re-allocate memory every time we receive a message so we cache -// them in a variable. -// -var cache = Object.create(null); +'use strict'; + +/** + * Generate a UTF-8 messages that we will be send to a connected client. + * + * @async + * @param {Number} size The specified in bytes for the message. + * @param {Function} fn The callback function for the data. + * @public + */ +exports.utf8 = function utf(size, fn) { + var key = 'utf8::'+ size + , cached = cache[key]; + + // We have a cached version of this size, return that instead. + if (cached) return fn(undefined, cached); + + cached = cache[key] = new Buffer(size).toString('utf-8'); + fn(undefined, cached); +}; + +/** + * Generate a binary message that we will be send to a connected client. + * + * @async + * @param {Number} size The specified in bytes for the message. + * @param {Function} fn The callback function for the data. + * @public + */ +exports.binary = function binary(size, fn) { + var key = 'binary::'+ size + , cached = cache[key]; + + // We have a cached version of this size, return that instead. + if (cached) return fn(undefined, cached); + + cached = cache[key] = new Buffer(size); + fn(undefined, cached); +}; + +// +// The following is not needed to create a session file. We don't want to +// re-create & re-allocate memory every time we receive a message so we cache +// them in a variable. +// +var cache = Object.create(null); diff --git a/metrics.js b/metrics.js index 6dfb849..7fef6a5 100644 --- a/metrics.js +++ b/metrics.js @@ -1,260 +1,260 @@ -'use strict'; - -var Stats = require('fast-stats').Stats - , colors = require('colors') - , sugar = require('sugar') - , table = require('tab'); - -/** - * Metrics collection and generation. - * - * @constructor - * @param {Number} requests The total amount of requests scheduled to be send - */ -function Metrics(requests) { - this.requests = requests; // The total amount of requests send - - this.connections = 0; // Connections established - this.disconnections = 0; // Closed connections - this.failures = 0; // Connections that received an error - - this.errors = Object.create(null); // Collection of different errors - this.timing = Object.create(null); // Different timings - - this.latency = new Stats(); // Latencies of the echo'd messages - this.handshaking = new Stats(); // Handshake duration - - this.read = 0; // Bytes read - this.send = 0; // Bytes send - - // Start tracking - this.start(); -} - -/** - * The metrics has started collecting. - * - * @api public - */ -Metrics.prototype.start = function start() { - this.timing.start = Date.now(); - return this; -}; - -/** - * The metrics has stopped collecting. - * - * @api public - */ -Metrics.prototype.stop = function stop() { - if (this.timing.stop) return this; - - this.timing.stop = Date.now(); - this.timing.duration = this.timing.stop - this.timing.start; - return this; -}; - -/** - * All the connections are established - * - * @api public - */ -Metrics.prototype.established = function established() { - if (this.timing.established) return this; - - this.timing.ready = Date.now(); - this.timing.established = this.timing.ready - this.timing.start; - return this; -}; - -/** - * Log an new error. - * - * @param {Object} data The error - * @api public - */ -Metrics.prototype.error = function error(data) { - this.failures++; - - var collection = this.errors[data.message]; - if (!collection) this.errors[data.message] = 1; - else this.errors[data.message]++; - - return this; -}; - -/** - * Register a message resposne. - * - * @param {Object} data The message details. - * @api public - */ -Metrics.prototype.message = function message(data) { - this.latency.push(data.latency); - - return this; -}; - -/** - * Register a successful handshake + open. - * - * @param {Object} data Handshake details. - * @api public - */ -Metrics.prototype.handshaken = function handshaken(data) { - this.connections++; - this.handshaking.push(data.duration); - - return this; -}; - -/** - * The connection has closed. - * - * @param {Object} data Close information - * @api public - */ -Metrics.prototype.close = function close(data) { - this.disconnections++; - this.read += data.read; - this.send += data.send; - - return this; -}; - -/** - * Generate a summary of the metrics. - * - * @returns {Object} The summary - * @api public - */ -Metrics.prototype.summary = function summary() { - var results = new table.TableOutputStream({ columns: [ - { label: '', width: 20 }, - { label: '' } - ]}); - - console.log(); - results.writeRow(['Online', this.timing.established + ' milliseconds']); - results.writeRow(['Time taken', this.timing.duration + ' milliseconds']); - results.writeRow(['Connected', this.connections]); - results.writeRow(['Disconnected', this.disconnections]); - results.writeRow(['Failed', this.failures]); - - results.writeRow(['Total transferred', this.send.bytes(2)]); - results.writeRow(['Total received', this.read.bytes(2)]); - - // Up next is outputting the series. - var handshaking = this.handshaking - , latency = this.latency - , hrange = handshaking.range() - , lrange = latency.range(); - - // - // Generate the width of the columns, based on the length of the longest - // number. If it's less then the max size of a label, we default to that. - // After that we also pad the strings with 1 char for extra spacing. - // - var width = (lrange[1] > hrange[1] ? lrange[1] : hrange[1]).toString().length; - if (width < 6) width = 6; - width++; - - console.log(); - console.log('Durations (ms):'); - console.log(); - - table.emitTable({ - columns: [ - { label: '', width: 20 }, - { label: 'min', width: width, align: 'left' }, - { label: 'mean', width: width, align: 'left' }, - { label: 'stddev', width: width, align: 'right' }, - { label: 'median', width: width, align: 'right' }, - { label: 'max', width: width, align: 'left' } - ], - rows: [ - [ - 'Handshaking', - hrange[0].toFixed(), - handshaking.amean().toFixed(), - handshaking.stddev().toFixed(), - handshaking.median().toFixed(), - hrange[1].toFixed() - ], - [ - 'Latency', - lrange[0].toFixed(), - latency.amean().toFixed(), - latency.stddev().toFixed(), - latency.median().toFixed(), - lrange[1].toFixed() - ] - ] - }); - - console.log(); - console.log('Percentile (ms):'); - console.log(); - - table.emitTable({ - columns: [ - { label: '', width: 20 }, - { label: ' 50%', width: width }, - { label: ' 66%', width: width }, - { label: ' 75%', width: width }, - { label: ' 80%', width: width }, - { label: ' 90%', width: width }, - { label: ' 95%', width: width }, - { label: ' 98%', width: width }, - { label: ' 98%', width: width }, - { label: '100%', width: width }, - ], - rows: [ - [ - 'Handshaking', - handshaking.percentile(50).toFixed(), - handshaking.percentile(66).toFixed(), - handshaking.percentile(75).toFixed(), - handshaking.percentile(80).toFixed(), - handshaking.percentile(90).toFixed(), - handshaking.percentile(95).toFixed(), - handshaking.percentile(98).toFixed(), - handshaking.percentile(99).toFixed(), - handshaking.percentile(100).toFixed() - ], - [ - 'Latency', - latency.percentile(50).toFixed(), - latency.percentile(66).toFixed(), - latency.percentile(75).toFixed(), - latency.percentile(80).toFixed(), - latency.percentile(90).toFixed(), - latency.percentile(95).toFixed(), - latency.percentile(98).toFixed(), - latency.percentile(99).toFixed(), - latency.percentile(100).toFixed() - ] - ] - }); - - // - // Output more error information, there could be multiple causes on why we - // failed to send a message. - // - if (this.failures) { - console.log(); - console.log('Received errors:'); - console.log(); - - Object.keys(this.errors).forEach(function error(err) { - results.writeRow([this.errors[err] +'x', err]); - }, this); - } - - return this; -}; - -// -// Expose the metrics constructor. -// -module.exports = Metrics; +'use strict'; + +var Stats = require('fast-stats').Stats + , colors = require('colors') + , sugar = require('sugar') + , table = require('tab'); + +/** + * Metrics collection and generation. + * + * @constructor + * @param {Number} requests The total amount of requests scheduled to be send + */ +function Metrics(requests) { + this.requests = requests; // The total amount of requests send + + this.connections = 0; // Connections established + this.disconnects = 0; // Closed connections + this.failures = 0; // Connections that received an error + + this.errors = Object.create(null); // Collection of different errors + this.timing = Object.create(null); // Different timings + + this.latency = new Stats(); // Latencies of the echo'd messages + this.handshaking = new Stats(); // Handshake duration + + this.read = 0; // Bytes read + this.send = 0; // Bytes send + + // Start tracking + this.start(); +} + +/** + * The metrics has started collecting. + * + * @api public + */ +Metrics.prototype.start = function start() { + this.timing.start = Date.now(); + return this; +}; + +/** + * The metrics has stopped collecting. + * + * @api public + */ +Metrics.prototype.stop = function stop() { + if (this.timing.stop) return this; + + this.timing.stop = Date.now(); + this.timing.duration = this.timing.stop - this.timing.start; + return this; +}; + +/** + * All the connections are established + * + * @api public + */ +Metrics.prototype.established = function established() { + if (this.timing.established) return this; + + this.timing.ready = Date.now(); + this.timing.established = this.timing.ready - this.timing.start; + return this; +}; + +/** + * Log an new error. + * + * @param {Object} data The error + * @api public + */ +Metrics.prototype.error = function error(data) { + this.failures++; + + var collection = this.errors[data.message]; + if (!collection) this.errors[data.message] = 1; + else this.errors[data.message]++; + + return this; +}; + +/** + * Register a message resposne. + * + * @param {Object} data The message details. + * @api public + */ +Metrics.prototype.message = function message(data) { + this.latency.push(data.latency); + + return this; +}; + +/** + * Register a successful handshake + open. + * + * @param {Object} data Handshake details. + * @api public + */ +Metrics.prototype.handshaken = function handshaken(data) { + this.connections++; + this.handshaking.push(data.duration); + + return this; +}; + +/** + * The connection has closed. + * + * @param {Object} data Close information + * @api public + */ +Metrics.prototype.close = function close(data) { + this.disconnections++; + this.read += data.read; + this.send += data.send; + + return this; +}; + +/** + * Generate a summary of the metrics. + * + * @returns {Object} The summary + * @api public + */ +Metrics.prototype.summary = function summary() { + var results = new table.TableOutputStream({ columns: [ + { label: '', width: 20 }, + { label: '' } + ]}); + + console.log(); + results.writeRow(['Online', this.timing.established + ' milliseconds']); + results.writeRow(['Time taken', this.timing.duration + ' milliseconds']); + results.writeRow(['Connected', this.connections]); + results.writeRow(['Disconnected', this.disconnects]); + results.writeRow(['Failed', this.failures]); + + results.writeRow(['Total transferred', this.send.bytes(2)]); + results.writeRow(['Total received', this.read.bytes(2)]); + + // Up next is outputting the series. + var handshaking = this.handshaking + , latency = this.latency + , hrange = handshaking.range() + , lrange = latency.range(); + + // + // Generate the width of the columns, based on the length of the longest + // number. If it's less then the max size of a label, we default to that. + // After that we also pad the strings with 1 char for extra spacing. + // + var width = (lrange[1] > hrange[1] ? lrange[1] : hrange[1]).toString().length; + if (width < 6) width = 6; + width++; + + console.log(); + console.log('Durations (ms):'); + console.log(); + + table.emitTable({ + columns: [ + { label: '', width: 20 }, + { label: 'min', width: width, align: 'left' }, + { label: 'mean', width: width, align: 'left' }, + { label: 'stddev', width: width, align: 'right' }, + { label: 'median', width: width, align: 'right' }, + { label: 'max', width: width, align: 'left' } + ], + rows: [ + [ + 'Handshaking', + hrange[0].toFixed(), + handshaking.amean().toFixed(), + handshaking.stddev().toFixed(), + handshaking.median().toFixed(), + hrange[1].toFixed() + ], + [ + 'Latency', + lrange[0].toFixed(), + latency.amean().toFixed(), + latency.stddev().toFixed(), + latency.median().toFixed(), + lrange[1].toFixed() + ] + ] + }); + + console.log(); + console.log('Percentile (ms):'); + console.log(); + + table.emitTable({ + columns: [ + { label: '', width: 20 }, + { label: ' 50%', width: width }, + { label: ' 66%', width: width }, + { label: ' 75%', width: width }, + { label: ' 80%', width: width }, + { label: ' 90%', width: width }, + { label: ' 95%', width: width }, + { label: ' 98%', width: width }, + { label: ' 98%', width: width }, + { label: '100%', width: width }, + ], + rows: [ + [ + 'Handshaking', + handshaking.percentile(50).toFixed(), + handshaking.percentile(66).toFixed(), + handshaking.percentile(75).toFixed(), + handshaking.percentile(80).toFixed(), + handshaking.percentile(90).toFixed(), + handshaking.percentile(95).toFixed(), + handshaking.percentile(98).toFixed(), + handshaking.percentile(99).toFixed(), + handshaking.percentile(100).toFixed() + ], + [ + 'Latency', + latency.percentile(50).toFixed(), + latency.percentile(66).toFixed(), + latency.percentile(75).toFixed(), + latency.percentile(80).toFixed(), + latency.percentile(90).toFixed(), + latency.percentile(95).toFixed(), + latency.percentile(98).toFixed(), + latency.percentile(99).toFixed(), + latency.percentile(100).toFixed() + ] + ] + }); + + // + // Output more error information, there could be multiple causes on why we + // failed to send a message. + // + if (this.failures) { + console.log(); + console.log('Received errors:'); + console.log(); + + Object.keys(this.errors).forEach(function error(err) { + results.writeRow([this.errors[err] +'x', err]); + }, this); + } + + return this; +}; + +// +// Expose the metrics constructor. +// +module.exports = Metrics; diff --git a/mjolnir.js b/mjolnir.js index f24d1cc..7bf7910 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -1,181 +1,174 @@ -'use strict'; - -var Socket = require('ws') - , connections = {}; - -// -// Get the session document that is used to generate the data. -// -var session = require(process.argv[2]); - -// -// WebSocket connection details. -// -var masked = process.argv[4] === 'true' - , binary = process.argv[5] === 'true' - , protocol = +process.argv[3] || 13; - -// 收集后一次性send给master -var metrics_datas = {collection:true, datas:[]}; - -process.on('message', function message(task) { - var now = Date.now(); - - // - // Write a new message to the socket. The message should have a size of x - // - if ('write' in task) { - Object.keys(connections).forEach(function write(id) { - write(connections[id], task, id); - }); - } - - // - // Shut down every single socket. - // - if (task.shutdown) { - Object.keys(connections).forEach(function shutdown(id) { - connections[id].close(); - }); - } - - // End of the line, we are gonna start generating new connections. - if (!task.url) return; - - var sock_opts = { - protocolVersion: protocol - }; - - if (task.localaddr) { - sock_opts.localAddress = task.localaddr; - }; - var socket = new Socket(task.url, sock_opts); - socket.last = Date.now(); - var inteval = null; - - socket.on('open', function open() { - var send_data = { type: 'open', duration: Date.now() - now, id: task.id }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - // write(socket, task, task.id); - // - if (task.send_opened) { - process.send({ type: 'opened', duration: Date.now() - now, id: task.id }); - }; - - inteval = setInterval(function ping(id, socket) { - if(socket && (typeof socket.ping == 'function')) { - socket.ping(); - }else{ - clearInterval(inteval); - } - }, 25000, task.id, socket); - // As the `close` event is fired after the internal `_socket` is cleaned up - // we need to do some hacky shit in order to tack the bytes send. - // - // process.send({ type: 'showopened', opened: Object.keys(connections).length }); - }); - - socket.on('message', function message(data) { - var send_data = { - type: 'message', latency: Date.now() - socket.last, - id: task.id - }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - console.log('['+task.id.substr(task.id.indexOf('::'))+']socket on message@'+socket.last, "\n", data, "\n"); - // Only write as long as we are allowed to send messages - if (--task.messages && task.messages > 0) { - write(socket, task, task.id); - } else { - // socket.close(); - } - }); - - socket.on('close', function close(log) { - var internal = socket._socket || {}; - // console.info('['+task.id+']socket on close'); - // console.log(socket); - - var send_data = { - type: 'close', id: task.id, - read: internal.bytesRead || 0, - send: internal.bytesWritten || 0 - }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - if (inteval) { - clearInterval(inteval); - }; - delete connections[task.id]; - // console.log('close ', Object.keys(connections).length); - if (Object.keys(connections) <= 0) { - // 一次性发送 - process.send(metrics_datas); - }; - }); - - socket.on('error', function error(err) { - console.error('['+task.id+']socket on error-------', "\n", err, "\n", '-------error'); - var send_data = { type: 'error', message: err.message, id: task.id }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - socket.close(); - socket.emit('close'); - delete connections[task.id]; - }); - - // Adding a new socket to our socket collection. - connections[task.id] = socket; - - // timeout to close socket - if (task.runtime && task.runtime > 0) { - setTimeout(function timeoutToCloseSocket(id, socket) { - // console.log('timeout to close socket:'+id); - socket.close(); - }, task.runtime * 1000, task.id, socket); - } -}); - -process.on('SIGINT', function () { - // console.log('process.SIGINT') -}); -process.on('exit', function () { - // console.log('process.exit') - // process.send(metrics_datas); -}); - -/** - * Helper function from writing messages to the socket. - * - * @param {WebSocket} socket WebSocket connection we should write to - * @param {Object} task The given task - * @param {String} id - * @param {Function} fn The callback - * @api private - */ -function write(socket, task, id, fn) { - var start = socket.last = Date.now(); - console.info("\n" + 'no no no! no write please~! ' + "\n" + 'Do that if and only if u know the server can parse ur msg, or server will cut ur connection.' + "\n"); - - session[binary ? 'binary' : 'utf8'](task.size, function message(err, data) { - socket.send(data, { - binary: binary, - mask: masked - }, function sending(err) { - if (err) { - var send_data = { type: 'error', message: err.message }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - socket.close(); - delete connections[id]; - } - - if (fn) fn(err); - }); - }); -} +'use strict'; + +var Socket = require('ws') + , connections = {} + , concurrent = 0; + +// +// Get the session document that is used to generate the data. +// +var session = require(process.argv[2]); + +// +// WebSocket connection details. +// +var masked = process.argv[4] === 'true' + , binary = process.argv[5] === 'true' + , protocol = +process.argv[3] || 13; + +// 收集后一次性send给master +var metrics_datas = {collection:true, datas:[]} + , process_send = function(data, task) { + if (task.realtimeStat || ('open' == data.type && task.openedStat)) { + process.send(data); + }else{ + metrics_datas.datas.push(data); + } + } + , checkConnectionLength = function(interval){ + if (Object.keys(connections).length <= 0) { + if (interval) { + clearInterval(interval); + }; + // 一次性发送 + process.send(metrics_datas, null, function clearDatas(err){ + // invoked after the message is sent but before the target may have received it + if (err) {return;}; + // WARNING: maybe we should use synchronize method here + metrics_datas.datas = []; + }); + }; + }; + +process.on('message', function message(task) { + var now = Date.now(); + + // + // Write a new message to the socket. The message should have a size of x + // + if ('write' in task) { + Object.keys(connections).forEach(function write(id) { + write(connections[id], task, id); + }); + } + + // + // Shut down every single socket. + // + if (task.shutdown) { + Object.keys(connections).forEach(function shutdown(id) { + connections[id].close(); + }); + } + + // End of the line, we are gonna start generating new connections. + if (!task.url) return; + + var socket = new Socket(task.url, { + protocolVersion: protocol, + localAddress: task.localaddr || null + }); + socket.last = Date.now(); + var interval = null; + + socket.on('open', function open() { + process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }); + // write(socket, task, task.id); + + if (task.pingInterval && task.pingInterval > 0) { + interval = setInterval(function ping(id, socket) { + if(socket && task.pingData && (typeof socket.send == 'function')) { + write(socket, task, task.id, null, task.pingData); + }else if(socket && (typeof socket.ping == 'function')) { + socket.ping(); + }else if(socket) { + write(socket, task, task.id); + }else{ + clearInterval(interval); + } + }, task.pingInterval * 1000, task.id, socket); + } + // As the `close` event is fired after the internal `_socket` is cleaned up + // we need to do some hacky shit in order to tack the bytes send. + }); + + socket.on('message', function message(data) { + process_send({ + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, + id: task.id + }); + + // Only write as long as we are allowed to send messages + if (task.messages > 0) + if (--task.messages) { + write(socket, task, task.id); + } else { + socket.close(); + } + }); + + socket.on('close', function close() { + var internal = socket._socket || {}; + + process_send({ + type: 'close', id: task.id, concurrent: --concurrent, + read: internal.bytesRead || 0, + send: internal.bytesWritten || 0 + }); + + delete connections[task.id]; + checkConnectionLength(interval); + }); + + socket.on('error', function error(err) { + process_send({ type: 'error', message: err.message, id: task.id, concurrent: --concurrent }); + + socket.close(); + delete connections[task.id]; + }); + + // Adding a new socket to our socket collection. + ++concurrent; + connections[task.id] = socket; + + // timeout to close socket + if (task.runtime && task.runtime > 0) { + setTimeout(function timeoutToCloseSocket(id, socket) { + socket.close(); + }, task.runtime * 1000, task.id, socket); + } +}); + +process.on('SIGINT', function () {}); +process.on('exit', function () {}); + +/** + * Helper function from writing messages to the socket. + * + * @param {WebSocket} socket WebSocket connection we should write to + * @param {Object} task The given task + * @param {String} id + * @param {Function} fn The callback + * @param {String} data + * @api private + */ +function write(socket, task, id, fn, data) { + // i thank the generator doesn't make any sense, but just let me do some change and leave it alone + session[binary ? 'binary' : 'utf8'](data || task.size, function message(err, data) { + var start = socket.last = Date.now(); + + socket.send(data, { + binary: binary, + mask: masked + }, function sending(err) { + if (err) { + process_send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }); + + socket.close(); + delete connections[id]; + } + + if (fn) fn(err); + }); + }); +} diff --git a/package.json b/package.json index b7f3657..4342e89 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,35 @@ -{ - "name": "thor", - "version": "1.0.0", - "description": "Thor is WebSocket benchmark utility", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "https://github.com/iorichina/thor.git" - }, - "keywords": [ - "WebSocket", - "benchmark", - "load" - ], - "author": "Arnout Kazemier ", - "license": "MIT", - "dependencies": { - "engine.io-client": "git://github.com/iorichina/engine.io-client.git#1.5.4", - "commander": "1.1.x", - "async": "0.2.x", - "tab": "0.1.x", - "colors": "0.6.x", - "ws": "git://github.com/einaros/ws.git", - "fast-stats": "git://github.com/bluesmoon/node-faststats.git", - "sugar": "1.3.8" - }, - "bin": { - "thor": "./bin/thor", - "thor-socketio": "./bin/socketio" - } -} +{ + "name": "thor", + "version": "1.0.1", + "description": "Thor is WebSocket benchmark utility", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/iorichina/thor.git" + }, + "keywords": [ + "WebSocket", + "benchmark", + "load" + ], + "author": "Arnout Kazemier ", + "license": "MIT", + "dependencies": { + "engine.io-client": "git://github.com/iorichina/engine.io-client.git#1.5.4", + "socket.io-client": "1.3.7", + "commander": "1.1.x", + "async": "0.2.x", + "tab": "0.1.x", + "colors": "0.6.x", + "ws": "git://github.com/einaros/ws.git", + "fast-stats": "git://github.com/bluesmoon/node-faststats.git", + "sugar": "1.3.8" + }, + "bin": { + "thor": "./bin/thor", + "thor-socketio": "./bin/socketio" + } +} diff --git a/worker-socketio.js b/worker-socketio.js index 4b590b1..efbc65e 100644 --- a/worker-socketio.js +++ b/worker-socketio.js @@ -1,172 +1,177 @@ -'use strict'; - -var Socket = require('socket.io-client') - , connections = {} - , connected = 0 - , received_msg = 0; - -// -// Get the session document that is used to generate the data. -// -var session = require(process.argv[2]); - -// 收集后一次性send给master -var metrics_datas = {collection:true, datas:[]}; - -var sendToMaster = setInterval(function () { - process.send({ type: 'connected', connected: connected, received_msg: received_msg, worker_id: process.pid }); -}, 60000); - -process.on('message', function message(task) { - var now = Date.now(); - - // - // Shut down every single socket. - // - if (task.shutdown) { - console.log('shutdown', process.pid); - Object.keys(connections).forEach(function shutdown(id) { - connections[id] && connections[id].disconnect(); - }); - - setInterval(function(){ - if (connected <= 0) { - clearInterval(sendToMaster); - // 一次性发送 - process.send(metrics_datas, null, function(err){ - metrics_datas.datas = []; - process.exit(); - }); - }; - }, 30000); - } - - // End of the line, we are gonna start generating new connections. - if (!task.url) return; - - var sock_opts = { - forceNew:true, - transports:['websocket'] - }; - - if (task.localaddr) { - sock_opts.localAddress = task.localaddr; - }; - var socket = new Socket(task.url, sock_opts); - socket.last = Date.now(); - - socket.on('connect', function open() { - connected ++; - // console.info(task.id + " opened", connections); - var send_data = { type: 'open', duration: Date.now() - now, id: task.id }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - // write(socket, task, task.id); - // - if (task.send_opened) { - process.send({ type: 'opened', duration: Date.now() - now, id: task.id }); - }; - /*if (connected % 1000 === 0) { - console.info('worker ', process.pid, ' has connection: ', connected); - };*/ - - // As the `close` event is fired after the internal `_socket` is cleaned up - // we need to do some hacky shit in order to tack the bytes send. - }); - - function message(data) { - var send_data = { - type: 'message', latency: Date.now() - socket.last, - id: task.id - }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - received_msg++; - // console.log('['+task.id.substr(task.id.indexOf('::'))+']socket on message@'+socket.last, "\n", data, "\n"); - // Only write as long as we are allowed to send messages - if (--task.messages && task.messages > 0) { - write(socket, task, task.id); - } else { - // socket.disconnect(); - } - }; - socket.on('message', message); - socket.on('onMessage', message); - - socket.on('disconnect', function close() { - connected--; - var internal = {}; - // console.log(socket); - // var internal = socket.io.engine._socket || {}; - // console.info('['+task.id+']socket on close'); - - var send_data = { - type: 'close', id: task.id, - read: internal.bytesRead || 0, - send: internal.bytesWritten || 0 - }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - delete connections[task.id]; - // console.log('close ', Object.keys(connections).length); - if (Object.keys(connections) <= 0) { - clearInterval(sendToMaster); - // 一次性发送 - process.send(metrics_datas); - metrics_datas.datas = []; - }; - }); - - socket.on('error', function error(err) { - console.error('['+task.id+']socket on error-------', "\n", err, "\n", '-------error'); - var send_data = { type: 'error', message: err.message, id: task.id }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - socket.disconnect(); - socket.emit('disconnect'); - delete connections[task.id]; - }); - - // Adding a new socket to our socket collection. - connections[task.id] = socket; - - // timeout to close socket - if (task.runtime && task.runtime > 0) { - setTimeout(function timeoutToCloseSocket(id, socket) { - // console.log('timeout to close socket:'+id); - socket.disconnect(); - }, task.runtime * 1000, task.id, socket); - } -}); - -process.on('SIGINT', function () { -}); - -/** - * Helper function from writing messages to the socket. - * - * @param {WebSocket} socket WebSocket connection we should write to - * @param {Object} task The given task - * @param {String} id - * @param {Function} fn The callback - * @api private - */ -function write(socket, task, id, fn) { - var start = socket.last = Date.now(); - socket.send(task.size, function (err) { - if (err) { - var send_data = { type: 'error', message: err.message }; - // process.send(send_data); - metrics_datas.datas.push(send_data); - - socket.disconnect(); - delete connections[id]; - } - - if (fn) fn(err); - }); - -} +'use strict'; + +var Socket = require('socket.io-client') + , connections = {} + , connected = 0 + , msg_received = 0; + +// 收集后一次性send给master +var metrics_datas = {collection:true, datas:[]}; + +var sendToMaster = setInterval(function () { + process.send({ type: 'connected', connected: connected, msg_received: msg_received, worker_id: process.pid }); + }, 60000) + , checkConnectionLength = function(){ + if (Object.keys(connections).length <= 0) { + // 一次性发送 + process.send(metrics_datas, null, function clearDatas(err){ + // invoked after the message is sent but before the target may have received it + if (err) {return;}; + metrics_datas.datas = []; + }); + }; + }; + +process.on('message', function message(task) { + var now = Date.now(); + + // + // Shut down every single socket. + // + if (task.shutdown) { + console.log('shutdown', process.pid); + Object.keys(connections).forEach(function shutdown(id) { + connections[id] && connections[id].disconnect(); + }); + + setInterval(function(){ + if (connected <= 0) { + clearInterval(sendToMaster); + // 一次性发送 + process.send(metrics_datas, null, function(err){ + metrics_datas.datas = []; + process.exit(); + }); + }; + }, 30000); + } + + // End of the line, we are gonna start generating new connections. + if (!task.url) return; + + var sock_opts = { + forceNew:true, + transports:['websocket'] + }; + + if (task.localaddr) { + sock_opts.localAddress = task.localaddr; + }; + var socket = new Socket(task.url, sock_opts); + socket.last = Date.now(); + + socket.on('connect', function open() { + connected ++; + // console.info(task.id + " opened", connections); + var send_data = { type: 'open', duration: Date.now() - now, id: task.id }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + // write(socket, task, task.id); + // + if (task.send_opened) { + process.send(send_data); + }; + + // As the `close` event is fired after the internal `_socket` is cleaned up + // we need to do some hacky shit in order to tack the bytes send. + }); + + function message(data) { + var send_data = { + type: 'message', latency: Date.now() - socket.last, + id: task.id + }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + msg_received++; + // console.log('['+task.id.substr(task.id.indexOf('::'))+']socket on message@'+socket.last, "\n", data, "\n"); + // Only write as long as we are allowed to send messages + if (--task.messages && task.messages > 0) { + write(socket, task, task.id); + } else { + // socket.disconnect(); + } + }; + socket.on('message', message); + socket.on('onMessage', message); + + socket.on('disconnect', function close() { + connected--; + var internal = {}; + try{ + internal = socket.io.engine.transport.ws._socket || {}; + }catch(e){ + internal = {}; + } + // console.info('['+task.id+']socket on close'); + + var send_data = { + type: 'close', id: task.id, + read: internal.bytesRead || 0, + send: internal.bytesWritten || 0 + }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + delete connections[task.id]; + // console.log('close ', Object.keys(connections).length); + if (Object.keys(connections) <= 0) { + clearInterval(sendToMaster); + // 一次性发送 + process.send(metrics_datas); + metrics_datas.datas = []; + }; + }); + + socket.on('error', function error(err) { + console.error('['+task.id+']socket on error-------', "\n", err, "\n", '-------error'); + var send_data = { type: 'error', message: err.message, id: task.id }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + socket.disconnect(); + socket.emit('disconnect'); + delete connections[task.id]; + }); + + // Adding a new socket to our socket collection. + connections[task.id] = socket; + + // timeout to close socket + if (task.runtime && task.runtime > 0) { + setTimeout(function timeoutToCloseSocket(id, socket) { + // console.log('timeout to close socket:'+id); + socket.disconnect(); + }, task.runtime * 1000, task.id, socket); + } +}); + +process.on('SIGINT', function () { +}); + +/** + * Helper function from writing messages to the socket. + * + * @param {WebSocket} socket WebSocket connection we should write to + * @param {Object} task The given task + * @param {String} id + * @param {Function} fn The callback + * @api private + */ +function write(socket, task, id, fn) { + var start = socket.last = Date.now(); + socket.send(task.size, function (err) { + if (err) { + var send_data = { type: 'error', message: err.message }; + // process.send(send_data); + metrics_datas.datas.push(send_data); + + socket.disconnect(); + delete connections[id]; + } + + if (fn) fn(err); + }); + +} From 40960cdd7e30314040a5877b87db583050db064f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 6 Nov 2015 19:08:00 +0800 Subject: [PATCH 03/11] optimize --- bin/thor | 55 +++++++++++++++++++++++----------------- mjolnir.js | 71 ++++++++++++++++++++++++++++++---------------------- package.json | 2 +- 3 files changed, 74 insertions(+), 54 deletions(-) diff --git a/bin/thor b/bin/thor index 31e9e44..f985511 100755 --- a/bin/thor +++ b/bin/thor @@ -12,30 +12,34 @@ var Metrics = require('../metrics') // var cli = require('commander'); -cli.usage('[options] ws://localhost') - .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) - .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) - .option('-M, --messages ', 'number of messages to be send per connection', parseInt, 0) - .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) - .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) - .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) - .option('-G, --generator ', 'custom message generators') +cli.usage(['[options] urls', + ,' urls like' + ,' ws://host/path/?params http://host/socket.io/?transport=websocket' + ,' ws://host/path/?params@@192.168.102.33 http://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) + .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) + .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) + .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) + .option('-P, --protocol [protocol]', 'WebSocket protocol version, default 13', parseInt, 13) + .option('-B, --buffer [size]', 'size of the messages that are send, default 1024', parseInt, 1024) + .option('-W, --workers [cpus]', 'workers to be spawned, default cpus.length', parseInt, os.cpus().length) + .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') - .option('-PT, --pingInterval', 'seconds for doing ping to keep-alive', parseInt, 25) - .option('-PD, --pingData ', 'specify ping data to be send to the server') - .option('-RS, --realtimeStat', 'worker will send stat info to master in realtime') - .option('-RT, --runtime ', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) + .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) + .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified') + .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') + .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) + .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) .version(require('../package.json').version) .parse(process.argv); + // // Check if all required arguments are supplied, if we don't have a valid url we // should bail out // if (!cli.args.length) return [ - 'Thor:' - , 'Odin is disappointed in you... pity human! You forgot to supply the urls.' + '愚かな人類よ! You forgot to supply the urls. Type -h for help.' ].forEach(function stderr(line) { console.error(line); }); @@ -49,7 +53,9 @@ var cluster = require('cluster') , concurrents = Object.create(null) , connections = 0 , received = 0 - , robin = []; + , robin = [] + , workers = Math.min(cli.amount * cli.args.length, workers) + , forked_workers = workers; cluster.setupMaster({ exec: path.resolve(__dirname, '../mjolnir.js') @@ -60,7 +66,8 @@ cluster.setupMaster({ : path.resolve(__dirname, '../generator.js'), cli.protocol, !!cli.masked, - !!cli.binary + !!cli.binary, + cli.statInterval ] }); @@ -126,7 +133,7 @@ Object.keys(cluster.workers).forEach(function each(id) { function live() { var frames = live.frames , len = frames.length - , interval = 100 + , interval = cli.statInterval * 1000 , i = 0; live.interval = setInterval(function tick() { @@ -136,7 +143,8 @@ function live() { process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ 'Created '.white + connections.toString().green, - 'Active '.white + active.toString().green + 'Active '.white + active.toString().green, + '@'.white + new Date().toLocaleString().green ].join(', ')); }, interval); } @@ -176,7 +184,7 @@ live.stop = function stop() { , 'God of Thunder, son of Odin and smasher of WebSockets!' , '' , 'Thou shall:' - , '- Spawn '+ cli.workers +' workers.' + , '- Spawn '+ forked_workers +' workers.' , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' , '' @@ -195,7 +203,8 @@ var metrics = new Metrics(cli.amount * cli.args.length); // is helpfull if you are testing multiple loadbalancer endpoints for example. async.forEach(cli.args, function forEach(url, done) { var i = cli.amount - , completed = 0; + , completed = 0 + , goOnTaskQueueWhenConcurrentLimited = (cli.concurrent && cli.concurrent < cli.amount); console.log('Connecting to %s', url); @@ -216,14 +225,14 @@ async.forEach(cli.args, function forEach(url, done) { worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id , localaddr: localaddr , pingInterval: cli.pingInterval - , pingData: cli.pingData - , openedStat: (cli.concurrent && cli.concurrent < cli.amount) + , serverEngine: cli.serverEngine + , nextTask: goOnTaskQueueWhenConcurrentLimited , realtimeStat: cli.realtimeStat , runtime: cli.runtime }); // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections - if (cli.concurrent && cli.concurrent < cli.amount) { + if (goOnTaskQueueWhenConcurrentLimited) { worker.once('open::'+ id, fn); }; diff --git a/mjolnir.js b/mjolnir.js index 7bf7910..d102712 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -16,29 +16,39 @@ var masked = process.argv[4] === 'true' , binary = process.argv[5] === 'true' , protocol = +process.argv[3] || 13; -// 收集后一次性send给master +// collect metics datas var metrics_datas = {collection:true, datas:[]} , process_send = function(data, task) { - if (task.realtimeStat || ('open' == data.type && task.openedStat)) { + if (task.realtimeStat || ('open' == data.type && task.nextTask)) { process.send(data); }else{ metrics_datas.datas.push(data); } } - , checkConnectionLength = function(interval){ - if (Object.keys(connections).length <= 0) { - if (interval) { - clearInterval(interval); - }; - // 一次性发送 - process.send(metrics_datas, null, function clearDatas(err){ - // invoked after the message is sent but before the target may have received it - if (err) {return;}; - // WARNING: maybe we should use synchronize method here - metrics_datas.datas = []; - }); + , process_sendAll = function(end) { + if (metrics_datas.datas.length <= 0) { + return; }; - }; + // send all data to parent + process.send(metrics_datas, null, function clearDatas(err){ + // invoked after the message is sent but before the target may have received it + if (err) {return;} + // WARNING: maybe we should use synchronize method here + metrics_datas.datas = []; + if (end) { + process.exit(); + }; + }); + } + , checkConnectionLength = function(){ + if (Object.keys(connections).length <= 0) { + process_sendAll(true); + } + } + , statInterval = +process.argv[6] || 60 + , workerStatInterval = setInterval(function () { + process_sendAll(); + }, statInterval * 1000); process.on('message', function message(task) { var now = Date.now(); @@ -69,22 +79,22 @@ process.on('message', function message(task) { localAddress: task.localaddr || null }); socket.last = Date.now(); - var interval = null; + var pingInterval = null; socket.on('open', function open() { - process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }); + process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }, task); // write(socket, task, task.id); if (task.pingInterval && task.pingInterval > 0) { - interval = setInterval(function ping(id, socket) { - if(socket && task.pingData && (typeof socket.send == 'function')) { - write(socket, task, task.id, null, task.pingData); - }else if(socket && (typeof socket.ping == 'function')) { - socket.ping(); - }else if(socket) { - write(socket, task, task.id); + pingInterval = setInterval(function ping(id, socket) { + if(socket){ + if(task.serverEngine && task.serverEngine in ['socket.io','engine.io','netty-socketio']) { + socket.ping('2'); + }else{ + socket.ping(); + } }else{ - clearInterval(interval); + clearInterval(pingInterval); } }, task.pingInterval * 1000, task.id, socket); } @@ -96,7 +106,7 @@ process.on('message', function message(task) { process_send({ type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, id: task.id - }); + }, task); // Only write as long as we are allowed to send messages if (task.messages > 0) @@ -114,14 +124,15 @@ process.on('message', function message(task) { type: 'close', id: task.id, concurrent: --concurrent, read: internal.bytesRead || 0, send: internal.bytesWritten || 0 - }); + }, task); delete connections[task.id]; - checkConnectionLength(interval); + clearInterval(pingInterval); + checkConnectionLength(); }); socket.on('error', function error(err) { - process_send({ type: 'error', message: err.message, id: task.id, concurrent: --concurrent }); + process_send({ type: 'error', message: err.message, id: task.id, concurrent: --concurrent }, task); socket.close(); delete connections[task.id]; @@ -162,7 +173,7 @@ function write(socket, task, id, fn, data) { mask: masked }, function sending(err) { if (err) { - process_send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }); + process_send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }, task); socket.close(); delete connections[id]; diff --git a/package.json b/package.json index 4342e89..6ca7616 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "thor", - "version": "1.0.1", + "version": "1.1.1", "description": "Thor is WebSocket benchmark utility", "main": "index.js", "scripts": { From 720bcdcabc9b0db0470918dc8606044a2e4d06b8 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 6 Nov 2015 19:21:54 +0800 Subject: [PATCH 04/11] set unix style --- bin/thor | 536 +++++++++++++++++++++++++++---------------------------- 1 file changed, 268 insertions(+), 268 deletions(-) diff --git a/bin/thor b/bin/thor index f985511..cecfa2d 100755 --- a/bin/thor +++ b/bin/thor @@ -1,268 +1,268 @@ -#!/usr/bin/env node -'use strict'; - -var Metrics = require('../metrics') - , colors = require('colors') - , async = require('async') - , path = require('path') - , os = require('os'); - -// -// Setup the Command-Line Interface. -// -var cli = require('commander'); - -cli.usage(['[options] urls', - ,' urls like' - ,' ws://host/path/?params http://host/socket.io/?transport=websocket' - ,' ws://host/path/?params@@192.168.102.33 http://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) - .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) - .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) - .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) - .option('-P, --protocol [protocol]', 'WebSocket protocol version, default 13', parseInt, 13) - .option('-B, --buffer [size]', 'size of the messages that are send, default 1024', parseInt, 1024) - .option('-W, --workers [cpus]', 'workers to be spawned, default cpus.length', parseInt, os.cpus().length) - .option('-G, --generator [file]', 'custom message generators') - .option('-M, --masked', 'send the messaged with a mask') - .option('-b, --binary', 'send binary messages instead of utf-8') - .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) - .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified') - .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') - .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) - .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) - .version(require('../package.json').version) - .parse(process.argv); - - -// -// Check if all required arguments are supplied, if we don't have a valid url we -// should bail out -// -if (!cli.args.length) return [ - '愚かな人類よ! You forgot to supply the urls. Type -h for help.' -].forEach(function stderr(line) { - console.error(line); -}); - -// -// By Odin's beard, unleash thunder! -// -var cluster = require('cluster') - , workers = cli.workers || 1 - , ids = Object.create(null) - , concurrents = Object.create(null) - , connections = 0 - , received = 0 - , robin = [] - , workers = Math.min(cli.amount * cli.args.length, workers) - , forked_workers = workers; - -cluster.setupMaster({ - exec: path.resolve(__dirname, '../mjolnir.js') - , silent: false - , args: [ - cli.generator - ? path.resolve(process.cwd(), cli.generator) - : path.resolve(__dirname, '../generator.js'), - cli.protocol, - !!cli.masked, - !!cli.binary, - cli.statInterval - ] -}); - -while (workers--) cluster.fork(); - -Object.keys(cluster.workers).forEach(function each(id) { - var worker = cluster.workers[id]; - - worker.on('message', function message(data) { - - var datas = []; - if (data.collection) { - datas = data.datas; - }else{ - datas = [data]; - } - for (var i = 0; i < datas.length; i++) { - var data = datas[i]; - if ('concurrent' in data) concurrents[data.id] = data.concurrent; - - switch (data.type) { - case 'open': - metrics.handshaken(data); - worker.emit('open::'+ data.id); - - // Output the connection progress - ++connections; - break; - - case 'close': - delete ids[data.id]; - - metrics.close(data); - break; - - case 'error': - delete ids[data.id]; - - metrics.error(data); - break; - - case 'message': - received++; - metrics.message(data); - } - - } - - // - // Check if we have processed all connections so we can quit cleanly. - // - if (!Object.keys(ids).length) process.exit(); - }); - - // Add our worker to our round robin queue so we can balance all our requests - // across the different workers that we spawned. - robin.push(worker); -}); - -// -// Output live, real-time stats. -// -function live() { - var frames = live.frames - , len = frames.length - , interval = cli.statInterval * 1000 - , i = 0; - - live.interval = setInterval(function tick() { - var active = Object.keys(concurrents).reduce(function (count, id) { - return count + (concurrents[id] || 0); - }, 0); - - process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ - 'Created '.white + connections.toString().green, - 'Active '.white + active.toString().green, - '@'.white + new Date().toLocaleString().green - ].join(', ')); - }, interval); -} - -/** - * Live frames. - * - * @type {Array} - * @api private - */ -live.frames = [ - ' \u001b[96m◜ \u001b[90m' - , ' \u001b[96m◠ \u001b[90m' - , ' \u001b[96m◝ \u001b[90m' - , ' \u001b[96m◞ \u001b[90m' - , ' \u001b[96m◡ \u001b[90m' - , ' \u001b[96m◟ \u001b[90m' -]; - -/** - * Stop the live stats from running. - * - * @api private - */ -live.stop = function stop() { - process.stdout.write('\u001b[2K'); - clearInterval(live.interval); -}; - -// -// Up our WebSocket socket connections. -// -[ - '' - , 'Thor: version: '+ cli._version - , '' - , 'God of Thunder, son of Odin and smasher of WebSockets!' - , '' - , 'Thou shall:' - , '- Spawn '+ forked_workers +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' - , '' -].forEach(function stdout(line) { - console.log(line); -}); - -// -// Metrics collection. -// -var metrics = new Metrics(cli.amount * cli.args.length); - -// Iterate over all the urls so we can target multiple locations at once, which -// is helpfull if you are testing multiple loadbalancer endpoints for example. -async.forEach(cli.args, function forEach(url, done) { - var i = cli.amount - , completed = 0 - , goOnTaskQueueWhenConcurrentLimited = (cli.concurrent && cli.concurrent < cli.amount); - - console.log('Connecting to %s', url); - - url = url.split('@@'); - var localaddr = url.length > 1 ? url[1] : null; - url = url[0]; - // - // Create a simple WebSocket connection generator. - // - var queue = async.queue(function working(id, fn) { - var worker = robin.shift(); - - // Register the id, so we can keep track of the connections that we still - // need to process. - ids[id] = 1; - - // Process the connections - worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id - , localaddr: localaddr - , pingInterval: cli.pingInterval - , serverEngine: cli.serverEngine - , nextTask: goOnTaskQueueWhenConcurrentLimited - , realtimeStat: cli.realtimeStat - , runtime: cli.runtime - }); - - // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections - if (goOnTaskQueueWhenConcurrentLimited) { - worker.once('open::'+ id, fn); - }; - - // Add the worker back at the end of the round robin queue. - robin.push(worker); - }, cli.concurrent || Infinity); - - // When all the events are processed successfully we should call.. back ;P - queue.drain = done; - - // Add all connections to the processing queue; - while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); -}, function established(err) { - metrics.established(); -}); - -// -// We are setup, everything is running -// -console.log(''); -live(); - -process.once('SIGINT', function end() { - robin.forEach(function nuke(worker) { - try { worker.send({ shutdown: true }); } - catch (e) {} - }); -}); - -process.once('exit', function summary() { - live.stop(); - metrics.established().stop().summary(); -}); +#!/usr/bin/env node +'use strict'; + +var Metrics = require('../metrics') + , colors = require('colors') + , async = require('async') + , path = require('path') + , os = require('os'); + +// +// Setup the Command-Line Interface. +// +var cli = require('commander'); + +cli.usage(['[options] urls', + ,' urls like' + ,' ws://host/path/?params http://host/socket.io/?transport=websocket' + ,' ws://host/path/?params@@192.168.102.33 http://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) + .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) + .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) + .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) + .option('-P, --protocol [protocol]', 'WebSocket protocol version, default 13', parseInt, 13) + .option('-B, --buffer [size]', 'size of the messages that are send, default 1024', parseInt, 1024) + .option('-W, --workers [cpus]', 'workers to be spawned, default cpus.length', parseInt, os.cpus().length) + .option('-G, --generator [file]', 'custom message generators') + .option('-M, --masked', 'send the messaged with a mask') + .option('-b, --binary', 'send binary messages instead of utf-8') + .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) + .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified') + .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') + .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) + .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) + .version(require('../package.json').version) + .parse(process.argv); + + +// +// Check if all required arguments are supplied, if we don't have a valid url we +// should bail out +// +if (!cli.args.length) return [ + '愚かな人類よ! You forgot to supply the urls. Type -h for help.' +].forEach(function stderr(line) { + console.error(line); +}); + +// +// By Odin's beard, unleash thunder! +// +var cluster = require('cluster') + , workers = cli.workers || 1 + , ids = Object.create(null) + , concurrents = Object.create(null) + , connections = 0 + , received = 0 + , robin = [] + , workers = Math.min(cli.amount * cli.args.length, workers) + , forked_workers = workers; + +cluster.setupMaster({ + exec: path.resolve(__dirname, '../mjolnir.js') + , silent: false + , args: [ + cli.generator + ? path.resolve(process.cwd(), cli.generator) + : path.resolve(__dirname, '../generator.js'), + cli.protocol, + !!cli.masked, + !!cli.binary, + cli.statInterval + ] +}); + +while (workers--) cluster.fork(); + +Object.keys(cluster.workers).forEach(function each(id) { + var worker = cluster.workers[id]; + + worker.on('message', function message(data) { + + var datas = []; + if (data.collection) { + datas = data.datas; + }else{ + datas = [data]; + } + for (var i = 0; i < datas.length; i++) { + var data = datas[i]; + if ('concurrent' in data) concurrents[data.id] = data.concurrent; + + switch (data.type) { + case 'open': + metrics.handshaken(data); + worker.emit('open::'+ data.id); + + // Output the connection progress + ++connections; + break; + + case 'close': + delete ids[data.id]; + + metrics.close(data); + break; + + case 'error': + delete ids[data.id]; + + metrics.error(data); + break; + + case 'message': + received++; + metrics.message(data); + } + + } + + // + // Check if we have processed all connections so we can quit cleanly. + // + if (!Object.keys(ids).length) process.exit(); + }); + + // Add our worker to our round robin queue so we can balance all our requests + // across the different workers that we spawned. + robin.push(worker); +}); + +// +// Output live, real-time stats. +// +function live() { + var frames = live.frames + , len = frames.length + , interval = cli.statInterval * 1000 + , i = 0; + + live.interval = setInterval(function tick() { + var active = Object.keys(concurrents).reduce(function (count, id) { + return count + (concurrents[id] || 0); + }, 0); + + process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ + 'Created '.white + connections.toString().green, + 'Active '.white + active.toString().green, + '@'.white + new Date().toLocaleString().green + ].join(', ')); + }, interval); +} + +/** + * Live frames. + * + * @type {Array} + * @api private + */ +live.frames = [ + ' \u001b[96m◜ \u001b[90m' + , ' \u001b[96m◠ \u001b[90m' + , ' \u001b[96m◝ \u001b[90m' + , ' \u001b[96m◞ \u001b[90m' + , ' \u001b[96m◡ \u001b[90m' + , ' \u001b[96m◟ \u001b[90m' +]; + +/** + * Stop the live stats from running. + * + * @api private + */ +live.stop = function stop() { + process.stdout.write('\u001b[2K'); + clearInterval(live.interval); +}; + +// +// Up our WebSocket socket connections. +// +[ + '' + , 'Thor: version: '+ cli._version + , '' + , 'God of Thunder, son of Odin and smasher of WebSockets!' + , '' + , 'Thou shall:' + , '- Spawn '+ forked_workers +' workers.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' + , '' + , 'The answers you seek shall be yours, once I claim what is mine.' + , '' +].forEach(function stdout(line) { + console.log(line); +}); + +// +// Metrics collection. +// +var metrics = new Metrics(cli.amount * cli.args.length); + +// Iterate over all the urls so we can target multiple locations at once, which +// is helpfull if you are testing multiple loadbalancer endpoints for example. +async.forEach(cli.args, function forEach(url, done) { + var i = cli.amount + , completed = 0 + , goOnTaskQueueWhenConcurrentLimited = (cli.concurrent && cli.concurrent < cli.amount); + + console.log('Connecting to %s', url); + + url = url.split('@@'); + var localaddr = url.length > 1 ? url[1] : null; + url = url[0]; + // + // Create a simple WebSocket connection generator. + // + var queue = async.queue(function working(id, fn) { + var worker = robin.shift(); + + // Register the id, so we can keep track of the connections that we still + // need to process. + ids[id] = 1; + + // Process the connections + worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id + , localaddr: localaddr + , pingInterval: cli.pingInterval + , serverEngine: cli.serverEngine + , nextTask: goOnTaskQueueWhenConcurrentLimited + , realtimeStat: cli.realtimeStat + , runtime: cli.runtime + }); + + // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections + if (goOnTaskQueueWhenConcurrentLimited) { + worker.once('open::'+ id, fn); + }; + + // Add the worker back at the end of the round robin queue. + robin.push(worker); + }, cli.concurrent || Infinity); + + // When all the events are processed successfully we should call.. back ;P + queue.drain = done; + + // Add all connections to the processing queue; + while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); +}, function established(err) { + metrics.established(); +}); + +// +// We are setup, everything is running +// +console.log(''); +live(); + +process.once('SIGINT', function end() { + robin.forEach(function nuke(worker) { + try { worker.send({ shutdown: true }); } + catch (e) {} + }); +}); + +process.once('exit', function summary() { + live.stop(); + metrics.established().stop().summary(); +}); From 4e4ef607cbf6d1eb150a868b75659eb708e2c0b6 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 6 Nov 2015 19:49:34 +0800 Subject: [PATCH 05/11] fix --- mjolnir.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mjolnir.js b/mjolnir.js index d102712..13f6b8f 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -88,13 +88,15 @@ process.on('message', function message(task) { if (task.pingInterval && task.pingInterval > 0) { pingInterval = setInterval(function ping(id, socket) { if(socket){ - if(task.serverEngine && task.serverEngine in ['socket.io','engine.io','netty-socketio']) { - socket.ping('2'); + if(task.serverEngine && -1 != ['socket.io','engine.io','netty-socketio'].indexOf(task.serverEngine)) { + socket.send('2'); }else{ socket.ping(); } }else{ + delete connections[task.id]; clearInterval(pingInterval); + checkConnectionLength(); } }, task.pingInterval * 1000, task.id, socket); } From 7f4fdf101842404229865345f37141927384140c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 6 Nov 2015 22:08:14 +0800 Subject: [PATCH 06/11] socketio add polling support --- bin/socketio | 218 +++++++++++++++++++++++++++------------------ bin/thor | 6 +- mjolnir.js | 3 +- worker-socketio.js | 212 ++++++++++++++++++++++++------------------- 4 files changed, 256 insertions(+), 183 deletions(-) diff --git a/bin/socketio b/bin/socketio index 0e06d3a..5831900 100644 --- a/bin/socketio +++ b/bin/socketio @@ -2,6 +2,7 @@ 'use strict'; var Metrics = require('../metrics') + , colors = require('colors') , async = require('async') , path = require('path') , os = require('os'); @@ -11,25 +12,34 @@ var Metrics = require('../metrics') // var cli = require('commander'); -cli.usage('[options] ws://localhost[@@vip]') - .option('-A, --amount ', 'the amount of persistent connections to generate', parseInt, 10000) - .option('-C, --concurrent [deprecated]', 'how many concurrent-connections per second', parseInt, 0) - .option('-M, --messages ', 'number of messages to be send per connection', parseInt, 0) - .option('-P, --protocol ', 'WebSocket protocol version', parseInt, 13) - .option('-B, --buffer ', 'size of the messages that are send', parseInt, 1024) - .option('-W, --workers ', 'workers to be spawned', parseInt, os.cpus().length) +cli.usage(['[options] urls', + ,' urls like' + ,' http://host/socket.io/?transport=websocket' + ,' http://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) + .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) + .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) + .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) + .option('-P, --protocol [protocol]', 'WebSocket protocol version, default 13', parseInt, 13) + .option('-B, --buffer [size]', 'size of the messages that are send, default 1024', parseInt, 1024) + .option('-W, --workers [cpus]', 'workers to be spawned, default cpus.length', parseInt, os.cpus().length) + .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') - .option('-T, --runtime ', 'timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c', parseInt, -1) + .option('--TP, --transport [transport]', '"polling"/"websocket" must be specified', null, 'websocket') + .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) + .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') + .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) + .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) .version(require('../package.json').version) .parse(process.argv); + // // Check if all required arguments are supplied, if we don't have a valid url we // should bail out // if (!cli.args.length) return [ - '愚かな人類よ! You forgot to supply the urls.' + '愚かな人類よ! You forgot to supply the urls. Type -h for help.' ].forEach(function stderr(line) { console.error(line); }); @@ -38,27 +48,30 @@ if (!cli.args.length) return [ // By Odin's beard, unleash thunder! // var cluster = require('cluster') - , worker_num = cli.workers || 1 - , worker_num = worker_num > cli.amount * cli.args.length ? cli.amount * cli.args.length : worker_num - , forked_worker_num = worker_num - , connect_ids = Object.create(null) - , connections = 0 // connected connections number of all worker - , worker_connections = {} // connected connections number of each worker - , received = 0 // total message received from server in all workers - , robin = []; // array store workers + , workers = cli.workers || 1 + , ids = Object.create(null) + , concurrents = Object.create(null) + , connections = 0 + , received = 0 + , robin = [] + , workers = Math.min(cli.amount * cli.args.length, workers) + , forked_workers = workers; cluster.setupMaster({ exec: path.resolve(__dirname, '../worker-socketio.js') , silent: false , args: [ - false, + cli.generator + ? path.resolve(process.cwd(), cli.generator) + : path.resolve(__dirname, '../generator.js'), cli.protocol, !!cli.masked, - !!cli.binary + !!cli.binary, + cli.statInterval ] }); -while (worker_num--) cluster.fork(); +while (workers--) cluster.fork(); Object.keys(cluster.workers).forEach(function each(id) { var worker = cluster.workers[id]; @@ -73,61 +86,32 @@ Object.keys(cluster.workers).forEach(function each(id) { } for (var i = 0; i < datas.length; i++) { var data = datas[i]; + if ('concurrent' in data) concurrents[data.id] = data.concurrent; switch (data.type) { - case 'connected': - worker_connections[data.worker_id] = data.connected; - var all_connected = 0, - all_workers = 0; - Object.keys(worker_connections).forEach(function each(id) { - all_workers++; - all_connected += worker_connections[id]; - console.log('[Master] %s connected, msg received %s in workers %s', worker_connections[id], data.msg_received, id); - }); - // Output the connection progress - console.log('[Master] %s connected, in %s workers' + "\n", all_connected, all_workers); - break; - - case 'opened': - worker.emit('opened::'+ data.id); - - // Output the connection progress - if (++connections % 100 === 0) { - console.log(' Opened %s connections', connections); - } - - break; - case 'open': metrics.handshaken(data); - // worker.emit('open::'+ data.id); + worker.emit('open::'+ data.id); // Output the connection progress - /*if (++connections % 1000 === 0) { - console.log(' Opened %s connections', connections); - }*/ - + ++connections; break; case 'close': - delete connect_ids[data.id]; + delete ids[data.id]; metrics.close(data); - // worker.emit('close::'+ data.id); break; case 'error': - delete connect_ids[data.id]; + delete ids[data.id]; metrics.error(data); - // worker.emit('error::'+ data.id); break; case 'message': received++; metrics.message(data); - // worker.emit('message::'+ data.id); - } } @@ -135,7 +119,7 @@ Object.keys(cluster.workers).forEach(function each(id) { // // Check if we have processed all connections so we can quit cleanly. // - if (!Object.keys(connect_ids).length) process.exit(); + if (!Object.keys(ids).length) process.exit(); }); // Add our worker to our round robin queue so we can balance all our requests @@ -143,6 +127,73 @@ Object.keys(cluster.workers).forEach(function each(id) { robin.push(worker); }); +// +// Output live, real-time stats. +// +function live() { + var frames = live.frames + , len = frames.length + , interval = cli.statInterval * 1000 + , i = 0; + + live.interval = setInterval(function tick() { + var active = Object.keys(concurrents).reduce(function (count, id) { + return count + (concurrents[id] || 0); + }, 0); + + process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ + 'Created '.white + connections.toString().green, + 'Active '.white + active.toString().green, + '@'.white + new Date().toLocaleString().green + ].join(', ')); + }, interval); +} + +/** + * Live frames. + * + * @type {Array} + * @api private + */ +live.frames = [ + ' \u001b[96m◜ \u001b[90m' + , ' \u001b[96m◠ \u001b[90m' + , ' \u001b[96m◝ \u001b[90m' + , ' \u001b[96m◞ \u001b[90m' + , ' \u001b[96m◡ \u001b[90m' + , ' \u001b[96m◟ \u001b[90m' +]; + +/** + * Stop the live stats from running. + * + * @api private + */ +live.stop = function stop() { + process.stdout.write('\u001b[2K'); + clearInterval(live.interval); +}; + +// +// Up our WebSocket socket connections. +// +[ + '' + , 'Thor: version: '+ cli._version + , '' + , 'God of Thunder, son of Odin and smasher of WebSockets!' + , '' + , 'Thou shall:' + , '- Spawn '+ forked_workers +' workers.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' + , '' + , 'The answers you seek shall be yours, once I claim what is mine.' + , '' +].forEach(function stdout(line) { + console.log(line); +}); + // // Metrics collection. // @@ -152,7 +203,8 @@ var metrics = new Metrics(cli.amount * cli.args.length); // is helpfull if you are testing multiple loadbalancer endpoints for example. async.forEach(cli.args, function forEach(url, done) { var i = cli.amount - , completed = 0; + , completed = 0 + , goOnTaskQueueWhenConcurrentLimited = (cli.concurrent && cli.concurrent < cli.amount); console.log('Connecting to %s', url); @@ -162,20 +214,26 @@ async.forEach(cli.args, function forEach(url, done) { // // Create a simple WebSocket connection generator. // - var queue = async.queue(function working(connect_id, fn) { + var queue = async.queue(function working(id, fn) { var worker = robin.shift(); // Register the id, so we can keep track of the connections that we still // need to process. - connect_ids[connect_id] = 1; + ids[id] = 1; // Process the connections - worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: connect_id, runtime: cli.runtime, localaddr: localaddr, send_opened: (cli.concurrent && cli.concurrent < cli.amount) }); + worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id + , localaddr: localaddr + , pingInterval: cli.pingInterval + , serverEngine: cli.serverEngine + , nextTask: goOnTaskQueueWhenConcurrentLimited + , realtimeStat: cli.realtimeStat + , runtime: cli.runtime + }); // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections - // we have to set this event to roll process to next connect - if (cli.concurrent && cli.concurrent < cli.amount) { - worker.once('opened::'+ connect_id, fn); + if (goOnTaskQueueWhenConcurrentLimited) { + worker.once('open::'+ id, fn); }; // Add the worker back at the end of the round robin queue. @@ -185,44 +243,26 @@ async.forEach(cli.args, function forEach(url, done) { // When all the events are processed successfully we should call.. back ;P queue.drain = done; - // Add all connections to the processing queue; //.push(connect_id) + // Add all connections to the processing queue; while (i--) queue.push(url + (localaddr ? '::'+localaddr : '') +'::'+ i); }, function established(err) { metrics.established(); }); +// +// We are setup, everything is running +// console.log(''); +live(); -// ctrl + c -process.on('SIGINT', function end() { +process.once('SIGINT', function end() { robin.forEach(function nuke(worker) { try { worker.send({ shutdown: true }); } catch (e) {} - }) -}); - -process.on('exit', function summary() { - console.log('-------------------------------------'); - -// -// Up our WebSocket socket connections. -// -[ - '' - , 'Thor: version: '+ cli._version - , '' - , 'God of Thunder, son of Odin and smasher of WebSockets!' - , '' - , 'Thou shall:' - , '- Spawn '+ forked_worker_num +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' - , '' -].forEach(function stdout(line) { - console.log(line); + }); }); +process.once('exit', function summary() { + live.stop(); metrics.established().stop().summary(); }); diff --git a/bin/thor b/bin/thor index cecfa2d..5b69555 100755 --- a/bin/thor +++ b/bin/thor @@ -14,8 +14,8 @@ var cli = require('commander'); cli.usage(['[options] urls', ,' urls like' - ,' ws://host/path/?params http://host/socket.io/?transport=websocket' - ,' ws://host/path/?params@@192.168.102.33 http://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) + ,' ws://host/path/?params ws://host/socket.io/?transport=websocket' + ,' ws://host/path/?params@@192.168.102.33 ws://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) @@ -25,8 +25,8 @@ cli.usage(['[options] urls', .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') - .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified') + .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) diff --git a/mjolnir.js b/mjolnir.js index 13f6b8f..30ecc83 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -67,7 +67,7 @@ process.on('message', function message(task) { // if (task.shutdown) { Object.keys(connections).forEach(function shutdown(id) { - connections[id].close(); + connections[id] && connections[id].close(); }); } @@ -138,6 +138,7 @@ process.on('message', function message(task) { socket.close(); delete connections[task.id]; + checkConnectionLength(); }); // Adding a new socket to our socket collection. diff --git a/worker-socketio.js b/worker-socketio.js index efbc65e..cf5894c 100644 --- a/worker-socketio.js +++ b/worker-socketio.js @@ -2,133 +2,158 @@ var Socket = require('socket.io-client') , connections = {} - , connected = 0 - , msg_received = 0; - -// 收集后一次性send给master -var metrics_datas = {collection:true, datas:[]}; - -var sendToMaster = setInterval(function () { - process.send({ type: 'connected', connected: connected, msg_received: msg_received, worker_id: process.pid }); - }, 60000) - , checkConnectionLength = function(){ - if (Object.keys(connections).length <= 0) { - // 一次性发送 + , concurrent = 0; + +// +// Get the session document that is used to generate the data. +// +var session = require(process.argv[2]); + +// +// WebSocket connection details. +// +var masked = process.argv[4] === 'true' + , binary = process.argv[5] === 'true' + , protocol = +process.argv[3] || 13; + +// collect metics datas +var metrics_datas = {collection:true, datas:[]} + , process_send = function(data, task) { + if (task.realtimeStat || ('open' == data.type && task.nextTask)) { + process.send(data); + }else{ + metrics_datas.datas.push(data); + } + } + , process_sendAll = function(end) { + if (metrics_datas.datas.length <= 0) { + return; + }; + // send all data to parent process.send(metrics_datas, null, function clearDatas(err){ // invoked after the message is sent but before the target may have received it - if (err) {return;}; + if (err) {return;} + // WARNING: maybe we should use synchronize method here metrics_datas.datas = []; + if (end) { + process.exit(); + }; }); - }; - }; + } + , checkConnectionLength = function(){ + if (Object.keys(connections).length <= 0) { + process_sendAll(true); + } + } + , statInterval = +process.argv[6] || 60 + , workerStatInterval = setInterval(function () { + process_sendAll(); + }, statInterval * 1000); process.on('message', function message(task) { var now = Date.now(); - + + // + // Write a new message to the socket. The message should have a size of x + // + if ('write' in task) { + Object.keys(connections).forEach(function write(id) { + write(connections[id], task, id); + }); + } + // // Shut down every single socket. // if (task.shutdown) { - console.log('shutdown', process.pid); Object.keys(connections).forEach(function shutdown(id) { connections[id] && connections[id].disconnect(); }); - - setInterval(function(){ - if (connected <= 0) { - clearInterval(sendToMaster); - // 一次性发送 - process.send(metrics_datas, null, function(err){ - metrics_datas.datas = []; - process.exit(); - }); - }; - }, 30000); } // End of the line, we are gonna start generating new connections. if (!task.url) return; - var sock_opts = { - forceNew:true, - transports:['websocket'] - }; - - if (task.localaddr) { - sock_opts.localAddress = task.localaddr; - }; - var socket = new Socket(task.url, sock_opts); + var socket = new Socket(task.url, { + 'force new connection': true, + reconnection: false, + timeout: 5000, + transports: ['polling'], + protocolVersion: protocol, + localAddress: task.localaddr || null + }); socket.last = Date.now(); + var pingInterval = null; socket.on('connect', function open() { - connected ++; - // console.info(task.id + " opened", connections); - var send_data = { type: 'open', duration: Date.now() - now, id: task.id }; - // process.send(send_data); - metrics_datas.datas.push(send_data); + process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }, task); // write(socket, task, task.id); - // - if (task.send_opened) { - process.send(send_data); - }; - + // As the `close` event is fired after the internal `_socket` is cleaned up // we need to do some hacky shit in order to tack the bytes send. }); - function message(data) { - var send_data = { - type: 'message', latency: Date.now() - socket.last, + socket.on('message', function message(data) { + process_send({ + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, id: task.id - }; - // process.send(send_data); - metrics_datas.datas.push(send_data); + }, task); - msg_received++; - // console.log('['+task.id.substr(task.id.indexOf('::'))+']socket on message@'+socket.last, "\n", data, "\n"); // Only write as long as we are allowed to send messages - if (--task.messages && task.messages > 0) { + if (task.messages > 0) + if (--task.messages) { write(socket, task, task.id); } else { - // socket.disconnect(); + socket.disconnect(); + socket.emit('disconnect'); } - }; - socket.on('message', message); - socket.on('onMessage', message); + }); + socket.on('onMessage', function onMessage(data) { + process_send({ + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, + id: task.id + }, task); + + // Only write as long as we are allowed to send messages + if (task.messages > 0) + if (--task.messages) { + write(socket, task, task.id); + } else { + socket.disconnect(); + socket.emit('disconnect'); + } + }); socket.on('disconnect', function close() { - connected--; var internal = {}; try{ - internal = socket.io.engine.transport.ws._socket || {}; + internal = socket.io.engine.transport.ws._socket; }catch(e){ - internal = {}; + // console.log(socket.io.engine.transport.pollXhr); } - // console.info('['+task.id+']socket on close'); - var send_data = { - type: 'close', id: task.id, + process_send({ + type: 'close', id: task.id, concurrent: --concurrent, read: internal.bytesRead || 0, send: internal.bytesWritten || 0 - }; - // process.send(send_data); - metrics_datas.datas.push(send_data); + }, task); delete connections[task.id]; - // console.log('close ', Object.keys(connections).length); - if (Object.keys(connections) <= 0) { - clearInterval(sendToMaster); - // 一次性发送 - process.send(metrics_datas); - metrics_datas.datas = []; - }; + clearInterval(pingInterval); + checkConnectionLength(); }); socket.on('error', function error(err) { - console.error('['+task.id+']socket on error-------', "\n", err, "\n", '-------error'); - var send_data = { type: 'error', message: err.message, id: task.id }; - // process.send(send_data); - metrics_datas.datas.push(send_data); + process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent }, task); + + socket.disconnect(); + socket.emit('disconnect'); + delete connections[task.id]; + }); + + // catch ECONNREFUSED + socket.io.on('connect_error', function(err){ + process_send({ type: 'error', message: err.description.message, id: task.id, concurrent: --concurrent }, task); socket.disconnect(); socket.emit('disconnect'); @@ -136,19 +161,20 @@ process.on('message', function message(task) { }); // Adding a new socket to our socket collection. + ++concurrent; connections[task.id] = socket; // timeout to close socket if (task.runtime && task.runtime > 0) { setTimeout(function timeoutToCloseSocket(id, socket) { - // console.log('timeout to close socket:'+id); socket.disconnect(); + socket.emit('disconnect'); }, task.runtime * 1000, task.id, socket); } }); -process.on('SIGINT', function () { -}); +process.on('SIGINT', function () {}); +process.on('exit', function () {}); /** * Helper function from writing messages to the socket. @@ -157,21 +183,27 @@ process.on('SIGINT', function () { * @param {Object} task The given task * @param {String} id * @param {Function} fn The callback + * @param {String} data * @api private */ -function write(socket, task, id, fn) { - var start = socket.last = Date.now(); - socket.send(task.size, function (err) { +function write(socket, task, id, fn, data) { + // i thank the generator doesn't make any sense, but just let me do some change and leave it alone + session[binary ? 'binary' : 'utf8'](data || task.size, function message(err, data) { + var start = socket.last = Date.now(); + + socket.send(data, { + binary: binary, + mask: masked + }, function sending(err) { if (err) { - var send_data = { type: 'error', message: err.message }; - // process.send(send_data); - metrics_datas.datas.push(send_data); + process_send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }, task); socket.disconnect(); + socket.emit('disconnect'); delete connections[id]; } if (fn) fn(err); }); - + }); } From 798234728ae8d2eb5af19a2c1a1771dd00806a64 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 6 Nov 2015 22:12:54 +0800 Subject: [PATCH 07/11] fix --- bin/socketio | 4 ++-- bin/thor | 2 +- worker-socketio.js | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bin/socketio b/bin/socketio index 5831900..91e4cd1 100644 --- a/bin/socketio +++ b/bin/socketio @@ -25,7 +25,7 @@ cli.usage(['[options] urls', .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') - .option('--TP, --transport [transport]', '"polling"/"websocket" must be specified', null, 'websocket') + .option('--TP, --transport [transport]', '"polling"/"websocket" must be specified', 'websocket') .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) @@ -224,8 +224,8 @@ async.forEach(cli.args, function forEach(url, done) { // Process the connections worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id , localaddr: localaddr + , transport: cli.transport , pingInterval: cli.pingInterval - , serverEngine: cli.serverEngine , nextTask: goOnTaskQueueWhenConcurrentLimited , realtimeStat: cli.realtimeStat , runtime: cli.runtime diff --git a/bin/thor b/bin/thor index 5b69555..b86aefe 100755 --- a/bin/thor +++ b/bin/thor @@ -224,8 +224,8 @@ async.forEach(cli.args, function forEach(url, done) { // Process the connections worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id , localaddr: localaddr - , pingInterval: cli.pingInterval , serverEngine: cli.serverEngine + , pingInterval: cli.pingInterval , nextTask: goOnTaskQueueWhenConcurrentLimited , realtimeStat: cli.realtimeStat , runtime: cli.runtime diff --git a/worker-socketio.js b/worker-socketio.js index cf5894c..f50b287 100644 --- a/worker-socketio.js +++ b/worker-socketio.js @@ -73,12 +73,11 @@ process.on('message', function message(task) { // End of the line, we are gonna start generating new connections. if (!task.url) return; - var socket = new Socket(task.url, { 'force new connection': true, reconnection: false, timeout: 5000, - transports: ['polling'], + transports: [task.transport], protocolVersion: protocol, localAddress: task.localaddr || null }); @@ -153,7 +152,7 @@ process.on('message', function message(task) { // catch ECONNREFUSED socket.io.on('connect_error', function(err){ - process_send({ type: 'error', message: err.description.message, id: task.id, concurrent: --concurrent }, task); + process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent }, task); socket.disconnect(); socket.emit('disconnect'); From 8275a3d836cd6a63b4f6c5a2db3ae1fcf52ba236 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Nov 2015 15:27:08 +0800 Subject: [PATCH 08/11] update stat interval --- bin/socketio | 2 -- bin/thor | 2 -- mjolnir.js | 4 ++-- worker-socketio.js | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/bin/socketio b/bin/socketio index 91e4cd1..8d4f58c 100644 --- a/bin/socketio +++ b/bin/socketio @@ -27,7 +27,6 @@ cli.usage(['[options] urls', .option('-b, --binary', 'send binary messages instead of utf-8') .option('--TP, --transport [transport]', '"polling"/"websocket" must be specified', 'websocket') .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) - .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) .version(require('../package.json').version) @@ -227,7 +226,6 @@ async.forEach(cli.args, function forEach(url, done) { , transport: cli.transport , pingInterval: cli.pingInterval , nextTask: goOnTaskQueueWhenConcurrentLimited - , realtimeStat: cli.realtimeStat , runtime: cli.runtime }); diff --git a/bin/thor b/bin/thor index b86aefe..0bf5ada 100755 --- a/bin/thor +++ b/bin/thor @@ -27,7 +27,6 @@ cli.usage(['[options] urls', .option('-b, --binary', 'send binary messages instead of utf-8') .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified') .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) - .option('--RS, --realtimeStat', 'worker will send stat info to master in realtime') .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) .version(require('../package.json').version) @@ -227,7 +226,6 @@ async.forEach(cli.args, function forEach(url, done) { , serverEngine: cli.serverEngine , pingInterval: cli.pingInterval , nextTask: goOnTaskQueueWhenConcurrentLimited - , realtimeStat: cli.realtimeStat , runtime: cli.runtime }); diff --git a/mjolnir.js b/mjolnir.js index 30ecc83..f14581f 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -18,8 +18,9 @@ var masked = process.argv[4] === 'true' // collect metics datas var metrics_datas = {collection:true, datas:[]} + , statInterval = +process.argv[6] || 60 , process_send = function(data, task) { - if (task.realtimeStat || ('open' == data.type && task.nextTask)) { + if (statInterval <= 0 || ('open' == data.type && task.nextTask)) { process.send(data); }else{ metrics_datas.datas.push(data); @@ -45,7 +46,6 @@ var metrics_datas = {collection:true, datas:[]} process_sendAll(true); } } - , statInterval = +process.argv[6] || 60 , workerStatInterval = setInterval(function () { process_sendAll(); }, statInterval * 1000); diff --git a/worker-socketio.js b/worker-socketio.js index f50b287..62546bf 100644 --- a/worker-socketio.js +++ b/worker-socketio.js @@ -18,8 +18,9 @@ var masked = process.argv[4] === 'true' // collect metics datas var metrics_datas = {collection:true, datas:[]} + , statInterval = +process.argv[6] || 60 , process_send = function(data, task) { - if (task.realtimeStat || ('open' == data.type && task.nextTask)) { + if (statInterval <= 0 || ('open' == data.type && task.nextTask)) { process.send(data); }else{ metrics_datas.datas.push(data); @@ -45,7 +46,6 @@ var metrics_datas = {collection:true, datas:[]} process_sendAll(true); } } - , statInterval = +process.argv[6] || 60 , workerStatInterval = setInterval(function () { process_sendAll(); }, statInterval * 1000); From 350165cbb60542916da57ee877dd116c64c21216 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Nov 2015 18:36:47 +0800 Subject: [PATCH 09/11] update 1.1.2 --- CHANGELOG.md | 4 ++ README.md | 150 ++++++++++++++++++++++++++++++--------------- bin/socketio | 15 +++-- bin/thor | 15 +++-- mjolnir.js | 29 +++++++-- package.json | 2 +- worker-socketio.js | 19 +++--- 7 files changed, 154 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abcc934..382727e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.1.2 + +make things better + ### 1.0.1 Add socket.io support diff --git a/README.md b/README.md index 5032d8e..d692608 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The `-g` command flag tells `npm` to install the module globally on your system. ``` thor [options] -socketio [options] [@@vip] [[@@vip]...] +socketio [options] ``` Thor can hit multiple URL's at once; this is useful if you are testing your @@ -39,7 +39,7 @@ thor --amount 5000 ws://localhost:8080 wss://localhost:8081 ``` or use http/https if and only if ur using socket.io(nodejs/java/etc) in server ``` -socketio --amount 5000 http://localhost:8080 https://localhost:8081 +socketio --amount 5000 http://localhost:8080/ https://localhost:8081/ ``` The snippet above will open up `5000` connections against the regular @@ -57,21 +57,29 @@ which is limited by TCP/IP protocal. U can special the [@@one_of_the_machine_vip #### thor Options ``` - Usage: thor [options] ws://localhost + Usage: thor [options] urls + + urls like + ws://localhost:8080/?params + ws://localhost:8080/?params ws://localhost:8080/socket.io/?transport=websocket + ws://localhost:8080/?params@@192.168.102.33 ws://localhost:8080/socket.io/?transport=websocket@@192.168.102.53 Options: -h, --help output usage information - -A, --amount the amount of persistent connections to generate - -C, --concurrent how many concurrent-connections per second - -M, --messages number of messages to be send per connection - -P, --protocol WebSocket protocol version - -B, --buffer size of the messages that are send - -W, --workers workers to be spawned - -G, --generator custom message generators + -A, --amount the amount of persistent connections to generate, default 10000 + -C, --concurrent [connections] [deprecated]how many concurrent-connections per second, default 0 + -M, --messages [messages] number of messages to be send per connection, default 0 + -P, --protocol [protocol] WebSocket protocol version, default 13 + -B, --buffer [size] size of the messages that are send, default 1024 + -W, --workers [cpus] workers to be spawned, default cpus.length + -G, --generator [file] custom message generators -M, --masked send the messaged with a mask -b, --binary send binary messages instead of utf-8 - -T, --runtime timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c + --SE, --serverEngine [engine] "socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified if ur using these engine in ur server + --PI, --pingInterval [seconds] seconds for doing ping to keep-alive, default 50 + --SI, --statInterval [seconds] show stat info interval, default 60 + --RT, --runtime [seconds] timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c -V, --version output the version number ``` @@ -87,21 +95,30 @@ Some small notes about the options: #### socketio Options ``` - Usage: socketio [options] http[s]://localhost[@@vip] + Usage: socketio [options] urls + + urls like + http://localhost:8080/ + http://localhost:8080/?params + http://localhost:8080/?params@@192.168.102.53 Options: - -h, --help output usage information - -A, --amount the amount of persistent connections to generate - -C, --concurrent [deprecated] how many concurrent-connections per second - -M, --messages number of messages to be send per connection - -P, --protocol WebSocket protocol version - -B, --buffer size of the messages that are send - -W, --workers workers to be spawned - -M, --masked send the messaged with a mask - -b, --binary send binary messages instead of utf-8 - -T, --runtime timeout to close socket(seconds), default to unlimited and u must stop by ctrl+c - -V, --version output the version number + -h, --help output usage information + -A, --amount the amount of persistent connections to generate, default 10000 + -C, --concurrent [connections] [deprecated]how many concurrent-connections per second, default 0 + -M, --messages [messages] number of messages to be send per connection, default 0 + -P, --protocol [protocol] WebSocket protocol version, default 13 + -B, --buffer [size] size of the messages that are send, default 1024 + -W, --workers [cpus] workers to be spawned, default cpus.length + -G, --generator [file] custom message generators + -M, --masked send the messaged with a mask + -b, --binary send binary messages instead of utf-8 + --TP, --transport [transport] "polling"/"websocket" default websocket + --PI, --pingInterval [seconds] seconds for doing ping to keep-alive, default 50 + --SI, --statInterval [seconds] show stat info interval, default 60 + --RT, --runtime [seconds] timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c + -V, --version output the version number ``` @@ -122,7 +139,7 @@ thor --amount 1000 --generator ws://localhost:8080 ### Example ``` -thor --amount 1000 --messages 100 ws://localhost:8080 +thor --amount 5000 ws://localhost:8080 ``` This will hit the WebSocket server that runs on localhost:8080 with 1000 @@ -130,50 +147,83 @@ connections and sends 100 messages over each established connection. Once `thor` is done with smashing your connections it will generate a detailed report: ``` -Thor: version: 1.0.0 +Thor: version: 1.1.2 God of Thunder, son of Odin and smasher of WebSockets! Thou shall: - Spawn 4 workers. -- Create all the concurrent/parallel connections. -- Smash 1000 connections with the mighty Mjölnir. - -The answers you seek shall be yours, once I claim what is mine. +- Create all the concurrent connections. +- Smash 5000 connections . Connecting to ws://localhost:8080 - Opened 100 connections - Opened 200 connections - Opened 300 connections - Opened 400 connections - Opened 500 connections - Opened 600 connections - Opened 700 connections - Opened 800 connections - Opened 900 connections - Opened 1000 connections - - -Online 15000 milliseconds -Time taken 31775 milliseconds -Connected 1000 + ◜ Progress :: Created 0, Active 0, @2015-11-11 17:31:15 ==> this will hide after process end + +Online 96728 milliseconds +Time taken 96728 milliseconds +Connected 2211 +Disconnected 0 +Failed 2789 +Total transferred 658.86kB +Total received 631.64kB + +Durations (ms): + + min mean stddev median max +Handshaking 171 3354 1631 2915 7737 +Latency NaN NaN NaN NaN NaN + +Percentile (ms): + + 50% 66% 75% 80% 90% 95% 98% 98% 100% +Handshaking 2915 3627 4705 4827 5811 6412 7686 7712 7737 +Latency NaN NaN NaN NaN NaN NaN NaN NaN NaN + +Received errors: + +2789x undefined +``` + +### Example + +``` +socketio --amount 5000 --RT 300 --SI 30 http://localhost:8080/ +``` + +``` +Thor: version: 1.1.2 + +God of Thunder, son of Odin and smasher of WebSockets! + +Thou shall: +- Spawn 4 workers. +- Create all the concurrent connections. +- Smash 5 connections. + +Connecting to http://localhost:8080/ + + + +Online 137539 milliseconds +Time taken 137539 milliseconds +Connected 5000 Disconnected 0 Failed 0 -Total transferred 120.46MB -Total received 120.43MB +Total transferred 14.59kB +Total received 14.68kB Durations (ms): min mean stddev median max -Handshaking 217 5036 4094 3902 14451 -Latency 0 215 104 205 701 +Handshaking 598 614 13 610 638 +Latency NaN NaN NaN NaN NaN Percentile (ms): 50% 66% 75% 80% 90% 95% 98% 98% 100% -Handshaking 3902 6425 8273 9141 11409 12904 13382 13945 14451 -Latency 205 246 266 288 371 413 437 443 701 +Handshaking 610 617 617 638 638 638 638 638 638 +Latency NaN NaN NaN NaN NaN NaN NaN NaN NaN ``` ### License diff --git a/bin/socketio b/bin/socketio index 8d4f58c..56e3944 100644 --- a/bin/socketio +++ b/bin/socketio @@ -14,8 +14,9 @@ var cli = require('commander'); cli.usage(['[options] urls', ,' urls like' - ,' http://host/socket.io/?transport=websocket' - ,' http://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) + ,' http://localhost:8080/' + ,' http://localhost:8080/?params' + ,' http://localhost:8080/?params@@192.168.102.53'].join("\n")) .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) @@ -25,7 +26,7 @@ cli.usage(['[options] urls', .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') - .option('--TP, --transport [transport]', '"polling"/"websocket" must be specified', 'websocket') + .option('--TP, --transport [transport]', '"polling"/"websocket" default websocket', 'websocket') .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) @@ -85,7 +86,7 @@ Object.keys(cluster.workers).forEach(function each(id) { } for (var i = 0; i < datas.length; i++) { var data = datas[i]; - if ('concurrent' in data) concurrents[data.id] = data.concurrent; + if ('concurrent' in data) concurrents[data.workerid] = data.concurrent; switch (data.type) { case 'open': @@ -184,10 +185,8 @@ live.stop = function stop() { , '' , 'Thou shall:' , '- Spawn '+ forked_workers +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections.' , '' ].forEach(function stdout(line) { console.log(line); diff --git a/bin/thor b/bin/thor index 0bf5ada..3517991 100755 --- a/bin/thor +++ b/bin/thor @@ -14,8 +14,9 @@ var cli = require('commander'); cli.usage(['[options] urls', ,' urls like' - ,' ws://host/path/?params ws://host/socket.io/?transport=websocket' - ,' ws://host/path/?params@@192.168.102.33 ws://host/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) + ,' ws://localhost:8080/?params' + ,' ws://localhost:8080/?params ws://localhost:8080/socket.io/?transport=websocket' + ,' ws://localhost:8080/?params@@192.168.102.33 ws://localhost:8080/socket.io/?transport=websocket@@192.168.102.53'].join("\n")) .option('-A, --amount ', 'the amount of persistent connections to generate, default 10000', parseInt, 10000) .option('-C, --concurrent [connections]', '[deprecated]how many concurrent-connections per second, default 0', parseInt, 0) .option('-M, --messages [messages]', 'number of messages to be send per connection, default 0', parseInt, 0) @@ -25,7 +26,7 @@ cli.usage(['[options] urls', .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') - .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified') + .option('--SE, --serverEngine [engine]', '"socket.io"/"engine.io"(nodejs), "netty-socketio"(java) must be specified if ur using these engine in ur server') .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) .option('--RT, --runtime [seconds]', 'timeout to close socket(seconds), default to unlimited, u must stop by ctrl+c', parseInt, -1) @@ -85,7 +86,7 @@ Object.keys(cluster.workers).forEach(function each(id) { } for (var i = 0; i < datas.length; i++) { var data = datas[i]; - if ('concurrent' in data) concurrents[data.id] = data.concurrent; + if ('concurrent' in data) concurrents[data.workerid] = data.concurrent; switch (data.type) { case 'open': @@ -184,10 +185,8 @@ live.stop = function stop() { , '' , 'Thou shall:' , '- Spawn '+ forked_workers +' workers.' - , '- Create '+ (cli.concurrent || 'all the') + ' concurrent/parallel connections.' - , '- Smash '+ (cli.amount || 'infinite') +' connections with the mighty Mjölnir.' - , '' - , 'The answers you seek shall be yours, once I claim what is mine.' + , '- Create '+ (cli.concurrent || 'all the') + ' concurrent connections.' + , '- Smash '+ (cli.amount || 'infinite') +' connections.' , '' ].forEach(function stdout(line) { console.log(line); diff --git a/mjolnir.js b/mjolnir.js index f14581f..c282c2f 100644 --- a/mjolnir.js +++ b/mjolnir.js @@ -82,7 +82,7 @@ process.on('message', function message(task) { var pingInterval = null; socket.on('open', function open() { - process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }, task); + process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent, workerid: process.pid }, task); // write(socket, task, task.id); if (task.pingInterval && task.pingInterval > 0) { @@ -106,7 +106,7 @@ process.on('message', function message(task) { socket.on('message', function message(data) { process_send({ - type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, workerid: process.pid, id: task.id }, task); @@ -116,6 +116,22 @@ process.on('message', function message(task) { write(socket, task, task.id); } else { socket.close(); + socket.emit('close'); + } + }); + socket.on('onMessage', function onMessage(data) { + process_send({ + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, workerid: process.pid, + id: task.id + }, task); + + // Only write as long as we are allowed to send messages + if (task.messages > 0) + if (--task.messages) { + write(socket, task, task.id); + } else { + socket.close(); + socket.emit('close'); } }); @@ -123,7 +139,7 @@ process.on('message', function message(task) { var internal = socket._socket || {}; process_send({ - type: 'close', id: task.id, concurrent: --concurrent, + type: 'close', id: task.id, concurrent: --concurrent, workerid: process.pid, read: internal.bytesRead || 0, send: internal.bytesWritten || 0 }, task); @@ -134,9 +150,10 @@ process.on('message', function message(task) { }); socket.on('error', function error(err) { - process_send({ type: 'error', message: err.message, id: task.id, concurrent: --concurrent }, task); + process_send({ type: 'error', message: err.message, id: task.id, concurrent: --concurrent, workerid: process.pid }, task); socket.close(); + socket.emit('close'); delete connections[task.id]; checkConnectionLength(); }); @@ -149,6 +166,7 @@ process.on('message', function message(task) { if (task.runtime && task.runtime > 0) { setTimeout(function timeoutToCloseSocket(id, socket) { socket.close(); + socket.emit('close'); }, task.runtime * 1000, task.id, socket); } }); @@ -176,9 +194,10 @@ function write(socket, task, id, fn, data) { mask: masked }, function sending(err) { if (err) { - process_send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }, task); + process_send({ type: 'error', message: err.message, concurrent: --concurrent, workerid: process.pid, id: id }, task); socket.close(); + socket.emit('close'); delete connections[id]; } diff --git a/package.json b/package.json index 6ca7616..1581765 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "thor", - "version": "1.1.1", + "version": "1.1.2", "description": "Thor is WebSocket benchmark utility", "main": "index.js", "scripts": { diff --git a/worker-socketio.js b/worker-socketio.js index 62546bf..a7d3862 100644 --- a/worker-socketio.js +++ b/worker-socketio.js @@ -73,6 +73,7 @@ process.on('message', function message(task) { // End of the line, we are gonna start generating new connections. if (!task.url) return; + var socket = new Socket(task.url, { 'force new connection': true, reconnection: false, @@ -85,7 +86,7 @@ process.on('message', function message(task) { var pingInterval = null; socket.on('connect', function open() { - process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent }, task); + process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent, workerid: process.pid }, task); // write(socket, task, task.id); // As the `close` event is fired after the internal `_socket` is cleaned up @@ -94,7 +95,7 @@ process.on('message', function message(task) { socket.on('message', function message(data) { process_send({ - type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, workerid: process.pid, id: task.id }, task); @@ -109,7 +110,7 @@ process.on('message', function message(task) { }); socket.on('onMessage', function onMessage(data) { process_send({ - type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, + type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, workerid: process.pid, id: task.id }, task); @@ -126,13 +127,13 @@ process.on('message', function message(task) { socket.on('disconnect', function close() { var internal = {}; try{ - internal = socket.io.engine.transport.ws._socket; + internal = socket.io.engine.transport.ws._socket || {}; }catch(e){ // console.log(socket.io.engine.transport.pollXhr); } process_send({ - type: 'close', id: task.id, concurrent: --concurrent, + type: 'close', id: task.id, concurrent: --concurrent, workerid: process.pid, read: internal.bytesRead || 0, send: internal.bytesWritten || 0 }, task); @@ -143,20 +144,22 @@ process.on('message', function message(task) { }); socket.on('error', function error(err) { - process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent }, task); + process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent, workerid: process.pid }, task); socket.disconnect(); socket.emit('disconnect'); delete connections[task.id]; + checkConnectionLength(); }); // catch ECONNREFUSED socket.io.on('connect_error', function(err){ - process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent }, task); + process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent, workerid: process.pid }, task); socket.disconnect(); socket.emit('disconnect'); delete connections[task.id]; + checkConnectionLength(); }); // Adding a new socket to our socket collection. @@ -195,7 +198,7 @@ function write(socket, task, id, fn, data) { mask: masked }, function sending(err) { if (err) { - process_send({ type: 'error', message: err.message, concurrent: --concurrent, id: id }, task); + process_send({ type: 'error', message: err.message, concurrent: --concurrent, workerid: process.pid, id: id }, task); socket.disconnect(); socket.emit('disconnect'); From 6f438ef9094814418a4c72ff88a0d5237d996bb2 Mon Sep 17 00:00:00 2001 From: iorichina Date: Thu, 12 Nov 2015 16:31:03 +0800 Subject: [PATCH 10/11] change socketio mode +x --- bin/socketio | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bin/socketio diff --git a/bin/socketio b/bin/socketio old mode 100644 new mode 100755 From 9545ce29efc24eb58b7e17af027a4d18c072f604 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 29 Dec 2015 18:09:48 +0800 Subject: [PATCH 11/11] add log error, fix concurrent summary --- bin/socketio | 36 ++++++++++++++++++++++------ worker-socketio.js | 58 ++++++++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/bin/socketio b/bin/socketio index 56e3944..e36f537 100755 --- a/bin/socketio +++ b/bin/socketio @@ -26,6 +26,8 @@ cli.usage(['[options] urls', .option('-G, --generator [file]', 'custom message generators') .option('-M, --masked', 'send the messaged with a mask') .option('-b, --binary', 'send binary messages instead of utf-8') + .option('-t, --connectTimeout [connectTimeout]', 'default 5(s)', parseInt, 5) + .option('-l, --logError [logError]', 'default 0 means false, otherwise true', parseInt, 0) .option('--TP, --transport [transport]', '"polling"/"websocket" default websocket', 'websocket') .option('--PI, --pingInterval [seconds]', 'seconds for doing ping to keep-alive, default 50', parseInt, 50) .option('--SI, --statInterval [seconds]', 'show stat info interval, default 60', parseInt, 60) @@ -51,7 +53,10 @@ var cluster = require('cluster') , workers = cli.workers || 1 , ids = Object.create(null) , concurrents = Object.create(null) + , concurrent_timestamps = Object.create(null) , connections = 0 + , close_connections = 0 + , error_connections = 0 , received = 0 , robin = [] , workers = Math.min(cli.amount * cli.args.length, workers) @@ -67,7 +72,8 @@ cluster.setupMaster({ cli.protocol, !!cli.masked, !!cli.binary, - cli.statInterval + cli.statInterval, + cli.logError ] }); @@ -75,7 +81,11 @@ while (workers--) cluster.fork(); Object.keys(cluster.workers).forEach(function each(id) { var worker = cluster.workers[id]; - + /** + * message from worker via IPC + * @param {Array|Object} data) Array of Object or Object{id:connection_id, wid:worker_id, ...} + * @return null + */ worker.on('message', function message(data) { var datas = []; @@ -86,7 +96,11 @@ Object.keys(cluster.workers).forEach(function each(id) { } for (var i = 0; i < datas.length; i++) { var data = datas[i]; - if ('concurrent' in data) concurrents[data.workerid] = data.concurrent; + if ('concurrent' in data && 'cur_time' in data) { + if (!concurrent_timestamps[data.wid] || concurrent_timestamps[data.wid] < data.cur_time) { + concurrents[data.wid] = data.concurrent; + } + } switch (data.type) { case 'open': @@ -101,12 +115,14 @@ Object.keys(cluster.workers).forEach(function each(id) { delete ids[data.id]; metrics.close(data); + ++close_connections; break; case 'error': delete ids[data.id]; metrics.error(data); + ++error_connections; break; case 'message': @@ -141,9 +157,12 @@ function live() { return count + (concurrents[id] || 0); }, 0); - process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ + // process.stdout.write('\r'+ frames[i++ % len] +' Progress :: '.white + [ + console.log('\r'+ frames[i++ % len] +' Progress :: '.white + [ 'Created '.white + connections.toString().green, 'Active '.white + active.toString().green, + 'Error '.white + error_connections.toString().green, + 'Received '.white + received.toString().green, '@'.white + new Date().toLocaleString().green ].join(', ')); }, interval); @@ -170,7 +189,8 @@ live.frames = [ * @api private */ live.stop = function stop() { - process.stdout.write('\u001b[2K'); + // process.stdout.write('\u001b[2K'); + console.log(''); clearInterval(live.interval); }; @@ -222,10 +242,11 @@ async.forEach(cli.args, function forEach(url, done) { // Process the connections worker.send({ url: url, size: cli.buffer, messages: cli.messages, id: id , localaddr: localaddr - , transport: cli.transport + , transport: cli.transport.split(',') , pingInterval: cli.pingInterval , nextTask: goOnTaskQueueWhenConcurrentLimited , runtime: cli.runtime + , connectTimeout: cli.connectTimeout }); // do it if cuncurrent is not 0 and smaller than the amount of all tcp connections @@ -250,7 +271,8 @@ async.forEach(cli.args, function forEach(url, done) { // We are setup, everything is running // console.log(''); -live(); +// live(); +setTimeout(live, 1000); process.once('SIGINT', function end() { robin.forEach(function nuke(worker) { diff --git a/worker-socketio.js b/worker-socketio.js index a7d3862..9c83797 100644 --- a/worker-socketio.js +++ b/worker-socketio.js @@ -19,6 +19,7 @@ var masked = process.argv[4] === 'true' // collect metics datas var metrics_datas = {collection:true, datas:[]} , statInterval = +process.argv[6] || 60 + , logError = +process.argv[7] || 0 , process_send = function(data, task) { if (statInterval <= 0 || ('open' == data.type && task.nextTask)) { process.send(data); @@ -51,7 +52,7 @@ var metrics_datas = {collection:true, datas:[]} }, statInterval * 1000); process.on('message', function message(task) { - var now = Date.now(); + var start_timestamp = Date.now(); // // Write a new message to the socket. The message should have a size of x @@ -77,26 +78,27 @@ process.on('message', function message(task) { var socket = new Socket(task.url, { 'force new connection': true, reconnection: false, - timeout: 5000, - transports: [task.transport], + timeout: task.connectTimeout * 1000, + transports: task.transport, protocolVersion: protocol, - localAddress: task.localaddr || null + localAddress: task.localaddr || null, + headers: {'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36'} }); socket.last = Date.now(); var pingInterval = null; socket.on('connect', function open() { - process_send({ type: 'open', duration: Date.now() - now, id: task.id, concurrent: concurrent, workerid: process.pid }, task); + process_send({ type: 'open', duration: Date.now() - start_timestamp, id: task.id, concurrent: concurrent, cur_time: Date.now(), wid: process.pid }, task); // write(socket, task, task.id); // As the `close` event is fired after the internal `_socket` is cleaned up // we need to do some hacky shit in order to tack the bytes send. }); - socket.on('message', function message(data) { + socket.on(process.env.NODE_ON_MESSAGE?process.env.NODE_ON_MESSAGE:'message', function message(data) { process_send({ - type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, workerid: process.pid, - id: task.id + type: 'message', latency: Date.now() - socket.last, + id: task.id, wid: process.pid }, task); // Only write as long as we are allowed to send messages @@ -108,23 +110,13 @@ process.on('message', function message(task) { socket.emit('disconnect'); } }); - socket.on('onMessage', function onMessage(data) { - process_send({ - type: 'message', latency: Date.now() - socket.last, concurrent: concurrent, workerid: process.pid, - id: task.id - }, task); - // Only write as long as we are allowed to send messages - if (task.messages > 0) - if (--task.messages) { - write(socket, task, task.id); - } else { - socket.disconnect(); - socket.emit('disconnect'); + socket.on('disconnect', function close(msg) { + var err = msg=='error' && arguments.length > 2 ? Array.prototype.slice.call(arguments, 1, 2).pop() : null; + if (err && logError) { + console.error(err); } - }); - socket.on('disconnect', function close() { var internal = {}; try{ internal = socket.io.engine.transport.ws._socket || {}; @@ -133,7 +125,7 @@ process.on('message', function message(task) { } process_send({ - type: 'close', id: task.id, concurrent: --concurrent, workerid: process.pid, + type: 'close', id: task.id, wid: process.pid, concurrent: --concurrent, cur_time: Date.now(), read: internal.bytesRead || 0, send: internal.bytesWritten || 0 }, task); @@ -144,20 +136,20 @@ process.on('message', function message(task) { }); socket.on('error', function error(err) { - process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent, workerid: process.pid }, task); + process_send({ type: 'error', message: err.description ? err.description.message : (err.message?err.message:err), id: task.id, wid: process.pid }, task); - socket.disconnect(); - socket.emit('disconnect'); + socket.disconnect('error', err); + socket.emit('disconnect', 'error', err); delete connections[task.id]; checkConnectionLength(); }); // catch ECONNREFUSED - socket.io.on('connect_error', function(err){ - process_send({ type: 'error', message: err.description ? err.description.message : err.message, id: task.id, concurrent: --concurrent, workerid: process.pid }, task); + socket.io.on('connect_error', function connect_error(err){ + process_send({ type: 'error', message: err.description ? err.description.message : (err.message?err.message:err), id: task.id, wid: process.pid }, task); - socket.disconnect(); - socket.emit('disconnect'); + socket.disconnect('error', err); + socket.emit('disconnect', 'error', err); delete connections[task.id]; checkConnectionLength(); }); @@ -198,10 +190,10 @@ function write(socket, task, id, fn, data) { mask: masked }, function sending(err) { if (err) { - process_send({ type: 'error', message: err.message, concurrent: --concurrent, workerid: process.pid, id: id }, task); + process_send({ type: 'error', message: err.description ? err.description.message : (err.message?err.message:err), id: task.id, wid: process.pid }, task); - socket.disconnect(); - socket.emit('disconnect'); + socket.disconnect('error', err); + socket.emit('disconnect', 'error', err); delete connections[id]; }