Socket
Socket
Sign inDemoInstall

loadtest

Package Overview
Dependencies
28
Maintainers
1
Versions
122
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.2.0 to 6.0.0

lib/options.js

180

bin/loadtest.js
#!/usr/bin/env node
'use strict';
/**
* Binary to run loadtest.
* (C) 2013 Manuel Ernst, Alex Fernández.
*/
import {readFile} from 'fs/promises'
import * as stdio from 'stdio'
import {loadTest} from '../lib/loadtest.js'
// requires
const stdio = require('stdio');
const fs = require('fs');
const path = require('path');
const urlLib = require('url');
const loadTest = require('../lib/loadtest.js');
const headers = require('../lib/headers.js');
const packageJson = require(__dirname + '/../package.json');
const config = require('../lib/config');
// init
const options = stdio.getopt({

@@ -42,152 +30,36 @@ maxRequests: {key: 'n', args: 1, description: 'Number of requests to perform'},

rps: {args: 1, description: 'Specify the requests per second for each client'},
agent: {description: 'Use a keep-alive http agent (deprecated)'},
index: {args: 1, description: 'Replace the value of given arg with an index in the URL'},
quiet: {description: 'Do not log any messages'},
debug: {description: 'Show debug messages'},
insecure: {description: 'Allow self-signed certificates over https'},
key: {args: 1, description: 'The client key to use'},
cert: {args: 1, description: 'The client certificate to use'}
cert: {args: 1, description: 'The client certificate to use'},
agent: {description: 'Use a keep-alive http agent (deprecated)'},
quiet: {description: 'Do not log any messages (deprecated)'},
debug: {description: 'Show debug messages (deprecated)'}
});
if (options.version) {
console.log('Loadtest version: %s', packageJson.version);
process.exit(0);
}
// is there an url? if not, break and display help
if (!options.args || options.args.length < 1) {
console.error('Missing URL to load-test');
help();
} else if (options.args.length > 1) {
console.error('Too many arguments: %s', options.args);
help();
}
const configuration = config.loadConfig(options);
options.url = options.args[0];
options.agentKeepAlive = options.keepalive || options.agent || configuration.agentKeepAlive;
options.indexParam = options.index || configuration.indexParam;
//TODO: add index Param
// Allow a post body string in options
// Ex -P '{"foo": "bar"}'
if (options.postBody) {
options.method = 'POST';
options.body = options.postBody;
}
if (options.postFile) {
options.method = 'POST';
options.body = readBody(options.postFile, '-p');
}
if (options.data) {
options.body = JSON.parse(options.data);
}
if (options.method) {
const acceptedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'get', 'post', 'put', 'delete', 'patch'];
if (acceptedMethods.indexOf(options.method) === -1) {
options.method = 'GET';
async function processAndRun(options) {
if (options.version) {
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url)))
console.log('Loadtest version: %s', packageJson.version);
process.exit(0);
}
}
if(options.putFile) {
options.method = 'PUT';
options.body = readBody(options.putFile, '-u');
}
if (options.patchBody) {
options.method = 'PATCH';
options.body = options.patchBody;
}
if(options.patchFile) {
options.method = 'PATCH';
options.body = readBody(options.patchFile, '-a');
}
if(!options.method) {
options.method = configuration.method;
}
if(!options.body) {
if(configuration.body) {
options.body = configuration.body;
} else if(configuration.file) {
options.body = readBody(configuration.file, 'configuration.request.file');
}
}
options.requestsPerSecond = options.rps ? parseFloat(options.rps) : configuration.requestsPerSecond;
if(!options.key) {
options.key = configuration.key;
}
if(options.key) {
options.key = fs.readFileSync(options.key);
}
if(!options.cert) {
options.cert = configuration.cert;
}
if(options.cert) {
options.cert = fs.readFileSync(options.cert);
}
const defaultHeaders = options.headers || !configuration.headers ? {} : configuration.headers;
defaultHeaders['host'] = urlLib.parse(options.url).host;
defaultHeaders['user-agent'] = 'loadtest/' + packageJson.version;
defaultHeaders['accept'] = '*/*';
if (options.headers) {
headers.addHeaders(options.headers, defaultHeaders);
console.log('headers: %s, %j', typeof defaultHeaders, defaultHeaders);
}
options.headers = defaultHeaders;
if (!options.requestGenerator) {
options.requestGenerator = configuration.requestGenerator;
}
if (options.requestGenerator) {
options.requestGenerator = require(path.resolve(options.requestGenerator));
}
// Use configuration file for other values
if(!options.maxRequests) {
options.maxRequests = configuration.maxRequests;
}
if(!options.concurrency) {
options.concurrency = configuration.concurrency;
}
if(!options.maxSeconds) {
options.maxSeconds = configuration.maxSeconds;
}
if(!options.timeout && configuration.timeout) {
options.timeout = configuration.timeout;
}
if(!options.contentType) {
options.contentType = configuration.contentType;
}
if(!options.cookies) {
options.cookies = configuration.cookies;
}
if(!options.secureProtocol) {
options.secureProtocol = configuration.secureProtocol;
}
if(!options.insecure) {
options.insecure = configuration.insecure;
}
if(!options.recover) {
options.recover = configuration.recover;
}
if(!options.proxy) {
options.proxy = configuration.proxy;
}
loadTest.loadTest(options);
function readBody(filename, option) {
if (typeof filename !== 'string') {
console.error('Invalid file to open with %s: %s', option, filename);
// is there an url? if not, break and display help
if (!options.args || options.args.length < 1) {
console.error('Missing URL to load-test');
help();
} else if (options.args.length > 1) {
console.error('Too many arguments: %s', options.args);
help();
}
if(path.extname(filename) === '.js') {
return require(path.resolve(filename));
options.url = options.args[0];
try {
loadTest(options)
} catch(error) {
console.error(error.message)
help()
}
}
const ret = fs.readFileSync(filename, {encoding: 'utf8'}).replace("\n", "");
await processAndRun(options)
return ret;
}
/**

@@ -194,0 +66,0 @@ * Show online help.

#!/usr/bin/env node
'use strict';
/**
* Binary to run test server.
* (C) 2013 Manuel Ernst, Alex Fernández.
*/
import * as stdio from 'stdio'
import {startServer} from '../lib/testserver.js'
import {loadConfig} from '../lib/config.js'
// requires
const stdio = require('stdio');
const testServer = require('../lib/testserver');
const config = require('../lib/config')
// init
const options = stdio.getopt({

@@ -20,3 +13,3 @@ delay: {key: 'd', args: 1, description: 'Delay the response for the given milliseconds'},

});
const configuration = config.loadConfig(options)
const configuration = loadConfig()
if (options.args && options.args.length == 1) {

@@ -49,3 +42,3 @@ options.port = parseInt(options.args[0], 10);

testServer.startServer(options);
startServer(options);

@@ -1,16 +0,10 @@

'use strict';
import {loadTest} from './lib/loadtest.js'
import {startServer} from './lib/testserver.js'
/**
* Package contains a load test script and a test server.
* (C) 2013 Alex Fernández.
*/
const loadtest = {loadTest, startServer}
export default loadtest
// requires
const loadtest = require('./lib/loadtest.js');
const testserver = require('./lib/testserver.js');
export * from './lib/loadtest.js'
export * from './lib/testserver.js'
// exports
exports.loadTest = loadtest.loadTest;
exports.startServer = testserver.startServer;

@@ -1,12 +0,6 @@

"use strict";
import * as urlLib from 'url'
import {addUserAgent} from './headers.js'
const urlLib = require('url');
const Log = require('log');
const headers = require('./headers.js');
// globals
const log = new Log('info');
class BaseClient {
export class BaseClient {
constructor(operation, params) {

@@ -25,3 +19,2 @@ this.operation = operation;

if (error) {
log.debug('Connection %s failed: %s', id, error);
if (result) {

@@ -32,4 +25,2 @@ errorCode = result;

}
} else {
log.debug('Connection %s ended', id);
}

@@ -61,26 +52,18 @@ this.operation.latency.end(id, errorCode);

if (typeof this.params.body == 'string') {
log.debug('Received string body');
this.generateMessage = () => this.params.body;
} else if (typeof this.params.body == 'object') {
log.debug('Received JSON body');
this.generateMessage = () => this.params.body;
} else if (typeof this.params.body == 'function') {
log.debug('Received function body');
this.generateMessage = this.params.body;
} else {
log.error('Unrecognized body: %s', typeof this.params.body);
console.error('Unrecognized body: %s', typeof this.params.body);
}
this.options.headers['Content-Type'] = this.params.contentType || 'text/plain';
}
headers.addUserAgent(this.options.headers);
addUserAgent(this.options.headers);
if (this.params.secureProtocol) {
this.options.secureProtocol = this.params.secureProtocol;
}
log.debug('Options: %j',this.options);
}
}
module.exports = {
BaseClient,
}

@@ -1,10 +0,2 @@

"use strict";
/**
* Support for configuration file.
*/
// requires
const Log = require("log");
const {
import {
Confinode,

@@ -21,6 +13,4 @@ Level,

stringItem
} = require("confinode");
} from 'confinode'
// globals
const log = new Log("info");

@@ -30,9 +20,3 @@ /**

*/
exports.loadConfig = function(options) {
if (options.debug) {
log.level = Log.DEBUG;
}
if (options.quiet) {
log.level = Log.NOTICE;
}
export function loadConfig() {
const description = literal({

@@ -66,3 +50,3 @@ delay: numberItem(0),

return result ? result.configuration : {};
};
}

@@ -75,12 +59,12 @@ /**

case Level.Error:
log.error(msg.toString());
console.error(msg.toString());
break;
case Level.Warning:
log.warning(msg.toString());
console.warn(msg.toString());
break;
case Level.Information:
log.info(msg.toString());
console.info(msg.toString());
break;
default:
log.debug(msg.toString());
// nothing
}

@@ -106,1 +90,2 @@ }

}

@@ -1,16 +0,7 @@

'use strict';
/**
* Support for custom headers.
* (C) 2013 Alex Fernández.
*/
// requires
const testing = require('testing');
/**
* Add all raw headers given to the given array.
*/
exports.addHeaders = function(rawHeaders, headers) {
export function addHeaders(rawHeaders, headers) {
if (Array.isArray(rawHeaders)) {

@@ -25,3 +16,3 @@ rawHeaders.forEach(function(header) {

}
};
}

@@ -41,62 +32,10 @@ /**

function testAddHeaders(callback) {
const tests = [ {
raw: 'k:v',
headers: { 'k': 'v' }
}, {
raw: ['k:v', 'k:v2'],
headers: { 'k': 'v2' }
}, {
raw: ['k:v', 'k2:v2'],
headers: { 'k': 'v', 'k2': 'v2' }
}, {
raw: 'K:v',
headers: { 'k': 'v' }
}, {
raw: 'k:v:w',
headers: { 'k': 'v:w' }
}
];
tests.forEach(function(test) {
const headers = {};
exports.addHeaders(test.raw, headers);
testing.assertEquals(headers, test.headers, 'Wrong headers', callback);
});
testing.success(callback);
}
/**
* Add a user-agent header if not present.
*/
exports.addUserAgent = function(headers) {
export function addUserAgent(headers) {
if(!headers['user-agent']) {
headers['user-agent'] = 'node.js loadtest bot';
}
};
function testAddUserAgent(callback) {
const headers = {'k': 'v', 'q': 'r' };
exports.addUserAgent(headers);
testing.assertEquals(Object.keys(headers).length, 3, 'Did not add user agent', callback);
const userAgent = headers['user-agent'];
testing.assert(userAgent.includes('bot'), 'Invalid user agent', callback);
exports.addUserAgent(headers);
testing.assertEquals(Object.keys(headers).length, 3, 'Should not add user agent', callback);
testing.success(callback);
}
/**
* Run all tests.
*/
exports.test = function(callback) {
testing.run([
testAddHeaders,
testAddUserAgent,
], callback);
};
// run tests if invoked directly
if (__filename == process.argv[1]) {
exports.test(testing.show);
}

@@ -1,17 +0,3 @@

'use strict';
/**
* Measure latency for a load test.
* (C) 2013 Alex Fernández.
*/
// requires
const testing = require('testing');
const Log = require('log');
// globals
const log = new Log('info');
/**

@@ -22,3 +8,3 @@ * A high resolution timer. Params:

*/
class HighResolutionTimer {
export class HighResolutionTimer {

@@ -62,3 +48,3 @@ /**

const drift = diff / this.delayMs - this.counter;
log.debug('Seconds: ' + Math.round(diff / 1000) + ', counter: ' + this.counter + ', drift: ' + drift);
console.debug('Seconds: ' + Math.round(diff / 1000) + ', counter: ' + this.counter + ', drift: ' + drift);
}

@@ -82,40 +68,1 @@

/**
* Test a high resolution timer.
*/
function testTimerStop(callback) {
const timer = new HighResolutionTimer(10, callback);
setImmediate(() => timer.stop())
}
function testTimerRun(callback) {
let run = 0
const timer = new HighResolutionTimer(100, () => run++)
setTimeout(() => {
testing.equals(run, 3, callback)
timer.stop()
testing.success(callback)
}, 250)
}
/**
* Run package tests.
*/
function test(callback) {
const tests = [
testTimerStop,
testTimerRun,
];
testing.run(tests, callback);
}
module.exports = {
HighResolutionTimer,
test,
}
// run tests if invoked directly
if (__filename == process.argv[1]) {
test(testing.show);
}

@@ -1,24 +0,12 @@

'use strict';
import * as urlLib from 'url'
import * as http from 'http'
import * as https from 'https'
import * as querystring from 'querystring'
import * as websocket from 'websocket'
import {HighResolutionTimer} from './hrtimer.js'
import {addUserAgent} from './headers.js'
import * as agentkeepalive from 'agentkeepalive'
import * as HttpsProxyAgent from 'https-proxy-agent'
/**
* Load Test a URL, website or websocket.
* (C) 2013 Alex Fernández.
*/
// requires
const testing = require('testing');
const urlLib = require('url');
const http = require('http');
const https = require('https');
const qs = require('querystring');
const websocket = require('websocket');
const Log = require('log');
const {HighResolutionTimer} = require('./hrtimer.js');
const headers = require('./headers.js');
// globals
const log = new Log('info');
/**

@@ -28,5 +16,5 @@ * Create a new HTTP client.

*/
exports.create = function(operation, params) {
export function create(operation, params) {
return new HttpClient(operation, params);
};
}

@@ -62,3 +50,3 @@ /**

if (this.params.agentKeepAlive) {
const KeepAlive = (this.options.protocol == 'https:') ? require('agentkeepalive').HttpsAgent : require('agentkeepalive');
const KeepAlive = (this.options.protocol == 'https:') ? agentkeepalive.HttpsAgent : agentkeepalive;
let maxSockets = 10;

@@ -79,15 +67,12 @@ if (this.params.requestsPerSecond) {

if (typeof this.params.body == 'string') {
log.debug('Received string body');
this.generateMessage = () => this.params.body;
} else if (typeof this.params.body == 'object') {
log.debug('Received JSON body');
if (this.params.contentType === 'application/x-www-form-urlencoded') {
this.params.body = qs.stringify(this.params.body);
this.params.body = querystring.stringify(this.params.body);
}
this.generateMessage = () => this.params.body;
} else if (typeof this.params.body == 'function') {
log.debug('Received function body');
this.generateMessage = this.params.body;
} else {
log.error('Unrecognized body: %s', typeof this.params.body);
console.error('Unrecognized body: %s', typeof this.params.body);
}

@@ -105,7 +90,6 @@ this.options.headers['Content-Type'] = this.params.contentType || 'text/plain';

}
headers.addUserAgent(this.options.headers);
addUserAgent(this.options.headers);
if (this.params.secureProtocol) {
this.options.secureProtocol = this.params.secureProtocol;
}
log.debug('Options: %j', this.options);
}

@@ -152,3 +136,2 @@

}
const HttpsProxyAgent = require('https-proxy-agent');

@@ -186,3 +169,3 @@ // adding proxy configuration

if (!timeout) {
log.error('Invalid timeout %s', this.params.timeout);
console.error('Invalid timeout %s', this.params.timeout);
}

@@ -209,3 +192,2 @@ request.setTimeout(timeout, () => {

if (error) {
log.debug('Connection %s failed: %s', id, error);
if (result) {

@@ -219,4 +201,2 @@ errorCode = result.statusCode;

}
} else {
log.debug('Connection %s ended', id);
}

@@ -249,6 +229,4 @@

return connection => {
log.debug('HTTP client connected to %s with id %s', this.params.url, id);
connection.setEncoding('utf8');
connection.on('data', chunk => {
log.debug('Body: %s', chunk);
body += chunk;

@@ -287,27 +265,1 @@ });

function testHttpClient(callback) {
const options = {
url: 'http://localhost:7357/',
maxSeconds: 0.1,
concurrency: 1,
quiet: true,
};
exports.create({}, options);
testing.success(callback);
}
/**
* Run all tests.
*/
exports.test = function (callback) {
testing.run([
testHttpClient,
], callback);
};
// run tests if invoked directly
if (__filename == process.argv[1]) {
exports.test(testing.show);
}

@@ -1,19 +0,4 @@

'use strict';
import * as crypto from 'crypto'
/**
* Measure latency for a load test.
* (C) 2013 Alex Fernández.
*/
// requires
const testing = require('testing');
const util = require('util');
const crypto = require('crypto');
const Log = require('log');
// globals
const log = new Log('info');
/**

@@ -23,7 +8,6 @@ * Latency measurements. Options can be:

* - maxSeconds: max seconds, alternative to max requests.
* - quiet: do not log messages.
* An optional callback(error, results) will be called with an error,
* or the results after max is reached.
*/
class Latency {
export class Latency {
constructor(options, callback) {

@@ -49,8 +33,2 @@ this.options = options

this.requestIdToIndex = {};
if (options.quiet) {
log.level = Log.NOTICE;
}
if (options.debug) {
log.level = Log.DEBUG;
}
}

@@ -82,3 +60,2 @@

if (!(requestId in this.requests)) {
log.debug('Message id ' + requestId + ' not found');
return -2;

@@ -100,3 +77,2 @@ }

add(time, errorCode) {
log.debug('New value: %s', time);
this.partialTime += time;

@@ -115,3 +91,2 @@ this.partialRequests++;

}
log.debug('Partial requests: %s', this.partialRequests);
const rounded = Math.floor(time);

@@ -125,3 +100,2 @@ if (rounded > this.maxLatencyMs) {

if (!this.histogramMs[rounded]) {
log.debug('Initializing histogram for %s', rounded);
this.histogramMs[rounded] = 0;

@@ -149,6 +123,6 @@ }

}
log.info('Requests: %s%s, requests per second: %s, mean latency: %s ms', this.totalRequests, percent, results.rps, results.meanLatencyMs);
console.info('Requests: %s%s, requests per second: %s, mean latency: %s ms', this.totalRequests, percent, results.rps, results.meanLatencyMs);
if (this.totalErrors) {
percent = Math.round(100 * 10 * this.totalErrors / this.totalRequests) / 10;
log.info('Errors: %s, accumulated errors: %s, %s% of total requests', this.partialErrors, this.totalErrors, percent);
console.info('Errors: %s, accumulated errors: %s, %s% of total requests', this.partialErrors, this.totalErrors, percent);
}

@@ -183,5 +157,3 @@ this.partialTime = 0;

isFinished() {
log.debug('Total requests %s, max requests: %s', this.totalRequests, this.options.maxRequests);
if (this.options.maxRequests && this.totalRequests >= this.options.maxRequests) {
log.debug('Max requests reached: %s', this.totalRequests);
return true;

@@ -191,3 +163,2 @@ }

if (this.options.maxSeconds && elapsedSeconds >= this.options.maxSeconds) {
log.debug('Max seconds reached: %s', this.totalRequests);
return true;

@@ -206,3 +177,2 @@ }

}
this.show();
}

@@ -233,3 +203,2 @@

computePercentiles() {
log.debug('Histogram: %s', util.inspect(this.histogramMs));
const percentiles = {

@@ -247,3 +216,2 @@ 50: false,

}
log.debug('Histogram for %s: %s', ms, this.histogramMs[ms]);
counted += this.histogramMs[ms];

@@ -253,3 +221,2 @@ const percent = counted / this.totalRequests * 100;

Object.keys(percentiles).forEach(percentile => {
log.debug('Checking percentile %s for %s', percentile, percent);
if (!percentiles[percentile] && percent > percentile) {

@@ -272,10 +239,10 @@ percentiles[percentile] = ms;

const results = this.getResults();
log.info('');
log.info('Target URL: %s', this.options.url);
console.info('');
console.info('Target URL: %s', this.options.url);
if (this.options.maxRequests) {
log.info('Max requests: %s', this.options.maxRequests);
console.info('Max requests: %s', this.options.maxRequests);
} else if (this.options.maxSeconds) {
log.info('Max time (s): %s', this.options.maxSeconds);
console.info('Max time (s): %s', this.options.maxSeconds);
}
log.info('Concurrency level: %s', this.options.concurrency);
console.info('Concurrency level: %s', this.options.concurrency);
let agent = 'none';

@@ -285,27 +252,27 @@ if (this.options.agentKeepAlive) {

}
log.info('Agent: %s', agent);
console.info('Agent: %s', agent);
if (this.options.requestsPerSecond) {
log.info('Requests per second: %s', this.options.requestsPerSecond * this.options.concurrency);
console.info('Requests per second: %s', this.options.requestsPerSecond * this.options.concurrency);
}
log.info('');
log.info('Completed requests: %s', results.totalRequests);
log.info('Total errors: %s', results.totalErrors);
log.info('Total time: %s s', results.totalTimeSeconds);
log.info('Requests per 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');
console.info('');
console.info('Completed requests: %s', results.totalRequests);
console.info('Total errors: %s', results.totalErrors);
console.info('Total time: %s s', results.totalTimeSeconds);
console.info('Requests per second: %s', results.rps);
console.info('Mean latency: %s ms', results.meanLatencyMs);
console.info('');
console.info('Percentage of the requests served within a certain time');
Object.keys(results.percentiles).forEach(percentile => {
log.info(' %s% %s ms', percentile, results.percentiles[percentile]);
console.info(' %s% %s ms', percentile, results.percentiles[percentile]);
});
log.info(' 100% %s ms (longest request)', this.maxLatencyMs);
console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
if (results.totalErrors) {
log.info('');
log.info(' 100% %s ms (longest request)', this.maxLatencyMs);
log.info('');
console.info('');
console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
console.info('');
Object.keys(results.errorCodes).forEach(errorCode => {
const padding = ' '.repeat(errorCode.length < 4 ? 4 - errorCode.length : 1);
log.info(' %s%s: %s errors', padding, errorCode, results.errorCodes[errorCode]);
console.info(' %s%s: %s errors', padding, errorCode, results.errorCodes[errorCode]);
});

@@ -326,90 +293,1 @@ }

/**
* Test latency ids.
*/
function testLatencyIds(callback) {
const latency = new Latency({});
const firstId = latency.start();
testing.assert(firstId, 'Invalid first latency id %s', firstId, callback);
const secondId = latency.start();
testing.assert(secondId, 'Invalid second latency id', callback);
testing.assert(firstId != secondId, 'Repeated latency ids', callback);
testing.success(callback);
}
/**
* Test latency measurements.
*/
function testLatencyRequests(callback) {
const options = {
maxRequests: 10,
};
const errorCode = '500';
const latency = new Latency(options, (error, result) => {
testing.check(error, 'Could not compute latency', callback);
testing.assertEquals(result.totalRequests, 10, 'Invalid total requests', callback);
testing.assertEquals(result.totalErrors, 1, 'Invalid total errors', callback);
testing.assert(errorCode in result.errorCodes, 'Error code not found', callback);
testing.assertEquals(result.errorCodes[errorCode], 1, 'Should have one ' + errorCode, callback);
testing.success(callback);
});
let id;
for (let i = 0; i < 9; i++) {
id = latency.start();
latency.end(id);
}
id = latency.start();
latency.end(id, errorCode);
}
/**
* Check that percentiles are correct.
*/
function testLatencyPercentiles(callback) {
const options = {
maxRequests: 10
};
const latency = new Latency(options, error => {
testing.check(error, 'Error while testing latency percentiles', callback);
const percentiles = latency.getResults().percentiles;
Object.keys(percentiles).forEach(percentile => {
testing.assert(percentiles[percentile] !== false, 'Empty percentile for %s', percentile, callback);
});
testing.success(percentiles, callback);
});
for (let ms = 1; ms <= 10; ms++) {
log.debug('Starting %s', ms);
(function() {
const id = latency.start();
setTimeout(() => {
log.debug('Ending %s', id);
latency.end(id);
}, ms);
})();
}
}
/**
* Run package tests.
*/
function test(callback) {
const tests = [
testLatencyIds,
testLatencyRequests,
testLatencyPercentiles,
];
testing.run(tests, callback);
}
module.exports = {
Latency,
test,
}
// run tests if invoked directly
if (__filename == process.argv[1]) {
test(testing.show);
}

@@ -1,26 +0,11 @@

'use strict';
import * as http from 'http'
import * as https from 'https'
import * as httpClient from './httpClient.js'
import * as websocket from './websocket.js'
import {Latency} from './latency.js'
import {HighResolutionTimer} from './hrtimer.js'
import {processOptions} from './options.js'
/**
* Load Test a URL, website or websocket.
* (C) 2013 Alex Fernández.
*/
// requires
const Log = require('log');
const http = require('http');
const https = require('https');
const testing = require('testing');
const httpClient = require('./httpClient.js');
const websocket = require('./websocket.js');
const {Latency} = require('./latency.js');
const {HighResolutionTimer} = require('./hrtimer.js');
// globals
const log = new Log('info');
// constants
const SHOW_INTERVAL_MS = 5000;
// init
http.globalAgent.maxSockets = 1000;

@@ -44,39 +29,20 @@ https.globalAgent.maxSockets = 1000;

* - agentKeepAlive: if true, then use connection keep-alive.
* - debug: show debug messages.
* - quiet: do not log any messages.
* - indexParam: string to replace with a unique index.
* - insecure: allow https using self-signed certs.
* - debug: show debug messages (deprecated).
* - quiet: do not log any messages (deprecated).
* An optional callback will be called if/when the test finishes.
*/
exports.loadTest = function(options, callback) {
if (!options.url) {
log.error('Missing URL in options');
return;
}
options.concurrency = options.concurrency || 1;
if (options.requestsPerSecond) {
options.requestsPerSecond = options.requestsPerSecond / options.concurrency;
}
if (options.debug) {
log.level = Log.DEBUG;
}
if (!options.url.startsWith('http://') && !options.url.startsWith('https://') && !options.url.startsWith('ws://')) {
log.error('Invalid URL %s, must be http://, https:// or ws://', options.url);
return;
}
if (callback && !('quiet' in options)) {
options.quiet = true;
}
if (options.url.startsWith('ws:')) {
if (options.requestsPerSecond) {
log.error('"requestsPerSecond" not supported for WebSockets');
export function loadTest(options, callback) {
processOptions(options, error => {
if (error) {
if (callback) return callback(error)
throw new error
}
}
const operation = new Operation(options, callback);
operation.start();
return operation;
})
}
const operation = new Operation(options, callback);
operation.start();
return operation;
};
/**

@@ -126,5 +92,2 @@ * Used to keep track of individual load test Operation runs.

}
if (this.requests > this.options.maxRequests) {
log.debug('Should have no more running clients');
}
}

@@ -207,95 +170,1 @@ if (this.running && next) {

/**
* A load test with max seconds.
*/
function testMaxSeconds(callback) {
const options = {
url: 'http://localhost:7357/',
maxSeconds: 0.1,
concurrency: 1,
quiet: true,
};
exports.loadTest(options, callback);
}
/**
* A load test with max seconds.
*/
function testWSEcho(callback) {
const options = {
url: 'ws://localhost:7357/',
maxSeconds: 0.1,
concurrency: 1,
quiet: true,
};
exports.loadTest(options, callback);
}
function testIndexParam(callback) {
const options = {
url: 'http://localhost:7357/replace',
concurrency:1,
quiet: true,
maxSeconds: 0.1,
indexParam: "replace"
};
exports.loadTest(options, callback);
}
function testIndexParamWithBody(callback) {
const options = {
url: 'http://localhost:7357/replace',
concurrency:1,
quiet: true,
maxSeconds: 0.1,
indexParam: "replace",
body: '{"id": "replace"}'
};
exports.loadTest(options, callback);
}
function testIndexParamWithCallback(callback) {
const options = {
url: 'http://localhost:7357/replace',
concurrency:1,
quiet: true,
maxSeconds: 0.1,
indexParam: "replace",
indexParamCallback: function() {
//https://gist.github.com/6174/6062387
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
};
exports.loadTest(options, callback);
}
function testIndexParamWithCallbackAndBody(callback) {
const options = {
url: 'http://localhost:7357/replace',
concurrency:1,
quiet: true,
maxSeconds: 0.1,
body: '{"id": "replace"}',
indexParam: "replace",
indexParamCallback: function() {
//https://gist.github.com/6174/6062387
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
};
exports.loadTest(options, callback);
}
/**
* Run all tests.
*/
exports.test = function(callback) {
testing.run([testMaxSeconds, testWSEcho, testIndexParam, testIndexParamWithBody, testIndexParamWithCallback, testIndexParamWithCallbackAndBody], callback);
};
// run tests if invoked directly
if (__filename == process.argv[1]) {
exports.test(testing.show);
}

@@ -1,22 +0,7 @@

'use strict';
import * as http from 'http'
import {server as WebSocketServer} from 'websocket'
import * as util from 'util'
import * as net from 'net'
import {Latency} from './latency.js'
/**
* Test server to load test.
* (C) 2013 Alex Fernández.
*/
// requires
const testing = require('testing');
const http = require('http');
const WebSocketServer = require('websocket').server;
const util = require('util');
const net = require('net');
const Log = require('log');
const {Latency} = require('./latency.js');
// globals
const log = new Log('info');
// constants
const PORT = 7357;

@@ -37,5 +22,2 @@ const LOG_HEADERS_INTERVAL_SECONDS = 1;

this.debuggedTime = Date.now();
if (options.quiet) {
log.level = 'notice';
}
}

@@ -67,3 +49,3 @@

this.server.listen(this.port, () => {
log.info('Listening on port %s', this.port);
console.info(`Listening on http://localhost:${this.port}/`);
if (callback) {

@@ -76,9 +58,6 @@ callback();

const connection = request.accept(null, request.origin);
log.debug(' Connection accepted.');
connection.on('message', message => {
if (message.type === 'utf8') {
log.debug('Received Message: ' + message.utf8Data);
connection.sendUTF(message.utf8Data);
} else if (message.type === 'binary') {
log.debug('Received Binary Message of ' + message.binaryData.length + ' bytes');
connection.sendBytes(message.binaryData);

@@ -88,3 +67,3 @@ }

connection.on('close', () => {
log.info('Peer %s disconnected', connection.remoteAddress);
console.info('Peer %s disconnected', connection.remoteAddress);
});

@@ -100,3 +79,3 @@ });

if (!callback) {
return log.error(message);
return console.error(message);
}

@@ -135,3 +114,3 @@ callback(message);

socket.on('error', error => {
log.error('socket error: %s', error);
console.error('socket error: %s', error);
socket.end();

@@ -146,3 +125,3 @@ });

readData(data) {
log.info('data: %s', data);
console.info('data: %s', data);
}

@@ -154,5 +133,5 @@

debug(request) {
log.info('Headers for %s to %s: %s', request.method, request.url, util.inspect(request.headers));
console.info('Headers for %s to %s: %s', request.method, request.url, util.inspect(request.headers));
if (request.body) {
log.info('Body: %s', request.body);
console.info('Body: %s', request.body);
}

@@ -184,3 +163,3 @@ }

if (!percent) {
log.error('Invalid error percent %s', this.options.percent);
console.error('Invalid error percent %s', this.options.percent);
return false;

@@ -196,40 +175,11 @@ }

* - delay: wait the given milliseconds before answering.
* - quiet: do not log any messages.
* - quiet: do not log any messages (deprecated).
* - percent: give an error (default 500) on some % of requests.
* - error: set an HTTP error code, default is 500.
* An optional callback is called after the server has started.
* In this case the quiet option is enabled.
*/
exports.startServer = function(options, callback) {
if (callback) {
options.quiet = true;
}
export function startServer(options, callback) {
const server = new TestServer(options);
return server.start(callback);
};
function testStartServer(callback) {
const options = {
port: 10530,
};
const server = exports.startServer(options, error => {
testing.check(error, 'Could not start server', callback);
server.close(error => {
testing.check(error, 'Could not stop server', callback);
testing.success(callback);
});
});
}
/**
* Run the tests.
*/
exports.test = function(callback) {
testing.run([testStartServer], 5000, callback);
};
// start server if invoked directly
if (__filename == process.argv[1]) {
exports.test(testing.show);
}

@@ -1,17 +0,4 @@

'use strict';
import websocket from 'websocket'
import {BaseClient} from './baseClient.js'
/**
* Load test a websocket.
* (C) 2013 Alex Fernández.
*/
// requires
const WebSocketClient = require('websocket').client;
const testing = require('testing');
const Log = require('log');
const BaseClient = require('./baseClient.js').BaseClient;
// globals
const log = new Log('info');
let latency;

@@ -23,5 +10,5 @@

*/
exports.create = function(operation, params) {
export function create(operation, params) {
return new WebsocketClient(operation, params);
};
}

@@ -44,9 +31,6 @@ /**

start() {
this.client = new WebSocketClient();
this.client.on('connectFailed', error => {
log.debug('WebSocket client connection error ' + error);
});
this.client = new websocket.client();
this.client.on('connectFailed', () => {});
this.client.on('connect', connection => this.connect(connection));
this.client.connect(this.params.url, []);
log.debug('WebSocket client connected to ' + this.params.url);
}

@@ -60,3 +44,2 @@

this.connection.close();
log.debug('WebSocket client disconnected from ' + this.params.url);
}

@@ -106,3 +89,3 @@ }

if (message.type != 'utf8') {
log.error('Invalid message type ' + message.type);
console.error('Invalid message type ' + message.type);
return;

@@ -115,8 +98,6 @@ }

catch(e) {
log.error('Invalid JSON: ' + message.utf8Data);
console.error('Invalid JSON: ' + message.utf8Data);
return;
}
log.debug("Received response %j", json);
// eat the client_connected message we get at the beginning

@@ -131,3 +112,2 @@ if ((json && json[0] && json[0][0] == 'client_connected')) {

latency.add(newCall - this.lastCall);
log.debug('latency: ' + (newCall - this.lastCall));
this.lastCall = null;

@@ -161,27 +141,1 @@ }

function testWebsocketClient(callback) {
const options = {
url: 'ws://localhost:7357/',
maxSeconds: 0.1,
concurrency: 1,
quiet: true,
};
exports.create({}, options);
testing.success(callback);
}
/**
* Run tests, currently nothing.
*/
exports.test = function(callback) {
testing.run([
testWebsocketClient,
], callback);
};
// start tests if invoked directly
if (__filename == process.argv[1]) {
exports.test(testing.show);
}
{
"name": "loadtest",
"version": "5.2.0",
"version": "6.0.0",
"type": "module",
"description": "Run load tests for your web application. Mostly ab-compatible interface, with an option to force requests per second. Includes an API for automated load testing.",

@@ -20,9 +21,8 @@ "homepage": "https://github.com/alexfernandez/loadtest",

"https-proxy-agent": "^2.2.1",
"log": "1.4.*",
"stdio": "^0.2.3",
"testing": "^1.1.1",
"websocket": "^1.0.28"
"stdio": "0.2.7",
"testing": "^3.0.3",
"websocket": "^1.0.34"
},
"devDependencies": {
"eslint": "^4.19.1"
"eslint": "^8.47.0"
},

@@ -39,3 +39,3 @@ "keywords": [

"engines": {
"node": ">=10"
"node": ">=16"
},

@@ -48,3 +48,3 @@ "bin": {

"scripts": {
"test": "node test.js",
"test": "node test/all.js",
"posttest": "eslint ."

@@ -51,0 +51,0 @@ },

@@ -36,4 +36,5 @@ [![Build Status](https://secure.travis-ci.org/alexfernandez/loadtest.svg)](http://travis-ci.org/alexfernandez/loadtest)

Versions 5 and later should be used at least with Node.js v10 or later:
Versions 6 and later should be used at least with Node.js v16 or later:
* Node.js v16 or later: ^6.0.0
* Node.js v10 or later: ^5.0.0

@@ -193,4 +194,4 @@ * Node.js v8 or later: 4.x.y.

If `POST-file` has `.js` extension it will be `require`d. It should be a valid node module and it
should `export` a single function, which is invoked with an automatically generated request identifier
If `POST-file` has `.js` extension it will be `import`ed. It should be a valid node module and it
should `export` a default function, which is invoked with an automatically generated request identifier
to provide the body of each request.

@@ -202,3 +203,3 @@ This is useful if you want to generate request bodies dynamically and vary them for each request.

```javascript
module.exports = function(requestId) {
export default function request(requestId) {
// this object will be serialized to JSON and sent in the body of the request

@@ -208,6 +209,8 @@ return {

requestId: requestId
};
};
}
}
```
See sample file in `sample/post-file.js`, and test in `test/body-generator.js`.
#### `-u PUT-file`

@@ -218,7 +221,7 @@

If `PUT-file` has `.js` extension it will be `require`d. It should be a valid node module and it
should `export` a single function, which is invoked with an automatically generated request identifier
If `PUT-file` has `.js` extension it will be `import`ed. It should be a valid node module and it
should `export` a default function, which is invoked with an automatically generated request identifier
to provide the body of each request.
This is useful if you want to generate request bodies dynamically and vary them for each request.
For an example function see above for `-p`.
For examples see above for `-p`.

@@ -230,7 +233,7 @@ #### `-a PATCH-file`

If `PATCH-file` has `.js` extension it will be `require`d. It should be a valid node module and it
should `export` a single function, which is invoked with an automatically generated request identifier
If `PATCH-file` has `.js` extension it will be `import`ed. It should be a valid node module and it
should `export` a default function, which is invoked with an automatically generated request identifier
to provide the body of each request.
This is useful if you want to generate request bodies dynamically and vary them for each request.
For an example function see above for `-p`.
For examples see above for `-p`.

@@ -315,10 +318,14 @@ ##### `-r`

#### `--quiet`
#### `--quiet` (deprecated)
Do not show any messages.
#### `--debug`
Note: deprecated in version 6+, shows a warning.
#### `--debug` (deprecated)
Show debug messages.
Note: deprecated in version 6+, shows a warning.
#### `--insecure`

@@ -483,15 +490,14 @@

```javascript
const loadtest = require('loadtest');
import {loadTest} from 'loadtest'
const options = {
url: 'http://localhost:8000',
maxRequests: 1000,
};
loadtest.loadTest(options, function(error, result)
{
if (error)
{
return console.error('Got an error: %s', error);
}
loadTest(options, function(error, result) {
if (error) {
return console.error('Got an error: %s', error)
}
console.log('Tests run successfully');
});
console.log('Tests run successfully')
})
```

@@ -599,6 +605,8 @@

#### `quiet`
#### `quiet` (deprecated)
Do not show any messages.
Note: deprecated in version 6+, shows a warning.
#### `indexParam`

@@ -645,3 +653,3 @@

```javascript
const loadtest = require('loadtest');
import {loadTest} from 'loadtest'

@@ -652,10 +660,10 @@ const options = {

secureProtocol: 'TLSv1_method'
};
}
loadtest.loadTest(options, function(error) {
loadTest(options, function(error) {
if (error) {
return console.error('Got an error: %s', error);
return console.error('Got an error: %s', error)
}
console.log('Tests run successfully');
});
console.log('Tests run successfully')
})
```

@@ -682,10 +690,10 @@

```javascript
const loadtest = require('loadtest');
import {loadTest} from 'loadtest'
function statusCallback(error, result, latency) {
console.log('Current latency %j, result %j, error %j', latency, result, error);
console.log('----');
console.log('Request elapsed milliseconds: ', result.requestElapsed);
console.log('Request index: ', result.requestIndex);
console.log('Request loadtest() instance index: ', result.instanceIndex);
console.log('Current latency %j, result %j, error %j', latency, result, error)
console.log('----')
console.log('Request elapsed milliseconds: ', result.requestElapsed)
console.log('Request index: ', result.requestIndex)
console.log('Request loadtest() instance index: ', result.instanceIndex)
}

@@ -697,10 +705,10 @@

statusCallback: statusCallback
};
}
loadtest.loadTest(options, function(error) {
loadTest(options, function(error) {
if (error) {
return console.error('Got an error: %s', error);
return console.error('Got an error: %s', error)
}
console.log('Tests run successfully');
});
console.log('Tests run successfully')
})
```

@@ -807,4 +815,4 @@

```javascript
const testserver = require('testserver');
const server = testserver.startServer({ port: 8000 });
import {startServer} from 'loadtest'
const server = startServer({port: 8000})
```

@@ -873,2 +881,4 @@

See sample file in `sample/.loadtestrc`.
For more information about the actual configuration file name, read the [confinode user manual](https://github.com/slune-org/confinode/blob/master/doc/en/usermanual.md#configuration-search). In the list of the [supported file types](https://github.com/slune-org/confinode/blob/master/doc/extensions.md), please note that only synchronous loaders can be used with _loadtest_.

@@ -878,3 +888,3 @@

The file `lib/integration.js` shows a complete example, which is also a full integration test:
The file `test/integration.js` shows a complete example, which is also a full integration test:
it starts the server, send 1000 requests, waits for the callback and closes down the server.

@@ -881,0 +891,0 @@

@@ -1,3 +0,1 @@

'use strict';
/**

@@ -9,3 +7,3 @@ * Sample request generator usage.

const loadtest = require('../lib/loadtest.js');
import {loadTest} from '../index.js'

@@ -31,3 +29,3 @@ const options = {

loadtest.loadTest(options, (error, results) => {
loadTest(options, (error, results) => {
if (error) {

@@ -34,0 +32,0 @@ return console.error('Got an error: %s', error);

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc