loadtest 5.1.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/headers.js CHANGED
@@ -1,18 +1,9 @@
1
- 'use strict';
2
-
3
- /**
4
- * Support for custom headers.
5
- * (C) 2013 Alex Fernández.
6
- */
7
-
8
- // requires
9
- const testing = require('testing');
10
1
 
11
2
 
12
3
  /**
13
4
  * Add all raw headers given to the given array.
14
5
  */
15
- exports.addHeaders = function(rawHeaders, headers) {
6
+ export function addHeaders(rawHeaders, headers) {
16
7
  if (Array.isArray(rawHeaders)) {
17
8
  rawHeaders.forEach(function(header) {
18
9
  addHeader(header, headers);
@@ -22,7 +13,7 @@ exports.addHeaders = function(rawHeaders, headers) {
22
13
  } else {
23
14
  console.error('Invalid header structure %j, it should be an array');
24
15
  }
25
- };
16
+ }
26
17
 
27
18
  /**
28
19
  * Add a single header to an array.
@@ -37,64 +28,12 @@ function addHeader(rawHeader, headers) {
37
28
  headers[key.toLowerCase()] = value;
38
29
  }
39
30
 
40
- function testAddHeaders(callback) {
41
- const tests = [ {
42
- raw: 'k:v',
43
- headers: { 'k': 'v' }
44
- }, {
45
- raw: ['k:v', 'k:v2'],
46
- headers: { 'k': 'v2' }
47
- }, {
48
- raw: ['k:v', 'k2:v2'],
49
- headers: { 'k': 'v', 'k2': 'v2' }
50
- }, {
51
- raw: 'K:v',
52
- headers: { 'k': 'v' }
53
- }, {
54
- raw: 'k:v:w',
55
- headers: { 'k': 'v:w' }
56
- }
57
- ];
58
- tests.forEach(function(test) {
59
- const headers = {};
60
- exports.addHeaders(test.raw, headers);
61
- testing.assertEquals(headers, test.headers, 'Wrong headers', callback);
62
- });
63
- testing.success(callback);
64
- }
65
-
66
31
  /**
67
32
  * Add a user-agent header if not present.
68
33
  */
69
- exports.addUserAgent = function(headers) {
34
+ export function addUserAgent(headers) {
70
35
  if(!headers['user-agent']) {
71
36
  headers['user-agent'] = 'node.js loadtest bot';
72
37
  }
73
- };
74
-
75
- function testAddUserAgent(callback) {
76
- const headers = {'k': 'v', 'q': 'r' };
77
- exports.addUserAgent(headers);
78
- testing.assertEquals(Object.keys(headers).length, 3, 'Did not add user agent', callback);
79
- const userAgent = headers['user-agent'];
80
- testing.assert(userAgent.includes('bot'), 'Invalid user agent', callback);
81
- exports.addUserAgent(headers);
82
- testing.assertEquals(Object.keys(headers).length, 3, 'Should not add user agent', callback);
83
- testing.success(callback);
84
- }
85
-
86
- /**
87
- * Run all tests.
88
- */
89
- exports.test = function(callback) {
90
- testing.run([
91
- testAddHeaders,
92
- testAddUserAgent,
93
- ], callback);
94
- };
95
-
96
- // run tests if invoked directly
97
- if (__filename == process.argv[1]) {
98
- exports.test(testing.show);
99
38
  }
100
39
 
package/lib/hrtimer.js CHANGED
@@ -1,17 +1,3 @@
1
- 'use strict';
2
-
3
- /**
4
- * Measure latency for a load test.
5
- * (C) 2013 Alex Fernández.
6
- */
7
-
8
-
9
- // requires
10
- const testing = require('testing');
11
- const Log = require('log');
12
-
13
- // globals
14
- const log = new Log('info');
15
1
 
16
2
 
17
3
  /**
@@ -19,7 +5,7 @@ const log = new Log('info');
19
5
  * - delayMs: miliseconds to wait before calls. Can be fractional.
20
6
  * - callback: function to call every time.
21
7
  */
22
- class HighResolutionTimer {
8
+ export class HighResolutionTimer {
23
9
 
24
10
  /**
25
11
  * Create a timer with the given delay.
@@ -58,7 +44,7 @@ class HighResolutionTimer {
58
44
  traceDrift() {
59
45
  const diff = Date.now() - this.start;
60
46
  const drift = diff / this.delayMs - this.counter;
61
- log.debug('Seconds: ' + Math.round(diff / 1000) + ', counter: ' + this.counter + ', drift: ' + drift);
47
+ console.debug('Seconds: ' + Math.round(diff / 1000) + ', counter: ' + this.counter + ', drift: ' + drift);
62
48
  }
63
49
 
64
50
  stop() {
@@ -77,42 +63,3 @@ class HighResolutionTimer {
77
63
  }
78
64
  }
79
65
 
80
- /**
81
- * Test a high resolution timer.
82
- */
83
- function testTimerStop(callback) {
84
- const timer = new HighResolutionTimer(10, callback);
85
- setImmediate(() => timer.stop())
86
- }
87
-
88
- function testTimerRun(callback) {
89
- let run = 0
90
- const timer = new HighResolutionTimer(100, () => run++)
91
- setTimeout(() => {
92
- testing.equals(run, 3, callback)
93
- timer.stop()
94
- testing.success(callback)
95
- }, 250)
96
- }
97
-
98
- /**
99
- * Run package tests.
100
- */
101
- function test(callback) {
102
- const tests = [
103
- testTimerStop,
104
- testTimerRun,
105
- ];
106
- testing.run(tests, callback);
107
- }
108
-
109
- module.exports = {
110
- HighResolutionTimer,
111
- test,
112
- }
113
-
114
- // run tests if invoked directly
115
- if (__filename == process.argv[1]) {
116
- test(testing.show);
117
- }
118
-
package/lib/httpClient.js CHANGED
@@ -1,33 +1,21 @@
1
- 'use strict';
2
-
3
- /**
4
- * Load Test a URL, website or websocket.
5
- * (C) 2013 Alex Fernández.
6
- */
7
-
8
-
9
- // requires
10
- const testing = require('testing');
11
- const urlLib = require('url');
12
- const http = require('http');
13
- const https = require('https');
14
- const qs = require('querystring');
15
- const websocket = require('websocket');
16
- const Log = require('log');
17
- const {HighResolutionTimer} = require('./hrtimer.js');
18
- const headers = require('./headers.js');
19
-
20
- // globals
21
- const log = new Log('info');
1
+ import * as urlLib from 'url'
2
+ import * as http from 'http'
3
+ import * as https from 'https'
4
+ import * as querystring from 'querystring'
5
+ import * as websocket from 'websocket'
6
+ import {HighResolutionTimer} from './hrtimer.js'
7
+ import {addUserAgent} from './headers.js'
8
+ import * as agentkeepalive from 'agentkeepalive'
9
+ import * as HttpsProxyAgent from 'https-proxy-agent'
22
10
 
23
11
 
24
12
  /**
25
13
  * Create a new HTTP client.
26
14
  * Seem parameters below.
27
15
  */
28
- exports.create = function(operation, params) {
16
+ export function create(operation, params) {
29
17
  return new HttpClient(operation, params);
30
- };
18
+ }
31
19
 
32
20
  /**
33
21
  * A client for an HTTP connection.
@@ -58,7 +46,7 @@ class HttpClient {
58
46
  }
59
47
  this.options.agent = false;
60
48
  if (this.params.agentKeepAlive) {
61
- const KeepAlive = (this.options.protocol == 'https:') ? require('agentkeepalive').HttpsAgent : require('agentkeepalive');
49
+ const KeepAlive = (this.options.protocol == 'https:') ? agentkeepalive.HttpsAgent : agentkeepalive;
62
50
  let maxSockets = 10;
63
51
  if (this.params.requestsPerSecond) {
64
52
  maxSockets += Math.floor(this.params.requestsPerSecond);
@@ -74,19 +62,16 @@ class HttpClient {
74
62
  }
75
63
  if (this.params.body) {
76
64
  if (typeof this.params.body == 'string') {
77
- log.debug('Received string body');
78
65
  this.generateMessage = () => this.params.body;
79
66
  } else if (typeof this.params.body == 'object') {
80
- log.debug('Received JSON body');
81
67
  if (this.params.contentType === 'application/x-www-form-urlencoded') {
82
- this.params.body = qs.stringify(this.params.body);
68
+ this.params.body = querystring.stringify(this.params.body);
83
69
  }
84
70
  this.generateMessage = () => this.params.body;
85
71
  } else if (typeof this.params.body == 'function') {
86
- log.debug('Received function body');
87
72
  this.generateMessage = this.params.body;
88
73
  } else {
89
- log.error('Unrecognized body: %s', typeof this.params.body);
74
+ console.error('Unrecognized body: %s', typeof this.params.body);
90
75
  }
91
76
  this.options.headers['Content-Type'] = this.params.contentType || 'text/plain';
92
77
  }
@@ -99,11 +84,10 @@ class HttpClient {
99
84
  console.error('Invalid cookies %j, please use an array or a string', this.params.cookies);
100
85
  }
101
86
  }
102
- headers.addUserAgent(this.options.headers);
87
+ addUserAgent(this.options.headers);
103
88
  if (this.params.secureProtocol) {
104
89
  this.options.secureProtocol = this.params.secureProtocol;
105
90
  }
106
- log.debug('Options: %j', this.options);
107
91
  }
108
92
 
109
93
  /**
@@ -145,7 +129,6 @@ class HttpClient {
145
129
  if (this.options.protocol == 'ws:') {
146
130
  lib = websocket;
147
131
  }
148
- const HttpsProxyAgent = require('https-proxy-agent');
149
132
 
150
133
  // adding proxy configuration
151
134
  if (this.params.proxy) {
@@ -178,7 +161,7 @@ class HttpClient {
178
161
  if (this.params.timeout) {
179
162
  const timeout = parseInt(this.params.timeout);
180
163
  if (!timeout) {
181
- log.error('Invalid timeout %s', this.params.timeout);
164
+ console.error('Invalid timeout %s', this.params.timeout);
182
165
  }
183
166
  request.setTimeout(timeout, () => {
184
167
  requestFinished('Connection timed out');
@@ -200,7 +183,6 @@ class HttpClient {
200
183
  return (error, result) => {
201
184
  let errorCode = null;
202
185
  if (error) {
203
- log.debug('Connection %s failed: %s', id, error);
204
186
  if (result) {
205
187
  errorCode = result.statusCode;
206
188
  if (result.customErrorCode !== undefined) {
@@ -209,8 +191,6 @@ class HttpClient {
209
191
  } else {
210
192
  errorCode = '-1';
211
193
  }
212
- } else {
213
- log.debug('Connection %s ended', id);
214
194
  }
215
195
 
216
196
  const elapsed = this.operation.latency.end(id, errorCode);
@@ -238,10 +218,8 @@ class HttpClient {
238
218
  getConnect(id, callback, contentInspector) {
239
219
  let body = '';
240
220
  return connection => {
241
- log.debug('HTTP client connected to %s with id %s', this.params.url, id);
242
221
  connection.setEncoding('utf8');
243
222
  connection.on('data', chunk => {
244
- log.debug('Body: %s', chunk);
245
223
  body += chunk;
246
224
  });
247
225
  connection.on('error', error => {
@@ -257,6 +235,9 @@ class HttpClient {
257
235
  body: body,
258
236
  headers: connection.headers,
259
237
  };
238
+ if (connection.req.labels) {
239
+ result.labels = connection.req.labels
240
+ }
260
241
  if (contentInspector) {
261
242
  contentInspector(result)
262
243
  }
@@ -272,29 +253,3 @@ class HttpClient {
272
253
  }
273
254
  }
274
255
 
275
- function testHttpClient(callback) {
276
- const options = {
277
- url: 'http://localhost:7357/',
278
- maxSeconds: 0.1,
279
- concurrency: 1,
280
- quiet: true,
281
- };
282
- exports.create({}, options);
283
- testing.success(callback);
284
- }
285
-
286
-
287
- /**
288
- * Run all tests.
289
- */
290
- exports.test = function (callback) {
291
- testing.run([
292
- testHttpClient,
293
- ], callback);
294
- };
295
-
296
- // run tests if invoked directly
297
- if (__filename == process.argv[1]) {
298
- exports.test(testing.show);
299
- }
300
-
package/lib/latency.js CHANGED
@@ -1,30 +1,14 @@
1
- 'use strict';
2
-
3
- /**
4
- * Measure latency for a load test.
5
- * (C) 2013 Alex Fernández.
6
- */
7
-
8
-
9
- // requires
10
- const testing = require('testing');
11
- const util = require('util');
12
- const crypto = require('crypto');
13
- const Log = require('log');
14
-
15
- // globals
16
- const log = new Log('info');
1
+ import * as crypto from 'crypto'
17
2
 
18
3
 
19
4
  /**
20
5
  * Latency measurements. Options can be:
21
6
  * - maxRequests: max number of requests to measure before stopping.
22
7
  * - maxSeconds: max seconds, alternative to max requests.
23
- * - quiet: do not log messages.
24
8
  * An optional callback(error, results) will be called with an error,
25
9
  * or the results after max is reached.
26
10
  */
27
- class Latency {
11
+ export class Latency {
28
12
  constructor(options, callback) {
29
13
  this.options = options
30
14
  this.callback = callback
@@ -45,12 +29,6 @@ class Latency {
45
29
  this.totalsShown = false;
46
30
  this.requestIndex = 0;
47
31
  this.requestIdToIndex = {};
48
- if (options.quiet) {
49
- log.level = Log.NOTICE;
50
- }
51
- if (options.debug) {
52
- log.level = Log.DEBUG;
53
- }
54
32
  }
55
33
 
56
34
  /**
@@ -77,7 +55,6 @@ class Latency {
77
55
  */
78
56
  end(requestId, errorCode) {
79
57
  if (!(requestId in this.requests)) {
80
- log.debug('Message id ' + requestId + ' not found');
81
58
  return -2;
82
59
  }
83
60
  if (!this.running) {
@@ -94,7 +71,6 @@ class Latency {
94
71
  * Accepts an optional error code signaling an error.
95
72
  */
96
73
  add(time, errorCode) {
97
- log.debug('New value: %s', time);
98
74
  this.partialTime += time;
99
75
  this.partialRequests++;
100
76
  this.totalTime += time;
@@ -108,7 +84,6 @@ class Latency {
108
84
  }
109
85
  this.errorCodes[errorCode] += 1;
110
86
  }
111
- log.debug('Partial requests: %s', this.partialRequests);
112
87
  const rounded = Math.floor(time);
113
88
  if (rounded > this.maxLatencyMs) {
114
89
  this.maxLatencyMs = rounded;
@@ -117,7 +92,6 @@ class Latency {
117
92
  this.minLatencyMs = rounded;
118
93
  }
119
94
  if (!this.histogramMs[rounded]) {
120
- log.debug('Initializing histogram for %s', rounded);
121
95
  this.histogramMs[rounded] = 0;
122
96
  }
123
97
  this.histogramMs[rounded] += 1;
@@ -140,10 +114,10 @@ class Latency {
140
114
  if (this.options.maxRequests) {
141
115
  percent = ' (' + Math.round(100 * this.totalRequests / this.options.maxRequests) + '%)';
142
116
  }
143
- log.info('Requests: %s%s, requests per second: %s, mean latency: %s ms', this.totalRequests, percent, results.rps, results.meanLatencyMs);
117
+ console.info('Requests: %s%s, requests per second: %s, mean latency: %s ms', this.totalRequests, percent, results.rps, results.meanLatencyMs);
144
118
  if (this.totalErrors) {
145
119
  percent = Math.round(100 * 10 * this.totalErrors / this.totalRequests) / 10;
146
- log.info('Errors: %s, accumulated errors: %s, %s% of total requests', this.partialErrors, this.totalErrors, percent);
120
+ console.info('Errors: %s, accumulated errors: %s, %s% of total requests', this.partialErrors, this.totalErrors, percent);
147
121
  }
148
122
  this.partialTime = 0;
149
123
  this.partialRequests = 0;
@@ -173,14 +147,11 @@ class Latency {
173
147
  * Check out if the measures are finished.
174
148
  */
175
149
  isFinished() {
176
- log.debug('Total requests %s, max requests: %s', this.totalRequests, this.options.maxRequests);
177
150
  if (this.options.maxRequests && this.totalRequests >= this.options.maxRequests) {
178
- log.debug('Max requests reached: %s', this.totalRequests);
179
151
  return true;
180
152
  }
181
153
  const elapsedSeconds = this.getElapsed(this.initialTime) / 1000;
182
154
  if (this.options.maxSeconds && elapsedSeconds >= this.options.maxSeconds) {
183
- log.debug('Max seconds reached: %s', this.totalRequests);
184
155
  return true;
185
156
  }
186
157
  return false;
@@ -194,7 +165,6 @@ class Latency {
194
165
  if (this.callback) {
195
166
  return this.callback(null, this.getResults());
196
167
  }
197
- this.show();
198
168
  }
199
169
 
200
170
  /**
@@ -220,7 +190,6 @@ class Latency {
220
190
  * Compute the percentiles.
221
191
  */
222
192
  computePercentiles() {
223
- log.debug('Histogram: %s', util.inspect(this.histogramMs));
224
193
  const percentiles = {
225
194
  50: false,
226
195
  90: false,
@@ -233,12 +202,10 @@ class Latency {
233
202
  if (!this.histogramMs[ms]) {
234
203
  continue;
235
204
  }
236
- log.debug('Histogram for %s: %s', ms, this.histogramMs[ms]);
237
205
  counted += this.histogramMs[ms];
238
206
  const percent = counted / this.totalRequests * 100;
239
207
 
240
208
  Object.keys(percentiles).forEach(percentile => {
241
- log.debug('Checking percentile %s for %s', percentile, percent);
242
209
  if (!percentiles[percentile] && percent > percentile) {
243
210
  percentiles[percentile] = ms;
244
211
  }
@@ -256,43 +223,43 @@ class Latency {
256
223
  }
257
224
  this.totalsShown = true;
258
225
  const results = this.getResults();
259
- log.info('');
260
- log.info('Target URL: %s', this.options.url);
226
+ console.info('');
227
+ console.info('Target URL: %s', this.options.url);
261
228
  if (this.options.maxRequests) {
262
- log.info('Max requests: %s', this.options.maxRequests);
229
+ console.info('Max requests: %s', this.options.maxRequests);
263
230
  } else if (this.options.maxSeconds) {
264
- log.info('Max time (s): %s', this.options.maxSeconds);
231
+ console.info('Max time (s): %s', this.options.maxSeconds);
265
232
  }
266
- log.info('Concurrency level: %s', this.options.concurrency);
233
+ console.info('Concurrency level: %s', this.options.concurrency);
267
234
  let agent = 'none';
268
235
  if (this.options.agentKeepAlive) {
269
236
  agent = 'keepalive';
270
237
  }
271
- log.info('Agent: %s', agent);
238
+ console.info('Agent: %s', agent);
272
239
  if (this.options.requestsPerSecond) {
273
- log.info('Requests per second: %s', this.options.requestsPerSecond * this.options.concurrency);
240
+ console.info('Requests per second: %s', this.options.requestsPerSecond * this.options.concurrency);
274
241
  }
275
- log.info('');
276
- log.info('Completed requests: %s', results.totalRequests);
277
- log.info('Total errors: %s', results.totalErrors);
278
- log.info('Total time: %s s', results.totalTimeSeconds);
279
- log.info('Requests per second: %s', results.rps);
280
- log.info('Mean latency: %s ms', results.meanLatencyMs);
281
- log.info('');
282
- log.info('Percentage of the requests served within a certain time');
242
+ console.info('');
243
+ console.info('Completed requests: %s', results.totalRequests);
244
+ console.info('Total errors: %s', results.totalErrors);
245
+ console.info('Total time: %s s', results.totalTimeSeconds);
246
+ console.info('Requests per second: %s', results.rps);
247
+ console.info('Mean latency: %s ms', results.meanLatencyMs);
248
+ console.info('');
249
+ console.info('Percentage of the requests served within a certain time');
283
250
 
284
251
  Object.keys(results.percentiles).forEach(percentile => {
285
- log.info(' %s% %s ms', percentile, results.percentiles[percentile]);
252
+ console.info(' %s% %s ms', percentile, results.percentiles[percentile]);
286
253
  });
287
254
 
288
- log.info(' 100% %s ms (longest request)', this.maxLatencyMs);
255
+ console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
289
256
  if (results.totalErrors) {
290
- log.info('');
291
- log.info(' 100% %s ms (longest request)', this.maxLatencyMs);
292
- log.info('');
257
+ console.info('');
258
+ console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
259
+ console.info('');
293
260
  Object.keys(results.errorCodes).forEach(errorCode => {
294
261
  const padding = ' '.repeat(errorCode.length < 4 ? 4 - errorCode.length : 1);
295
- log.info(' %s%s: %s errors', padding, errorCode, results.errorCodes[errorCode]);
262
+ console.info(' %s%s: %s errors', padding, errorCode, results.errorCodes[errorCode]);
296
263
  });
297
264
  }
298
265
  }
@@ -308,92 +275,3 @@ function createId() {
308
275
  return hash.update(value).digest('hex').toLowerCase();
309
276
  }
310
277
 
311
- /**
312
- * Test latency ids.
313
- */
314
- function testLatencyIds(callback) {
315
- const latency = new Latency({});
316
- const firstId = latency.start();
317
- testing.assert(firstId, 'Invalid first latency id %s', firstId, callback);
318
- const secondId = latency.start();
319
- testing.assert(secondId, 'Invalid second latency id', callback);
320
- testing.assert(firstId != secondId, 'Repeated latency ids', callback);
321
- testing.success(callback);
322
- }
323
-
324
- /**
325
- * Test latency measurements.
326
- */
327
- function testLatencyRequests(callback) {
328
- const options = {
329
- maxRequests: 10,
330
- };
331
- const errorCode = '500';
332
- const latency = new Latency(options, (error, result) => {
333
- testing.check(error, 'Could not compute latency', callback);
334
- testing.assertEquals(result.totalRequests, 10, 'Invalid total requests', callback);
335
- testing.assertEquals(result.totalErrors, 1, 'Invalid total errors', callback);
336
- testing.assert(errorCode in result.errorCodes, 'Error code not found', callback);
337
- testing.assertEquals(result.errorCodes[errorCode], 1, 'Should have one ' + errorCode, callback);
338
- testing.success(callback);
339
- });
340
- let id;
341
- for (let i = 0; i < 9; i++) {
342
- id = latency.start();
343
- latency.end(id);
344
- }
345
- id = latency.start();
346
- latency.end(id, errorCode);
347
- }
348
-
349
- /**
350
- * Check that percentiles are correct.
351
- */
352
- function testLatencyPercentiles(callback) {
353
- const options = {
354
- maxRequests: 10
355
- };
356
- const latency = new Latency(options, error => {
357
- testing.check(error, 'Error while testing latency percentiles', callback);
358
- const percentiles = latency.getResults().percentiles;
359
-
360
- Object.keys(percentiles).forEach(percentile => {
361
- testing.assert(percentiles[percentile] !== false, 'Empty percentile for %s', percentile, callback);
362
- });
363
-
364
- testing.success(percentiles, callback);
365
- });
366
- for (let ms = 1; ms <= 10; ms++) {
367
- log.debug('Starting %s', ms);
368
- (function() {
369
- const id = latency.start();
370
- setTimeout(() => {
371
- log.debug('Ending %s', id);
372
- latency.end(id);
373
- }, ms);
374
- })();
375
- }
376
- }
377
-
378
- /**
379
- * Run package tests.
380
- */
381
- function test(callback) {
382
- const tests = [
383
- testLatencyIds,
384
- testLatencyRequests,
385
- testLatencyPercentiles,
386
- ];
387
- testing.run(tests, callback);
388
- }
389
-
390
- module.exports = {
391
- Latency,
392
- test,
393
- }
394
-
395
- // run tests if invoked directly
396
- if (__filename == process.argv[1]) {
397
- test(testing.show);
398
- }
399
-