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.0.3 to 0.0.4

lib/loadserver.js

4

index.js

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

var loadtest = require('./lib/loadtest.js');
var server = require('./lib/server.js');
var loadserver = require('./lib/loadserver.js');

@@ -22,3 +22,3 @@ // globals

exports.loadTest = loadtest.loadTest;
exports.server = server.startServer;
exports.startServer = loadserver.startServer;

@@ -19,11 +19,20 @@ 'use strict';

var log = new Log('info');
var globalConcurrency = 100;
var globalRequestsPerSecond = 1;
var agent = true;
var latency;
var requests = 0;
// constants
var DEFAULT_OPTIONS = {
concurrency: 100,
requestsPerSecond: 1,
};
/**
* A client for an HTTP connection.
* Params is an object which may have:
* - requestsPerSecond: how many requests to create from this client.
* - maxRequests: stop sending requests after this global limit is reached.
* - noAgent: if true, do not use connection keep-alive.
*/
function HttpClient(url, requestsPerSecond)
function HttpClient(params)
{

@@ -36,2 +45,3 @@ // self-reference

var lastCall;
var timer;

@@ -43,3 +53,3 @@ /**

{
if (!requestsPerSecond)
if (!params.requestsPerSecond)
{

@@ -49,4 +59,4 @@ log.error('No requests per second selected');

}
var interval = Math.round(1000 / requestsPerSecond);
new timing.HighResolutionTimer(interval, makeRequest);
var interval = Math.round(1000 / params.requestsPerSecond);
timer = new timing.HighResolutionTimer(interval, makeRequest);
}

@@ -59,8 +69,13 @@

{
var id = timing.latency.start(id);
var options = urlLib.parse(url);
if (!agent)
requests += 1;
if (requests > params.maxRequests)
{
options.agent = false;
return timer.stop();
}
var id = latency.start(id);
var options = urlLib.parse(params.url);
if (params.noAgent)
{
options.noAgent = false;
}
var get = http.get(options, getConnect(id));

@@ -80,3 +95,3 @@ get.on('error', function(error)

{
log.debug('HTTP client connected to %s with id %s', url, id);
log.debug('HTTP client connected to %s with id %s', params.url, id);
connection.setEncoding('utf8');

@@ -90,3 +105,3 @@ connection.on('data', function(chunk)

log.error('Connection %s failed: %s', id, error);
timing.latency.end(id);
latency.end(id);

@@ -97,3 +112,3 @@ });

log.debug('Connection %s ended', id);
timing.latency.end(id);
latency.end(id);
});

@@ -151,3 +166,3 @@ };

var newCall = new Date().getTime();
timing.latency.add(newCall - lastCall);
latency.add(newCall - lastCall);
entry += ', latency: ' + (newCall - lastCall);

@@ -189,3 +204,3 @@ lastCall = null;

{
timing.latency.end(message.requestId);
latency.end(message.requestId);
}

@@ -206,3 +221,3 @@ }

connection.sendUTF(JSON.stringify(update));
timing.latency.start(update.requestId);
latency.start(update.requestId);
}

@@ -214,18 +229,29 @@ }

* Run a load test.
* Options is an object which may have:
* - url: mandatory URL to access.
* - concurrency: how many concurrent clients to use.
* - requestsPerSecond: how many requests per second per client.
* - noAction: if true, then do not use connection keep-alive.
* An optional callback will be called if/when the test finishes.
*/
exports.loadTest = function(url, concurrency, requestsPerSecond)
exports.loadTest = function(options, callback)
{
var constructor;
if (url.startsWith('ws://'))
if (!options.url)
{
constructor = function(url, requestsPerSecond)
log.error('Missing URL in options');
return;
}
if (options.url.startsWith('ws://'))
{
constructor = function(params)
{
return new WebsocketClient(url, requestsPerSecond);
return new WebsocketClient(params.url, params.requestsPerSecond);
};
}
else if (url.startsWith('http'))
else if (options.url.startsWith('http'))
{
constructor = function(url, requestsPerSecond)
constructor = function(params)
{
return new HttpClient(url, requestsPerSecond);
return new HttpClient(params);
};

@@ -237,3 +263,8 @@ }

}
startClients(url, concurrency, requestsPerSecond, constructor);
if (callback)
{
options.callback = callback;
}
latency = new timing.Latency(options);
startClients(options, constructor);
}

@@ -244,8 +275,8 @@

