Socket
Socket
Sign inDemoInstall

loadtest

Package Overview
Dependencies
Maintainers
1
Versions
122
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

loadtest - npm Package Compare versions

Comparing version 0.1.3 to 0.1.4

lib/websocket.js

301

lib/loadtest.js

@@ -10,3 +10,2 @@ 'use strict';

// requires
var WebSocketClient = require('websocket').client;
var urlLib = require('url');

@@ -17,7 +16,6 @@ var http = require('http');

var timing = require('./timing.js');
var websocket = require('./websocket.js');
// globals
var log = new Log('info');
var latency;
var requests = 0;

@@ -28,3 +26,2 @@ // constants

concurrency: 1,
requestsPerSecond: 1,
};

@@ -35,2 +32,5 @@

* A client for an HTTP connection.
* Operation is an object which has these attributes:
* - latency: a variable to measure latency.
* - running: if the operation is running or not.
* Params is an object which must contain:

@@ -42,9 +42,8 @@ * - url: destination URL.

* - payload: contents of request (default empty).
* - maxRequests: stop sending requests after this global limit is reached
* (default unlimited).
* - maxSeconds: stop receiving requests after this number of seconds
* (default unlimited).
* - maxRequests: stop sending requests after this global limit is reached.
* - requestsPerSecond: limit requests per second to send.
* - maxSeconds: stop receiving requests after this number of seconds.
* - noAgent: if true, do not use connection keep-alive (default false).
*/
function HttpClient(params)
function HttpClient(operation, params)
{

@@ -58,2 +57,3 @@ // self-reference

var timer;
var id;

@@ -67,3 +67,3 @@ /**

{
log.error('No requests per second selected');
self.makeRequest();
return;

@@ -76,16 +76,23 @@ }

/**
* Stop the HTTP client.
*/
self.stop = function()
{
if (!timer)
{
return;
}
timer.stop();
}
/**
* Make a single request to the server.
*/
self.makeRequest = function(callback)
self.makeRequest = function()
{
requests += 1;
if (requests > params.maxRequests)
if (!operation.running)
{
if (callback)
{
callback(null);
}
return timer.stop();
return;
}
var id = latency.start(id);
id = operation.latency.start(id);
var options = urlLib.parse(params.url);

@@ -120,18 +127,3 @@ if (params.noAgent)

}
var request = http.request(options, getConnect(id, function(error, result)
{
if (error)
{
log.error('Connection %s failed: %s', id, error);
}
else
{
log.debug('Connection %s ended', id);
}
latency.end(id);
if (callback)
{
callback(error, result);
}
}));
var request = http.request(options, getConnect(id, requestFinished));
if (message)

@@ -149,2 +141,24 @@ {

/**
* One request has finished, go for the next.
*/
function requestFinished(error, result)
{
if (error)
{
log.error('Connection %s failed: %s', id, error);
}
else
{
log.debug('Connection %s ended', id);
}
operation.latency.end(id);
var callback;
if (!params.requestsPerSecond)
{
callback = self.makeRequest;
}
operation.callback(error, result, callback);
}
/**
* Get a function to connect the player.

@@ -180,107 +194,2 @@ */

/**
* A client that connects to a websocket.
*/
function WebsocketClient(url, requestsPerSecond)
{
// self-reference
var self = this;
// attributes
var connection;
var lastCall;
/**
* Start the websocket client.
*/
self.start = function()
{
var client = new WebSocketClient();
client.on('connectFailed', function(error) {
error('Connect Error: ' + error.toString());
});
client.on('connect', connect);
client.connect(url, []);
log.debug('WebSocket client connected to ' + url);
}
/**
* Connect the player.
*/
function connect(localConnection)
{
connection = localConnection;
connection.on('error', function(error) {
error("Connection error: " + error.toString());
});
connection.on('close', function() {
log.debug('Connection closed');
});
connection.on('message', function(message) {
if (message.type != 'utf8')
{
error('Invalid message type ' + message.type);
return;
}
if (lastCall)
{
var newCall = new Date().getTime();
latency.add(newCall - lastCall);
entry += ', latency: ' + (newCall - lastCall);
lastCall = null;
}
var json;
try
{
json = JSON.parse(message.utf8Data);
}
catch(e)
{
error('Invalid JSON: ' + message.utf8Data);
return;
}
receive(json);
});
}
/**
* Receive a message from the server.
*/
function receive(message)
{
if (!message || !message.type)
{
error('Wrong message ' + JSON.stringify(message));
return;
}
if (message.type == 'start')
{
log.debug('Starting game for ' + self.playerId);
setInterval(requestUpdate, Math.round(1000 / requestsPerSecond));
return;
}
if (message.requestId)
{
latency.end(message.requestId);
}
}
/**
* Request an update from the server.
*/
function requestUpdate()
{
if (connection.connected)
{
var update = {
requestId: Math.floor(Math.random() * 0x100000000).toString(16),
type: 'ping',
};
connection.sendUTF(JSON.stringify(update));
latency.start(update.requestId);
}
}
}
/**
* Run a load test.

@@ -306,13 +215,7 @@ * Options is an object which may have:

{
constructor = function(params)
{
return new WebsocketClient(params.url, params.requestsPerSecond);
};
constructor = websocket.WebSocketClient;
}
else if (options.url.startsWith('http'))
{
constructor = function(params)
{
return new HttpClient(params);
};
constructor = HttpClient;
}

@@ -323,22 +226,91 @@ else

}
if (callback)
{
options.callback = callback;
}
latency = new timing.Latency(options);
startClients(options, constructor);
var operation = new Operation(options, constructor, callback);
operation.start();
}
/**
* Start a number of measuring clients.
* A load test operation.
*/
function startClients(options, constructor)
var Operation = function(options, constructor, callback)
{
for (var index = 0; index < options.concurrency; index++)
// self-reference
var self = this;
// attributes
self.running = true;
self.latency = null;
var clients = {};
var requests = 0;
/**
* Start the operation.
*/
self.start = function()
{
options.url = options.url.replaceAll('$index', index);
var client = constructor(options);
// start each client 100 ms after the last
setTimeout(client.start, (index * 100) % 1000);
self.latency = new timing.Latency(options);
startClients();
if (options.maxSeconds)
{
setTimeout(stop, options.maxSeconds * 1000);
}
}
/**
* Call after each operation has finished.
*/
self.callback = function(error, result, next)
{
requests += 1;
if (options.maxRequests)
{
if (requests == options.maxRequests)
{
stop();
}
if (requests > options.maxRequests)
{
log.debug('Should have no more running clients');
}
}
if (self.running && next)
{
next();
}
}
/**
* Start a number of measuring clients.
*/
function startClients()
{
var url = options.url;
for (var index = 0; index < options.concurrency; index++)
{
if (options.indexParam)
{
options.url = url.replaceAll(indexParam, index);
}
var client = new constructor(self, options);
clients[index] = client;
// start each client 100 ms after the last
setTimeout(client.start, (index * 100) % 1000);
}
}
/**
* Stop clients.
*/
function stop()
{
self.running = false;
if (callback)
{
callback();
}
for (var index in clients)
{
clients[index].stop();
}
}
}

@@ -353,6 +325,8 @@

console.log(' where URL can be a regular HTTP or websocket URL.');
console.log('Options are:');
console.log('Apache ab-compatible options are:');
console.log(' -n requests Number of requests to perform');
console.log(' -c concurrency Number of multiple requests to make');
console.log(' -t timelimit Seconds to max. wait for responses');
console.log(' -r Do not exit on socket receive errors');
console.log('Other options are:');
console.log(' --rps Requests per second for each client');

@@ -391,2 +365,5 @@ console.log(' --noagent Do not use http agent (default)');

return true;
case '-r':
options.recover = true;
return true;
case '--rps':

@@ -393,0 +370,0 @@ options.requestsPerSecond = args.shift();

@@ -20,3 +20,3 @@ 'use strict';

// constants
var PORT = 8000;
var PORT = 10408;

@@ -33,3 +33,2 @@

concurrency: 100,
requestsPerSecond: 10,
method: 'POST',

@@ -36,0 +35,0 @@ payload: {

@@ -21,3 +21,3 @@ 'use strict';

var headersDebuggedTime = Date.now();
var latency = new timing.Latency();
var latency = new timing.Latency({});

@@ -24,0 +24,0 @@ // constants

@@ -22,7 +22,10 @@ 'use strict';

* Latency measurements. Options can be:
* - seconds: how many seconds to measure before showing latency.
* - maxRequests: how many requests to make, alternative to seconds.
* - callback: function to call when there are measures.
* - showSeconds: how many seconds to measure before showing latency.
* - showRequests: how many requests to make, alternative to seconds.
* - maxRequests: max number of requests to measure before stopping.
* - maxSeconds: max seconds, alternative to max requests.
* An optional callback(error, results) will be called with an error,
* or the results after max is reached.
*/
exports.Latency = function(options)
exports.Latency = function(options, callback)
{

@@ -34,26 +37,13 @@ // self-reference

var requests = {};
var secondsMeasured = 5;
var maxRequests = null;
var showSeconds = options.showSeconds || 5;
var partialRequests = 0;
var partialTime = 0;
var lastShown = microtime.now();
var initialTime = microtime.now();
var totalRequests = 0;
var totalTime = 0;
var lastShown = microtime.now();
var callback = null;
var maxLatencyMs = 0;
var histogramMs = {};
var running = true;
// init
if (options)
{
if (options.maxRequests)
{
maxRequests = options.maxRequests;
}
if (options.seconds)
{
secondsMeasured = options.seconds;
}
if (options.callback)
{
callback = options.callback;
}
}
/**

@@ -76,5 +66,9 @@ * Start the request with the given id.

{
error('Message id ' + requestId + ' not found');
log.error('Message id ' + requestId + ' not found');
return;
}
if (!running)
{
return;
}
add(microtime.now() - requests[requestId]);

@@ -87,16 +81,25 @@ delete requests[requestId];

*/
function add(value)
function add(time)
{
if (!secondsMeasured)
log.debug('New value: %s', time);
partialTime += time;
partialRequests++;
totalTime += time;
totalRequests++;
log.debug('Partial requests: %s', partialRequests);
var rounded = Math.floor(time / 1000);
if (rounded > maxLatencyMs)
{
error('Please call init() with seconds to measure');
return;
maxLatencyMs = rounded;
}
log.debug('New value: %s', value);
totalTime += value;
totalRequests++;
log.debug('Total requests: %s', totalRequests);
if (!histogramMs[rounded])
{
log.debug('Initializing histogram for %s', rounded);
histogramMs[rounded] = 0;
}
histogramMs[rounded] += 1;
showPartial();
if (isFinished())
{
show();
finish();
}

@@ -106,56 +109,132 @@ }

/**
* Show an error message, or send to the callback.
* Check out if enough seconds have elapsed, or enough requests were received.
* If so, show latency for partial requests.
*/
function error(message)
function showPartial()
{
if (callback)
if (options.showRequests && partialRequests < options.showRequests)
{
callback(error, null);
return;
}
log.error(message);
var elapsedSeconds = (microtime.now() - lastShown) / 1000000;
if (elapsedSeconds < showSeconds)
{
return;
}
var meanTime = partialTime / partialRequests;
var results = {
meanLatencyMs: Math.round(meanTime / 10) / 100,
rps: Math.round(partialRequests / elapsedSeconds),
}
log.info('Requests / second: %s, mean latency: %s ms', results.rps, results.meanLatencyMs);
partialTime = 0;
partialRequests = 0;
if (elapsedSeconds > 2 * showSeconds)
{
lastShown = microtime.now();
}
else
{
lastShown += showSeconds * 1000000;
}
}
/**
* Check out if enough seconds have elapsed, or enough requests were received.
* Check out if the measures are finished.
*/
function isFinished()
{
if (maxRequests)
log.debug('Total requests %s, max requests: %s', totalRequests, options.maxRequests);
if (options.maxRequests && totalRequests >= options.maxRequests)
{
return (totalRequests >= maxRequests);
log.debug('Max requests reached: %s', totalRequests);
return true;
}
var secondsElapsed = (microtime.now() - lastShown) / 1000000;
return (secondsElapsed >= secondsMeasured);
var elapsedSeconds = (microtime.now() - initialTime) / 1000000;
if (options.maxSeconds && elapsedSeconds >= maxSeconds)
{
log.debug('Max seconds reached: %s', totalRequests);
return true;
}
return false;
}
/**
* Checks if enough seconds have elapsed, or enough requests received.
* Show latency for finished requests, or send to the callback.
* We are finished.
*/
function show()
function finish()
{
var secondsElapsed = (microtime.now() - lastShown) / 1000000;
var meanTime = totalTime / totalRequests;
var results = {
meanLatencyMs: Math.round(meanTime / 10) / 100,
rps: Math.round(totalRequests / secondsElapsed),
}
running = false;
if (callback)
{
callback(null, results);
return callback(null, self.getResults());
}
else
{
log.info('Requests / second: %s, mean latency: %s ms', results.rps, results.meanLatencyMs);
self.show();
}
/**
* Get final results.
*/
self.getResults = function()
{
var elapsedSeconds = (microtime.now() - initialTime) / 1000000;
var meanTime = totalTime / totalRequests;
return {
totalRequests: totalRequests,
totalTimeSeconds: elapsedSeconds,
rps: Math.round(partialRequests / elapsedSeconds),
meanLatencyMs: Math.round(meanTime / 10) / 100,
}
totalTime = 0;
totalRequests = 0;
if (secondsElapsed > 2 * secondsMeasured)
}
/**
* Compute the percentiles.
*/
self.computePercentiles = function()
{
log.debug('Histogram: %s', util.inspect(histogramMs));
var percentiles = {
50: false,
90: false,
95: false,
99: false,
};
var counted = 0;
for (var ms = 0; ms <= maxLatencyMs; ms++)
{
lastShown = microtime.now();
if (!histogramMs[ms])
{
continue;
}
log.debug('Histogram for %s: %s', ms, histogramMs[ms]);
counted += histogramMs[ms];
var percent = counted / totalRequests * 100;
for (var percentile in percentiles)
{
log.debug('Checking percentile %s for %s', percentile, percent);
if (!percentiles[percentile] && percent > percentile)
{
percentiles[percentile] = ms;
}
}
}
else
return percentiles;
}
/**
* Show final results.
*/
self.show = function()
{
var results = self.getResults();
log.info('Completed requests: %s', results.totalRequests);
log.info('Total time: %s s', results.totalTimeSeconds);
log.info('Requests / second: %s', results.rps);
log.info('Mean latency: %s ms', results.meanLatencyMs);
log.info('');
log.info('Percentage of the requests served within a certain time');
var percentiles = self.computePercentiles();
for (var percentile in percentiles)
{
lastShown += secondsMeasured * 1000000;
log.info(' %s% %s ms', percentile, percentiles[percentile]);
}

@@ -170,3 +249,3 @@ }

{
var latency = new exports.Latency();
var latency = new exports.Latency({});
var firstId = latency.start();

@@ -187,5 +266,4 @@ testing.assert(firstId, 'Invalid first latency id', callback);

maxRequests: 10,
callback: callback,
};
var latency = new exports.Latency(options);
var latency = new exports.Latency(options, callback);
for (var i = 0; i < 10; i++)

@@ -199,2 +277,33 @@ {

/**
* Check that percentiles are correct.
*/
function testLatencyPercentiles(callback)
{
var options = {
maxRequests: 10,
};
var latency = new exports.Latency(options, function(error, result)
{
var percentiles = latency.computePercentiles();
for (var percentile in percentiles)
{
testing.assert(percentiles[percentile] !== false, 'Empty percentile for %s', percentile, callback);
}
testing.success(percentiles, callback);
});
for (var ms = 1; ms <= 10; ms++)
{
log.debug('Starting %s', ms);
(function() {
var id = latency.start();
setTimeout(function()
{
log.debug('Ending %s', id);
latency.end(id);
}, ms);
})();
}
}
/**
* A high resolution timer.

@@ -268,2 +377,3 @@ * Initialize with milliseconds to wait and the callback to call.

latencyRequests: testLatencyRequests,
latencyPercentiles: testLatencyPercentiles,
timer: testTimer,

@@ -270,0 +380,0 @@ };

{
"name": "loadtest",
"version": "0.1.3",
"version": "0.1.4",
"description": "Load test scripts.",

@@ -5,0 +5,0 @@ "homepage": "http://milliearth.org/",

@@ -7,7 +7,10 @@ # loadtest

Just run:
$ npm install loadtest
Install globally as root:
# npm install -g loadtest
Or add package loadtest to your package.json dependencies.
On Ubuntu or Mac OS X systems install using sudo:
$ sudo install -g loadtest
For programmatic access, install locally or add package loadtest to your package.json dependencies.
## Usage

@@ -17,7 +20,12 @@

$ node node_modules/lib/loadtest.js [URL] or [websocket URL]
$ loadtest [-n requests] [-c concurrency] [URL]
The URL can be "http://" or "ws://" for websockets. (Note: websockets are not working at the moment, patches welcome.) Set the desired level of concurrency with the -c parameter.
Single-dash parameters (e.g. -n) are designed to be compatible with Apache's ab.
http://httpd.apache.org/docs/2.2/programs/ab.html
To get online help, run loadtest without parameters:
$ node node_modules/lib/loadtest.js
$ loadtest

@@ -28,16 +36,24 @@ ### Advanced Usage

$ node node_modules/lib/loadtest.js [-n requests] [-c concurrency] ...
$ loadtest [-n requests] [-c concurrency] ...
#### Concurrency
#### -n requests
Number of requests to send out.
#### -c concurrency
loadtest will create a simultaneous number of clients; this parameter controls how many.
#### Requests Per Second
#### -t timelimit
Controls the number of requests per second for each client.
Number of seconds to wait until requests no longer go out. (Note: this is different than Apache's ab, which stops _receiving_ requests after the given seconds.)
#### --noagent
#### --rps requestsPerSecond
Open connections without keep-alive: send header 'Connection: Close' instead of 'Connection: Keep-alive'.
Controls the number of requests per second for each client. (Note: this parameter is not present in Apache's ab.)
#### --agent
Open connections using keep-alive: send header 'Connection: Keep-alive' instead of 'Connection: Close'. (Warning: uses the default node.js agent, which means there is a limit in outgoing connections.)
### Server

@@ -47,3 +63,3 @@

$ node node_modules/lib/testserver.js [port]
$ testserver [port]

@@ -50,0 +66,0 @@ It will show the number of requests received per second, the latency in answering requests and the headers for selected requests.

@@ -9,5 +9,2 @@ 'use strict';

// requires
var prototypes = require('./lib/prototypes.js');
var timing = require('./lib/timing.js');
var sample = require('./lib/sample.js');
var util = require('util');

@@ -26,7 +23,8 @@ var testing = require('testing');

{
var tests = {
prototypes: prototypes.test,
timing: timing.test,
sample: sample.test,
};
var tests = {};
var libs = [ 'prototypes', 'timing', 'sample', 'websocket' ];
libs.forEach(function(lib)
{
tests[lib] = require('./lib/' + lib + '.js').test;
});
testing.run(tests, 2200, callback);

@@ -33,0 +31,0 @@ }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc