loadtest 6.0.0 → 6.2.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/README.md CHANGED
@@ -84,7 +84,7 @@ but the resulting figure is much more robust.
84
84
  `loadtest` is also quite extensible.
85
85
  Using the provided API it is very easy to integrate loadtest with your package, and run programmatic load tests.
86
86
  loadtest makes it very easy to run load tests as part of systems tests, before deploying a new version of your software.
87
- The results include mean response times and percentiles,
87
+ The result includes mean response times and percentiles,
88
88
  so that you can abort deployment e.g. if 99% of the requests don't finish in 10 ms or less.
89
89
 
90
90
  ### Usage Don'ts
@@ -111,6 +111,7 @@ The following parameters are compatible with Apache ab.
111
111
  #### `-n requests`
112
112
 
113
113
  Number of requests to send out.
114
+ Default is no limit; will keep on sending if not specified.
114
115
 
115
116
  Note: the total number of requests sent can be bigger than the parameter if there is a concurrency parameter;
116
117
  loadtest will report just the first `n`.
@@ -119,6 +120,7 @@ loadtest will report just the first `n`.
119
120
 
120
121
  loadtest will create a certain number of clients; this parameter controls how many.
121
122
  Requests from them will arrive concurrently to the server.
123
+ Default value is 1.
122
124
 
123
125
  Note: requests are not sent in parallel (from different processes),
124
126
  but concurrently (a second request may be sent before the first has been answered).
@@ -126,6 +128,7 @@ but concurrently (a second request may be sent before the first has been answere
126
128
  #### `-t timelimit`
127
129
 
128
130
  Max number of seconds to wait until requests no longer go out.
131
+ Default is no limit; will keep on sending if not specified.
129
132
 
130
133
  Note: this is different than Apache `ab`, which stops _receiving_ requests after the given seconds.
131
134
 
@@ -175,15 +178,17 @@ Send the string as the PATCH body. E.g.: `-A '{"key": "a9acf03f"}'`
175
178
 
176
179
  #### `-m method`
177
180
 
178
- Send method to link. Accept: [GET, POST, PUT, DELETE, PATCH, get, post, put, delete, patch], Default is GET
179
- E.g.: -m POST
181
+ Set method that will be sent to the test URL.
182
+ Accepts: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`,
183
+ and lowercase versions. Default is `GET`.
184
+ Example: `-m POST`.
180
185
 
181
- #### `--data POST some variables`
186
+ #### `--data body`
182
187
 
183
- Send some data. It does not support method GET.
184
- E.g: `--data '{"username": "test", "password": "test"}' -T 'application/x-www-form-urlencoded' -m POST`
188
+ Add some data to send in the body. It does not support method GET.
189
+ Requires setting the method with `-m` and the type with `-T`.
190
+ Example: `--data '{"username": "test", "password": "test"}' -T 'application/x-www-form-urlencoded' -m POST`
185
191
 
186
- It required `-m` and `-T 'application/x-www-form-urlencoded'`
187
192
 
188
193
  #### `-p POST-file`
189
194
 
@@ -256,6 +261,7 @@ The following parameters are _not_ compatible with Apache ab.
256
261
 
257
262
  Controls the number of requests per second that are sent.
258
263
  Can be fractional, e.g. `--rps 0.5` sends one request every two seconds.
264
+ Not used by default: each request is sent as soon as the previous one is responded.
259
265
 
260
266
  Note: Concurrency doesn't affect the final number of requests per second,
261
267
  since rps will be shared by all the clients. E.g.:
@@ -309,17 +315,15 @@ Open connections using keep-alive.
309
315
 
310
316
  Note: instead of using the default agent, this option is now an alias for `-k`.
311
317
 
312
- #### `--quiet` (deprecated)
318
+ #### `--quiet`
313
319
 
314
320
  Do not show any messages.
315
321
 
316
- Note: deprecated in version 6+, shows a warning.
317
-
318
322
  #### `--debug` (deprecated)
319
323
 
320
324
  Show debug messages.
321
325
 
322
- Note: deprecated in version 6+, shows a warning.
326
+ Note: deprecated in version 6+.
323
327
 
324
328
  #### `--insecure`
325
329
 
@@ -383,7 +387,7 @@ with concurrency 10 (only relevant results are shown):
383
387
  99% 14 ms
384
388
  100% 35997 ms (longest request)
385
389
 
386
- Results were quite erratic, with some requests taking up to 36 seconds;
390
+ The result was quite erratic, with some requests taking up to 36 seconds;
387
391
  this suggests that Node.js is queueing some requests for a long time, and answering them irregularly.
388
392
  Now we will try a fixed rate of 1000 rps:
389
393
 
@@ -438,10 +442,10 @@ We now know that our server can accept 500 rps without problems.
438
442
  Not bad for a single-process naïve Node.js server...
439
443
  We may refine our results further to find at which point from 500 to 1000 rps our server breaks down.
440
444
 
441
- But instead let us research how to improve the results.
445
+ But instead let us research how to improve the result.
442
446
  One obvious candidate is to add keep-alive to the requests so we don't have to create
443
447
  a new connection for every request.
444
- The results (with the same test server) are impressive:
448
+ The result (with the same test server) is impressive:
445
449
 
446
450
  $ loadtest http://localhost:7357/ -t 20 -c 10 -k
447
451
  ...
@@ -477,7 +481,28 @@ thus allowing you to load test your application in your own tests.
477
481
 
478
482
  ### Invoke Load Test
479
483
 
480
- To run a load test, just call the exported function `loadTest()` with a set of options and an optional callback:
484
+ To run a load test, just `await` for the exported function `loadTest()` with the desired options, described below:
485
+
486
+ ```javascript
487
+ import {loadTest} from 'loadtest'
488
+
489
+ const options = {
490
+ url: 'http://localhost:8000',
491
+ maxRequests: 1000,
492
+ }
493
+ const result = await loadTest(options)
494
+ result.show()
495
+ console.log('Tests run successfully')
496
+ })
497
+ ```
498
+
499
+ The call returns a `Result` object that contains all info about the load test, also described below.
500
+ Call `result.show()` to display the results in the standard format on the console.
501
+
502
+ As a legacy from before promises existed,
503
+ if an optional callback is passed as second parameter then it will not behave as `async`:
504
+ the callback `function(error, result)` will be invoked when the max number of requests is reached,
505
+ or when the max number of seconds has elapsed.
481
506
 
482
507
  ```javascript
483
508
  import {loadTest} from 'loadtest'
@@ -490,16 +515,51 @@ loadTest(options, function(error, result) {
490
515
  if (error) {
491
516
  return console.error('Got an error: %s', error)
492
517
  }
518
+ result.show()
493
519
  console.log('Tests run successfully')
494
520
  })
495
521
  ```
496
522
 
497
- The callback `function(error, result)` will be invoked when the max number of requests is reached,
498
- or when the max number of seconds has elapsed.
499
523
 
500
524
  Beware: if there are no `maxRequests` and no `maxSeconds`, then tests will run forever
501
525
  and will not call the callback.
502
526
 
527
+ ### Result
528
+
529
+ The latency result returned at the end of the load test contains a full set of data, including:
530
+ mean latency, number of errors and percentiles.
531
+ An example follows:
532
+
533
+ ```javascript
534
+ {
535
+ url: 'http://localhost:80/',
536
+ maxRequests: 1000,
537
+ maxSeconds: 0,
538
+ concurrency: 10,
539
+ agent: 'none',
540
+ requestsPerSecond: undefined,
541
+ totalRequests: 1000,
542
+ percentiles: {
543
+ '50': 7,
544
+ '90': 10,
545
+ '95': 11,
546
+ '99': 15
547
+ },
548
+ rps: 2824,
549
+ totalTimeSeconds: 0.354108,
550
+ meanLatencyMs: 7.72,
551
+ maxLatencyMs: 20,
552
+ totalErrors: 3,
553
+ errorCodes: {
554
+ '0': 1,
555
+ '500': 2
556
+ },
557
+ }
558
+ ```
559
+
560
+ The `result` object also has a `result.show()` function
561
+ that displays the results on the console in the standard format.
562
+
503
563
  ### Options
504
564
 
505
565
  All options but `url` are, as their name implies, optional.
@@ -587,6 +647,9 @@ function(params, options, client, callback) {
587
647
  }
588
648
  ```