*/
function startClients(url, concurrency, requestsPerSecond, constructor)
function startClients(options, constructor)
{
for (var index = 0; index < concurrency; index++)
for (var index = 0; index < options.concurrency; index++)
{
url = url.replaceAll('$index', index);
var client = constructor(url, requestsPerSecond);
options.url = options.url.replaceAll('$index', index);
var client = constructor(options);
// start each client 100 ms after the last

@@ -272,5 +303,6 @@ setTimeout(client.start, (index * 100) % 1000);

/**
* Parse one argument. Returns true if there are more.
* Parse one argument and change options accordingly.
* Returns true if there may be more arguments to parse.
*/
function parseArgument(args)
function parseArgument(args, options)
{

@@ -286,7 +318,7 @@ if (args.length <= 1)

{
globalConcurrency = parseInt(argument);
options.concurrency = parseInt(argument);
}
else if (numbersParsed == 1)
{
globalRequestsPerSecond = parseInt(argument);
options.requestsPerSecond = parseInt(argument);
}

@@ -304,3 +336,2 @@ else

{
console.error('Unknown argument %s', argument);
return false;

@@ -310,3 +341,3 @@ }

{
agent = false;
options.noAgent = true;
args.splice(0, 1);

@@ -329,3 +360,4 @@ return true;

{
while (parseArgument(args))
var options = DEFAULT_OPTIONS;
while (parseArgument(args, options))
{

@@ -335,6 +367,14 @@ }

{
if (args.length == 0)
{
console.error('Missing URL');
}
else
{
console.error('Unknown arguments %s', JSON.stringify(args));
}
return help();
}
var url = args[0];
exports.loadTest(url, globalConcurrency, globalRequestsPerSecond);
options.url = args[0];
exports.loadTest(options);
}

@@ -341,0 +381,0 @@

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

// requires
var async = require('async');
var Log = require('log');

@@ -40,39 +41,117 @@

/**
* Run package tests.
* Run tests for string prototypes.
*/
exports.test = function()
function testStringPrototypes(callback)
{
if (!'pepito'.startsWith('pe'))
{
log.error('Failed to match using startsWith()')
return false;
return callback('Failed to match using startsWith()')
}
if ('pepito'.startsWith('po'))
{
log.error('Invalid match using startsWith()')
return false;
return callback('Invalid match using startsWith()')
}
if (!'pepito'.endsWith('to'))
{
log.error('Failed to match using endsWith()')
return false;
return callback('Failed to match using endsWith()')
}
if ('pepito'.startsWith('po'))
{
log.error('Invalid match using endsWith()')
return false;
return callback('Invalid match using endsWith()')
}
if ('pepito'.replaceAll('p', 'c') != 'cecito')
{
log.error('Invalid replaceAll().');
return false;
return callback('Invalid replaceAll().');
}
return true;
callback(null, true);
}
/**
* Count the number of properties in an object.
*/
exports.countProperties = function(object)
{
var count = 0;
for (var key in object)
{
count++;
}
return count;
}
/**
* Overwrite the given object with the original.
*/
exports.overwriteObject = function(overwriter, original)
{
if (!overwriter)
{
return original;
}
if (typeof(overwriter) != 'object')
{
log.error('Invalid overwriter object %s', overwriter);
return original;
}
for (var key in overwriter)
{
var value = overwriter[key];
if (value)
{
original[key] = value;
}
}
return original;
}
/**
* Test that overwrite object works.
*/
function testOverwriteObject(callback)
{
var first = {
a: 'a',
b: 'b',
};
var second = {
b: 'b2',
c: {d: 5},
};
exports.overwriteObject(first, second);
if (exports.countProperties(second) != 3)
{
return callback('Overwritten should have three properties');
}
if (second.b != 'b')
{
return callback('Property in second should be replaced with first');
}
callback(null, true);
}
/**
* Run package tests.
*/
exports.test = function(callback)
{
var tests = {
stringPrototypes: testStringPrototypes,
overwrite: testOverwriteObject,
};
async.series(tests, callback);
}
// run tests if invoked directly
if (__filename == process.argv[1])
{
exports.test();
exports.test(function(error, result)
{
if (error)
{
log.error('Tests failed: %s', error);
return;
}
log.info('Tests succesful');
});
}

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

// requires
var async = require('async');
var util = require('util');
var Log = require('log');

@@ -20,5 +22,8 @@ var microtime = require('microtime');

/**
* Latency measurements, global variable.
* 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.
*/
exports.latency = new function()
exports.Latency = function(options)
{

@@ -31,12 +36,23 @@ // self-reference

var secondsMeasured = 5;
var maxRequests = null;
var totalRequests = 0;
var totalTime = 0;
var lastShown = microtime.now();
var callback = null;
/**
* Initialize with seconds measured.
*/
self.init = function(seconds)
// init
if (options)
{
secondsMeasured = seconds;
if (options.maxRequests)
{
maxRequests = options.maxRequests;
}
if (options.seconds)
{
secondsMeasured = options.seconds;
}
if (options.callback)
{
callback = options.callback;
}
}

@@ -61,3 +77,3 @@

{
log.error('Message id ' + requestId + ' not found');
error('Message id ' + requestId + ' not found');
return;

@@ -76,3 +92,3 @@ }

{
log.error('Please call init() with seconds to measure');
error('Please call init() with seconds to measure');
return;

@@ -84,19 +100,64 @@ }

log.debug('Total requests: %s', totalRequests);
if (isFinished())
{
show();
}
}
/**
* Show an error message, or send to the callback.
*/
function error(message)
{
if (callback)
{
callback(error, null);
return;
}
log.error(message);
}
/**
* Check out if enough seconds have elapsed, or enough requests were received.
*/
function isFinished()
{
if (maxRequests)
{
return (totalRequests >= maxRequests);
}
var secondsElapsed = (microtime.now() - lastShown) / 1000000;
if (secondsElapsed >= secondsMeasured)
return (secondsElapsed >= secondsMeasured);
}
/**
* Checks if enough seconds have elapsed, or enough requests received.
* Show latency for finished requests, or send to the callback.
*/
function show()
{
var secondsElapsed = (microtime.now() - lastShown) / 1000000;
var meanTime = totalTime / totalRequests;
var results = {
meanLatencyMs: Math.round(meanTime / 10) / 100,
rps: Math.round(totalRequests / secondsElapsed),
}
if (callback)
{
var meanTime = totalTime / totalRequests;
var rps = Math.round(totalRequests / secondsElapsed);
log.info('Requests / second: %s, mean latency: %s ms', rps, Math.round(meanTime / 10) / 100);
totalTime = 0;
totalRequests = 0;
if (secondsElapsed > 2 * secondsMeasured)
{
lastShown = microtime.now();
}
else
{
lastShown += secondsMeasured * 1000000;
}
callback(null, results);
}
else
{
log.info('Requests / second: %s, mean latency: %s ms', results.rps, results.meanLatencyMs);
}
totalTime = 0;
totalRequests = 0;
if (secondsElapsed > 2 * secondsMeasured)
{
lastShown = microtime.now();
}
else
{
lastShown += secondsMeasured * 1000000;
}
}

@@ -106,2 +167,55 @@ }

/**
* Test latency ids.
*/
function testLatencyIds(callback)
{
var latency = new exports.Latency();
var firstId = latency.start();
if (!firstId)
{
return callback('Invalid first latency id');
}
var secondId = latency.start();
if (!secondId)
{
return callback('Invalid second latency id');
}
if (firstId == secondId)
{
return callback('Repeated latency ids');
}
callback(null, true);
}
/**
* Test latency measurements.
*/
function testLatencyRequests(callback)
{
var measured = false;
var options = {
maxRequests: 10,
callback: function(error, result)
{
measured = true;
callback(error, result);
},
};
var latency = new exports.Latency(options);
for (var i = 0; i < 10; i++)
{
var id = latency.start();
latency.end(id);
}
// give it time
setTimeout(function()
{
if (!measured)
{
callback('Latency did not call back in 10 ms');
}
}, 10);
}
/**
* A high resolution timer.

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

var start = Date.now();
var active = true;

@@ -125,2 +240,6 @@ /**

{
if (!active)
{
return false;
}
callback(delayMs);

@@ -142,2 +261,10 @@ counter ++;

/**
* Stop the timer.
*/
self.stop = function()
{
active = false;
}
// start timer

@@ -149,31 +276,49 @@ delayed();

/**
* Run package tests.
* Test a high resolution timer.
*/
exports.test = function()
function testTimer(callback)
{
var firstId = exports.latency.start();
if (!firstId)
var fired = false;
var timer = new exports.HighResolutionTimer(10, function()
{
log.error('Invalid first latency id');
return false;
}
var secondId = exports.latency.start();
if (!secondId)
fired = true;
callback(null, 'Timer fired');
});
timer.stop();
// give it time
setTimeout(function()
{
log.error('Invalid second latency id');
return false;
}
if (firstId == secondId)
{
log.error('Repeated latency ids');
return false;
}
return true;
if (!fired)
{
callback('Timer did not fire in 20 ms');
}
}, 20);
}
/**
* Run package tests.
*/
exports.test = function(callback)
{
var tests = {
latencyIds: testLatencyIds,
latencyRequests: testLatencyRequests,
timer: testTimer,
};
async.series(tests, callback);
}
// run tests if invoked directly
if (__filename == process.argv[1])
{
exports.test();
exports.test(function(error, result)
{
if (error)
{
log.error('Tests failed: %s', error);
return;
}
log.info('Tests succesful');
});
}
{
"name": "loadtest",
"version": "0.0.3",
"version": "0.0.4",
"description": "Load test scripts.",

@@ -16,2 +16,3 @@ "homepage": "http://milliearth.org/",

"microtime": "*",
"async": "*",
"log": "*"

@@ -18,0 +19,0 @@ },

@@ -1,8 +0,6 @@

loadtest
========
# loadtest
Runs a load test on the selected URL or websocket. Easy to extend minimally for your own ends.
Runs a load test on the selected HTTP or websocket URL. The API allows for easy integration in your own tests.
Installation
------------
## Installation

@@ -12,5 +10,6 @@ Just run:

Usage
-----
Or add package loadtest to your package.json dependencies.
## Usage
Run as a script to load test a URL:

@@ -30,20 +29,19 @@

## Concurrency
#### Concurrency
loadtest will create a simultaneous number of clients.
loadtest will create a simultaneous number of clients; this parameter controls how many.
## Requests Per Second
#### Requests Per Second
Controls the number of requests per second for each client.
## --noagent
#### --noagent
Open connections without keep-alive: send header 'Connection: Close' instead of 'Connection: Keep-alive'.
Server
------
### Server
loadtest bundles a test server. To run it:
$ node lib/server.js [port]
$ node lib/loadserver.js [port]

@@ -54,5 +52,59 @@ It will show the number of requests received per second, the latency in answering requests and the headers for selected requests.

License
-------
## API
loadtest is not limited to running from the command line; it can be controlled using an API, thus allowing you to load test your application in your own tests.
### Invoke Load Test
To run a load test use the exported function loadTest() passing it a set of options and an optional callback:
var loadtest = require('loadtest');
var options = {
url: 'http://localhost:8000',
maxRequests: 1000,
};
loadtest.loadTest(options, function(error, result)
{
if (error)
{
return console.error('Got an error: %s', error);
}
console.log('Tests run successfully');
});
The callback will be invoked when the max number of requests is reached, or when the number of seconds has elapsed. Options are:
### Options
This is the set of available options. Except where noted, all options are (as their name implies) optional.
#### url
The URL to invoke.
#### concurrency
How many clients to start in parallel.
#### requestsPerSecond
How many requests each client will send per second.
#### maxRequests
A max number of requests; after they are reached the test will end.
### Start Test Server
To start the test server use the exported function startServer() with a port and an optional callback:
var loadserver = require('loadserver');
loadserver.startServer(8000);
### Complete Sample
The file lib/sample.js shows a complete sample, which is also an integration test: it starts the server, send 1000 requests, waits for the callback and closes down the server.
## License
(The MIT License)

@@ -59,0 +111,0 @@

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

var timing = require('./lib/timing.js');
var sample = require('./lib/sample.js');
var util = require('util');
var async = require('async');
var Log = require('log');

@@ -21,15 +24,23 @@

*/
exports.test = function()
exports.test = function(callback)
{
if (!prototypes.test())
var run = false;
var tests = {
prototypes: prototypes.test,
timing: timing.test,
sample: sample.test,
};
async.series(tests, function(error, result)
{
log.error('Failure in prototypes test');
exit(1);
}
if (!timing.test())
run = true;
callback(error, result);
});
// give it time
setTimeout(function()
{
log.error('Failure in timing test');
exit(1);
}
log.notice('Tests run correctly');
if (!run)
{
callback('Package tests did not call back');
}
}, 2200);
}

@@ -40,4 +51,13 @@

{
exports.test();
exports.test(function(error, result)
{
if (error)
{
log.error('Failure in tests: %s', error);
process.exit(1);
return;
}
log.info('All tests successful: %s', util.inspect(result, true, 10, true));
});
}
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