589
649
 
650
+ See [`sample/request-generator.js`](sample/request-generator.js) for some sample code including a body
651
+ (or [`sample/request-generator.ts`](sample/request-generator.ts) for ES6/TypeScript).
652
+
590
653
  #### `agentKeepAlive`
591
654
 
592
655
  Use an agent with 'Connection: Keep-alive'.
@@ -659,11 +722,11 @@ loadTest(options, function(error) {
659
722
 
660
723
  #### `statusCallback`
661
724
 
662
- If present, this function executes after every request operation completes. Provides immediate access to test results while the
725
+ If present, this function executes after every request operation completes. Provides immediate access to the test result while the
663
726
  test batch is still running. This can be used for more detailed custom logging or developing your own spreadsheet or
664
- statistical analysis of results.
727
+ statistical analysis of the result.
665
728
 
666
- The results and error passed to the callback are in the same format as the results passed to the final callback.
729
+ The result and error passed to the callback are in the same format as the result passed to the final callback.
667
730
 
668
731
  In addition, the following three properties are added to the `result` object:
669
732
 
@@ -673,6 +736,19 @@ In addition, the following three properties are added to the `result` object:
673
736
 
674
737
  You will need to check if `error` is populated in order to determine which object to check for these properties.
675
738
 
739
+ The second parameter contains info about the current request:
740
+
741
+ ```javascript
742
+ {
743
+ host: 'localhost',
744
+ path: '/',
745
+ method: 'GET',
746
+ statusCode: 200,
747
+ body: '<html><body>hi</body></html>',
748
+ headers: [...]
749
+ }
750
+ ```
751
+
676
752
  Example:
677
753
 
678
754
  ```javascript
@@ -699,7 +775,6 @@ loadTest(options, function(error) {
699
775
  console.log('Tests run successfully')
700
776
  })
701
777
  ```
702
-
703
778
 
704
779
  In some situations request data needs to be available in the statusCallBack.
705
780
  This data can be assigned to `request.labels` in the requestGenerator:
@@ -753,57 +828,26 @@ function contentInspector(result) {
753
828
  }
754
829
  },
755
830
  ```
756
-
757
- ### Results
758
-
759
- The latency results passed to your callback at the end of the load test contains a full set of data, including:
760
- mean latency, number of errors and percentiles.
761
- An example follows:
762
-
763
- ```javascript
764
- {
765
- totalRequests: 1000,
766
- percentiles: {
767
- '50': 7,
768
- '90': 10,
769
- '95': 11,
770
- '99': 15
771
- },
772
- rps: 2824,
773
- totalTimeSeconds: 0.354108,
774
- meanLatencyMs: 7.72,
775
- maxLatencyMs: 20,
776
- totalErrors: 3,
777
- errorCodes: {
778
- '0': 1,
779
- '500': 2
780
- }
781
- }
782
- ```
783
-
784
- The second parameter contains info about the current request:
785
-
786
- ```javascript
787
- {
788
- host: 'localhost',
789
- path: '/',
790
- method: 'GET',
791
- statusCode: 200,
792
- body: '<html><body>hi</body></html>',
793
- headers: [...]
794
- }
795
- ```
796
831
 
797
832
  ### Start Test Server
798
833
 
799
- To start the test server use the exported function `startServer()` with a set of options and an optional callback:
834
+ To start the test server use the exported function `startServer()` with a set of options:
800
835
 
801
836
  ```javascript
802
837
  import {startServer} from 'loadtest'
803
- const server = startServer({port: 8000})
838
+ const server = await startServer({port: 8000})
839
+ // do your thing
840
+ await server.close()
804
841
  ```
805
842
 
806
- This function returns an HTTP server which can be `close()`d when it is no longer useful.
843
+ This function returns when the server is up and running,
844
+ with an HTTP server which can be `close()`d when it is no longer useful.
845
+ As a legacy from before promises existed,
846
+ if an optional callback is passed as second parameter then it will not behave as `async`:
847
+
848
+ ```
849
+ const server = startServer({port: 8000}, error => console.error(error))
850
+ ```
807
851
 
808
852
  The following options are available.
809
853
 
package/bin/loadtest.js CHANGED
@@ -31,8 +31,8 @@ const options = stdio.getopt({
31
31
  insecure: {description: 'Allow self-signed certificates over https'},
32
32
  key: {args: 1, description: 'The client key to use'},
33
33
  cert: {args: 1, description: 'The client certificate to use'},
34
+ quiet: {description: 'Do not log any messages'},
34
35
  agent: {description: 'Use a keep-alive http agent (deprecated)'},
35
- quiet: {description: 'Do not log any messages (deprecated)'},
36
36
  debug: {description: 'Show debug messages (deprecated)'}
37
37
  });
38
38
 
@@ -52,7 +52,8 @@ async function processAndRun(options) {
52
52
  }
53
53
  options.url = options.args[0];
54
54
  try {
55
- loadTest(options)
55
+ const result = await loadTest(options)
56
+ result.show()
56
57
  } catch(error) {
57
58
  console.error(error.message)
58
59
  help()
package/lib/httpClient.js CHANGED
@@ -28,6 +28,7 @@ class HttpClient {
28
28
  constructor(operation, params) {
29
29
  this.operation = operation
30
30
  this.params = params
31
+ this.stopped = false
31
32
  this.init();
32
33
  }
33
34
 
@@ -45,11 +46,15 @@ class HttpClient {
45
46
  this.options.key = this.params.key;
46
47
  }
47
48
  this.options.agent = false;
49
+ if (this.params.requestsPerSecond) {
50
+ // rps for each client is total / concurrency (# of clients)
51
+ this.options.requestsPerSecond = this.params.requestsPerSecond / this.params.concurrency
52
+ }
48
53
  if (this.params.agentKeepAlive) {
49
- const KeepAlive = (this.options.protocol == 'https:') ? agentkeepalive.HttpsAgent : agentkeepalive;
54
+ const KeepAlive = (this.options.protocol == 'https:') ? agentkeepalive.HttpsAgent : agentkeepalive.default;
50
55
  let maxSockets = 10;
51
- if (this.params.requestsPerSecond) {
52
- maxSockets += Math.floor(this.params.requestsPerSecond);
56
+ if (this.options.requestsPerSecond) {
57
+ maxSockets += Math.floor(this.options.requestsPerSecond);
53
58
  }
54
59
  this.options.agent = new KeepAlive({
55
60
  maxSockets: maxSockets,
@@ -71,7 +76,7 @@ class HttpClient {
71
76
  } else if (typeof this.params.body == 'function') {
72
77
  this.generateMessage = this.params.body;
73
78
  } else {
74
- console.error('Unrecognized body: %s', typeof this.params.body);
79
+ throw new Error(`Unrecognized body: ${typeof this.params.body}`);
75
80
  }
76
81
  this.options.headers['Content-Type'] = this.params.contentType || 'text/plain';
77
82
  }
@@ -81,7 +86,7 @@ class HttpClient {
81
86
  } else if (typeof this.params.cookies == 'string') {
82
87
  this.options.headers.Cookie = this.params.cookies;
83
88
  } else {
84
- console.error('Invalid cookies %j, please use an array or a string', this.params.cookies);
89
+ throw new Error(`Invalid cookies ${JSON.stringify(this.params.cookies)}, please use an array or a string`);
85
90
  }
86
91
  }
87
92
  addUserAgent(this.options.headers);
@@ -94,10 +99,15 @@ class HttpClient {
94
99
  * Start the HTTP client.
95
100
  */
96
101
  start() {
97
- if (!this.params.requestsPerSecond) {
102
+ if (this.stopped) {
103
+ // solves testing issue: with requestsPerSecond clients are started at random,
104
+ // so sometimes they are stopped before they have even started
105
+ return
106
+ }
107
+ if (!this.options.requestsPerSecond) {
98
108
  return this.makeRequest();
99
109
  }
100
- const interval = 1000 / this.params.requestsPerSecond;
110
+ const interval = 1000 / this.options.requestsPerSecond;
101
111
  this.requestTimer = new HighResolutionTimer(interval, () => this.makeRequest());
102
112
  }
103
113
 
@@ -105,6 +115,7 @@ class HttpClient {
105
115
  * Stop the HTTP client.
106
116
  */
107
117
  stop() {
118
+ this.stopped = true
108
119
  if (this.requestTimer) {
109
120
  this.requestTimer.stop();
110
121
  }
@@ -133,7 +144,6 @@ class HttpClient {
133
144
  // adding proxy configuration
134
145
  if (this.params.proxy) {
135
146
  const proxy = this.params.proxy;
136
- //console.log('using proxy server %j', proxy);
137
147
  const agent = new HttpsProxyAgent(proxy);
138
148
  this.options.agent = agent;
139
149
  }
@@ -154,7 +164,8 @@ class HttpClient {
154
164
  delete this.options.headers['Content-Length'];
155
165
  }
156
166
  if (typeof this.params.requestGenerator == 'function') {
157
- request = this.params.requestGenerator(this.params, this.options, lib.request, this.getConnect(id, requestFinished, this.params.contentInspector));
167
+ const connect = this.getConnect(id, requestFinished, this.params.contentInspector)
168
+ request = this.params.requestGenerator(this.params, this.options, lib.request, connect);
158
169
  } else {
159
170
  request = lib.request(this.options, this.getConnect(id, requestFinished, this.params.contentInspector));
160
171
  }
@@ -205,7 +216,7 @@ class HttpClient {
205
216
  result.instanceIndex = this.operation.instanceIndex;
206
217
  }
207
218
  let callback;
208
- if (!this.params.requestsPerSecond) {
219
+ if (!this.options.requestsPerSecond) {
209
220
  callback = this.makeRequest.bind(this);
210
221
  }
211
222
  this.operation.callback(error, result, callback);
package/lib/latency.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import * as crypto from 'crypto'
2
+ import {Result} from './result.js'
2
3
 
3
4
 
4
5
  /**
5
6
  * Latency measurements. Options can be:
6
7
  * - maxRequests: max number of requests to measure before stopping.
7
8
  * - maxSeconds: max seconds, alternative to max requests.
8
- * An optional callback(error, results) will be called with an error,
9
- * or the results after max is reached.
9
+ * An optional callback(error, result) will be called with an error,
10
+ * or the result after max is reached.
10
11
  */
11
12
  export class Latency {
12
13
  constructor(options, callback) {
@@ -106,7 +107,7 @@ export class Latency {
106
107
  showPartial() {
107
108
  const elapsedSeconds = this.getElapsed(this.lastShown) / 1000;
108
109
  const meanTime = this.partialTime / this.partialRequests || 0.0;
109
- const results = {
110
+ const result = {
110
111
  meanLatencyMs: Math.round(meanTime * 10) / 10,
111
112
  rps: Math.round(this.partialRequests / elapsedSeconds)
112
113
  };
@@ -114,10 +115,12 @@ export class Latency {
114
115
  if (this.options.maxRequests) {
115
116
  percent = ' (' + Math.round(100 * this.totalRequests / this.options.maxRequests) + '%)';
116
117
  }
117
- console.info('Requests: %s%s, requests per second: %s, mean latency: %s ms', this.totalRequests, percent, results.rps, results.meanLatencyMs);
118
- if (this.totalErrors) {
119
- percent = Math.round(100 * 10 * this.totalErrors / this.totalRequests) / 10;
120
- console.info('Errors: %s, accumulated errors: %s, %s% of total requests', this.partialErrors, this.totalErrors, percent);
118
+ if (!this.options.quiet) {
119
+ console.info('Requests: %s%s, requests per second: %s, mean latency: %s ms', this.totalRequests, percent, result.rps, result.meanLatencyMs);
120
+ if (this.totalErrors) {
121
+ percent = Math.round(100 * 10 * this.totalErrors / this.totalRequests) / 10;
122
+ console.info('Errors: %s, accumulated errors: %s, %s% of total requests', this.partialErrors, this.totalErrors, percent);
123
+ }
121
124
  }
122
125
  this.partialTime = 0;
123
126
  this.partialRequests = 0;
@@ -163,27 +166,16 @@ export class Latency {
163
166
  finish() {
164
167
  this.running = false;
165
168
  if (this.callback) {
166
- return this.callback(null, this.getResults());
169
+ return this.callback(null, this.getResult());
167
170
  }
168
171
  }
169
172
 
170
173
  /**
171
- * Get final results.
174
+ * Get final result.
172
175
  */
173
- getResults() {
174
- const elapsedSeconds = this.getElapsed(this.initialTime) / 1000;
175
- const meanTime = this.totalTime / this.totalRequests;
176
- return {
177
- totalRequests: this.totalRequests,
178
- totalErrors: this.totalErrors,
179
- totalTimeSeconds: elapsedSeconds,
180
- rps: Math.round(this.totalRequests / elapsedSeconds),
181
- meanLatencyMs: Math.round(meanTime * 10) / 10,
182
- maxLatencyMs: this.maxLatencyMs,
183
- minLatencyMs: this.minLatencyMs,
184
- percentiles: this.computePercentiles(),
185
- errorCodes: this.errorCodes
186
- };
176
+ getResult() {
177
+ const result = new Result(this.options, this)
178
+ return result
187
179
  }
188
180
 
189
181
  /**
@@ -215,57 +207,18 @@ export class Latency {
215
207
  }
216
208
 
217
209
  /**
218
- * Show final results.
210
+ * Show final result.
219
211
  */
220
212
  show() {
221
213
  if (this.totalsShown) {
222
214
  return;
223
215
  }
224
216
  this.totalsShown = true;
225
- const results = this.getResults();
226
- console.info('');
227
- console.info('Target URL: %s', this.options.url);
228
- if (this.options.maxRequests) {
229
- console.info('Max requests: %s', this.options.maxRequests);
230
- } else if (this.options.maxSeconds) {
231
- console.info('Max time (s): %s', this.options.maxSeconds);
232
- }
233
- console.info('Concurrency level: %s', this.options.concurrency);
234
- let agent = 'none';
235
- if (this.options.agentKeepAlive) {
236
- agent = 'keepalive';
237
- }
238
- console.info('Agent: %s', agent);
239
- if (this.options.requestsPerSecond) {
240
- console.info('Requests per second: %s', this.options.requestsPerSecond * this.options.concurrency);
241
- }
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');
250
-
251
- Object.keys(results.percentiles).forEach(percentile => {
252
- console.info(' %s% %s ms', percentile, results.percentiles[percentile]);
253
- });
254
-
255
- console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
256
- if (results.totalErrors) {
257
- console.info('');
258
- console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
259
- console.info('');
260
- Object.keys(results.errorCodes).forEach(errorCode => {
261
- const padding = ' '.repeat(errorCode.length < 4 ? 4 - errorCode.length : 1);
262
- console.info(' %s%s: %s errors', padding, errorCode, results.errorCodes[errorCode]);
263
- });
264
- }
217
+ const result = this.getResult();
218
+ result.show()
265
219
  }
266
220
  }
267
221
 
268
-
269
222
  /**
270
223
  * Create a unique, random token.
271
224
  */
package/lib/loadtest.js CHANGED
@@ -14,33 +14,49 @@ https.globalAgent.maxSockets = 1000;
14
14
 
15
15
  /**
16
16
  * Run a load test.
17
- * Options is an object which may have:
18
- * - url: mandatory URL to access.
19
- * - concurrency: how many concurrent clients to use.
20
- * - maxRequests: how many requests to send
21
- * - maxSeconds: how long to run the tests.
22
- * - cookies: a string or an array of strings, each with name:value.
23
- * - headers: a map with headers: {key1: value1, key2: value2}.
24
- * - method: the method to use: POST, PUT. Default: GET, what else.
25
- * - body: the contents to send along a POST or PUT request.
26
- * - contentType: the MIME type to use for the body, default text/plain.
27
- * - requestsPerSecond: how many requests per second to send.
28
- * - agentKeepAlive: if true, then use connection keep-alive.
29
- * - indexParam: string to replace with a unique index.
30
- * - insecure: allow https using self-signed certs.
31
- * - debug: show debug messages (deprecated).
32
- * - quiet: do not log any messages (deprecated).
33
- * An optional callback will be called if/when the test finishes.
17
+ * Parameters:
18
+ * - `options`: an object which may have:
19
+ * - url [string]: URL to access (mandatory).
20
+ * - concurrency [number]: how many concurrent clients to use.
21
+ * - maxRequests [number]: how many requests to send
22
+ * - maxSeconds [number]: how long to run the tests.
23
+ * - cookies [array]: a string or an array of strings, each with name:value.
24
+ * - headers [map]: a map with headers: {key1: value1, key2: value2}.
25
+ * - method [string]: the method to use: POST, PUT. Default: GET, what else.
26
+ * - body [string]: the contents to send along a POST or PUT request.
27
+ * - contentType [string]: the MIME type to use for the body, default text/plain.
28
+ * - requestsPerSecond [number]: how many requests per second to send.
29
+ * - agentKeepAlive: if true, then use connection keep-alive.
30
+ * - indexParam [string]: string to replace with a unique index.
31
+ * - insecure: allow https using self-signed certs.
32
+ * - secureProtocol [string]: TLS/SSL secure protocol method to use.
33
+ * - proxy [string]: use a proxy for requests e.g. http://localhost:8080.
34
+ * - quiet: do not log any messages.
35
+ * - debug: show debug messages (deprecated).
36
+ * - `callback`: optional `function(result, error)` called if/when the test finishes;
37
+ * if not present a promise is returned.
34
38
  */
35
39
  export function loadTest(options, callback) {
36
- processOptions(options, error => {
37
- if (error) {
38
- if (callback) return callback(error)
39
- throw new error
40
- }
41
- const operation = new Operation(options, callback);
40
+ if (!callback) {
41
+ return loadTestAsync(options)
42
+ }
43
+ loadTestAsync(options).then(result => callback(null, result)).catch(error => callback(error))
44
+ }
45
+
46
+ async function loadTestAsync(options) {
47
+ const processed = await processOptions(options)
48
+ return await runOperation(processed)
49
+ }
50
+
51
+ function runOperation(options) {
52
+ return new Promise((resolve, reject) => {
53
+ const operation = new Operation(options, (error, result) => {
54
+ if (error) {
55
+ return reject(error)
56
+ }
57
+ return resolve(result)
58
+ });
42
59
  operation.start();
43
- return operation;
44
60
  })
45
61
  }
46
62
 
@@ -93,7 +109,7 @@ class Operation {
93
109
  next();
94
110
  }
95
111
  if (this.options.statusCallback) {
96
- this.options.statusCallback(error, result, this.latency.getResults());
112
+ this.options.statusCallback(error, result, this.latency.getResult());
97
113
  }
98
114
  }
99
115
 
@@ -143,20 +159,19 @@ class Operation {
143
159
  * Stop clients.
144
160
  */
145
161
  stop() {
162
+ this.running = false;
163
+ this.latency.running = false;
146
164
  if (this.showTimer) {
147
165
  this.showTimer.stop();
148
166
  }
149
167
  if (this.stopTimeout) {
150
168
  clearTimeout(this.stopTimeout);
151
169
  }
152
- this.running = false;
153
- this.latency.running = false;
154
-
155
170
  Object.keys(this.clients).forEach(index => {
156
171
  this.clients[index].stop();
157
172
  });
158
173
  if (this.finalCallback) {
159
- const result = this.latency.getResults();
174
+ const result = this.latency.getResult();
160
175
  result.instanceIndex = this.instanceIndex;
161
176
  this.finalCallback(null, result);
162
177
  } else {