loadtest 5.2.0 → 6.1.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/.eslintrc +1 -1
- package/README.md +79 -59
- package/bin/loadtest.js +28 -154
- package/bin/testserver.js +5 -12
- package/index.js +6 -12
- package/lib/baseClient.js +5 -22
- package/lib/config.js +9 -24
- package/lib/headers.js +3 -64
- package/lib/hrtimer.js +2 -55
- package/lib/httpClient.js +16 -64
- package/lib/latency.js +18 -175
- package/lib/loadtest.js +48 -165
- package/lib/options.js +152 -0
- package/lib/show.js +47 -0
- package/lib/testserver.js +33 -78
- package/lib/websocket.js +8 -54
- package/package.json +8 -8
- package/sample/post-file.js +13 -0
- package/sample/request-generator.js +3 -5
- package/sample/request-generator.ts +2 -2
- package/test/all.js +33 -0
- package/test/body-generator.js +42 -0
- package/test/headers.js +51 -0
- package/test/hrtimer.js +39 -0
- package/test/httpClient.js +22 -0
- package/{lib → test}/integration.js +45 -39
- package/test/latency.js +81 -0
- package/test/loadtest.js +96 -0
- package/{lib → test}/request-generator.js +10 -21
- package/test/testserver.js +25 -0
- package/test/websocket.js +23 -0
- package/test.js +0 -36
- /package/{test → sample}/.loadtestrc +0 -0
package/.eslintrc
CHANGED
package/README.md
CHANGED
|
@@ -33,8 +33,9 @@ For access to the API just add package `loadtest` to your `package.json` devDepe
|
|
|
33
33
|
|
|
34
34
|
### Compatibility
|
|
35
35
|
|
|
36
|
-
Versions
|
|
36
|
+
Versions 6 and later should be used at least with Node.js v16 or later:
|
|
37
37
|
|
|
38
|
+
* Node.js v16 or later: ^6.0.0
|
|
38
39
|
* Node.js v10 or later: ^5.0.0
|
|
39
40
|
* Node.js v8 or later: 4.x.y.
|
|
40
41
|
* Node.js v6 or earlier: ^3.1.0.
|
|
@@ -83,7 +84,7 @@ but the resulting figure is much more robust.
|
|
|
83
84
|
`loadtest` is also quite extensible.
|
|
84
85
|
Using the provided API it is very easy to integrate loadtest with your package, and run programmatic load tests.
|
|
85
86
|
loadtest makes it very easy to run load tests as part of systems tests, before deploying a new version of your software.
|
|
86
|
-
The
|
|
87
|
+
The result includes mean response times and percentiles,
|
|
87
88
|
so that you can abort deployment e.g. if 99% of the requests don't finish in 10 ms or less.
|
|
88
89
|
|
|
89
90
|
### Usage Don'ts
|
|
@@ -110,6 +111,7 @@ The following parameters are compatible with Apache ab.
|
|
|
110
111
|
#### `-n requests`
|
|
111
112
|
|
|
112
113
|
Number of requests to send out.
|
|
114
|
+
Default is no limit; will keep on sending if not specified.
|
|
113
115
|
|
|
114
116
|
Note: the total number of requests sent can be bigger than the parameter if there is a concurrency parameter;
|
|
115
117
|
loadtest will report just the first `n`.
|
|
@@ -118,6 +120,7 @@ loadtest will report just the first `n`.
|
|
|
118
120
|
|
|
119
121
|
loadtest will create a certain number of clients; this parameter controls how many.
|
|
120
122
|
Requests from them will arrive concurrently to the server.
|
|
123
|
+
Default value is 1.
|
|
121
124
|
|
|
122
125
|
Note: requests are not sent in parallel (from different processes),
|
|
123
126
|
but concurrently (a second request may be sent before the first has been answered).
|
|
@@ -125,6 +128,7 @@ but concurrently (a second request may be sent before the first has been answere
|
|
|
125
128
|
#### `-t timelimit`
|
|
126
129
|
|
|
127
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.
|
|
128
132
|
|
|
129
133
|
Note: this is different than Apache `ab`, which stops _receiving_ requests after the given seconds.
|
|
130
134
|
|
|
@@ -174,59 +178,63 @@ Send the string as the PATCH body. E.g.: `-A '{"key": "a9acf03f"}'`
|
|
|
174
178
|
|
|
175
179
|
#### `-m method`
|
|
176
180
|
|
|
177
|
-
|
|
178
|
-
|
|
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`.
|
|
179
185
|
|
|
180
|
-
#### `--data
|
|
186
|
+
#### `--data body`
|
|
181
187
|
|
|
182
|
-
|
|
183
|
-
|
|
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`
|
|
184
191
|
|
|
185
|
-
It required `-m` and `-T 'application/x-www-form-urlencoded'`
|
|
186
192
|
|
|
187
193
|
#### `-p POST-file`
|
|
188
194
|
|
|
189
195
|
Send the data contained in the given file in the POST body.
|
|
190
196
|
Remember to set `-T` to the correct content-type.
|
|
191
197
|
|
|
192
|
-
If `POST-file` has `.js` extension it will be `
|
|
193
|
-
should `export` a
|
|
198
|
+
If `POST-file` has `.js` extension it will be `import`ed. It should be a valid node module and it
|
|
199
|
+
should `export` a default function, which is invoked with an automatically generated request identifier
|
|
194
200
|
to provide the body of each request.
|
|
195
201
|
This is useful if you want to generate request bodies dynamically and vary them for each request.
|
|
196
202
|
|
|
197
203
|
Example:
|
|
198
204
|
|
|
199
205
|
```javascript
|
|
200
|
-
|
|
206
|
+
export default function request(requestId) {
|
|
201
207
|
// this object will be serialized to JSON and sent in the body of the request
|
|
202
208
|
return {
|
|
203
209
|
key: 'value',
|
|
204
210
|
requestId: requestId
|
|
205
|
-
}
|
|
206
|
-
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
207
213
|
```
|
|
208
214
|
|
|
215
|
+
See sample file in `sample/post-file.js`, and test in `test/body-generator.js`.
|
|
216
|
+
|
|
209
217
|
#### `-u PUT-file`
|
|
210
218
|
|
|
211
219
|
Send the data contained in the given file as a PUT request.
|
|
212
220
|
Remember to set `-T` to the correct content-type.
|
|
213
221
|
|
|
214
|
-
If `PUT-file` has `.js` extension it will be `
|
|
215
|
-
should `export` a
|
|
222
|
+
If `PUT-file` has `.js` extension it will be `import`ed. It should be a valid node module and it
|
|
223
|
+
should `export` a default function, which is invoked with an automatically generated request identifier
|
|
216
224
|
to provide the body of each request.
|
|
217
225
|
This is useful if you want to generate request bodies dynamically and vary them for each request.
|
|
218
|
-
For
|
|
226
|
+
For examples see above for `-p`.
|
|
219
227
|
|
|
220
228
|
#### `-a PATCH-file`
|
|
221
229
|
|
|
222
230
|
Send the data contained in the given file as a PATCH request.
|
|
223
231
|
Remember to set `-T` to the correct content-type.
|
|
224
232
|
|
|
225
|
-
If `PATCH-file` has `.js` extension it will be `
|
|
226
|
-
should `export` a
|
|
233
|
+
If `PATCH-file` has `.js` extension it will be `import`ed. It should be a valid node module and it
|
|
234
|
+
should `export` a default function, which is invoked with an automatically generated request identifier
|
|
227
235
|
to provide the body of each request.
|
|
228
236
|
This is useful if you want to generate request bodies dynamically and vary them for each request.
|
|
229
|
-
For
|
|
237
|
+
For examples see above for `-p`.
|
|
230
238
|
|
|
231
239
|
##### `-r`
|
|
232
240
|
|
|
@@ -253,6 +261,7 @@ The following parameters are _not_ compatible with Apache ab.
|
|
|
253
261
|
|
|
254
262
|
Controls the number of requests per second that are sent.
|
|
255
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.
|
|
256
265
|
|
|
257
266
|
Note: Concurrency doesn't affect the final number of requests per second,
|
|
258
267
|
since rps will be shared by all the clients. E.g.:
|
|
@@ -310,10 +319,12 @@ Note: instead of using the default agent, this option is now an alias for `-k`.
|
|
|
310
319
|
|
|
311
320
|
Do not show any messages.
|
|
312
321
|
|
|
313
|
-
#### `--debug`
|
|
322
|
+
#### `--debug` (deprecated)
|
|
314
323
|
|
|
315
324
|
Show debug messages.
|
|
316
325
|
|
|
326
|
+
Note: deprecated in version 6+.
|
|
327
|
+
|
|
317
328
|
#### `--insecure`
|
|
318
329
|
|
|
319
330
|
Allow invalid and self-signed certificates over https.
|
|
@@ -376,7 +387,7 @@ with concurrency 10 (only relevant results are shown):
|
|
|
376
387
|
99% 14 ms
|
|
377
388
|
100% 35997 ms (longest request)
|
|
378
389
|
|
|
379
|
-
|
|
390
|
+
The result was quite erratic, with some requests taking up to 36 seconds;
|
|
380
391
|
this suggests that Node.js is queueing some requests for a long time, and answering them irregularly.
|
|
381
392
|
Now we will try a fixed rate of 1000 rps:
|
|
382
393
|
|
|
@@ -431,10 +442,10 @@ We now know that our server can accept 500 rps without problems.
|
|
|
431
442
|
Not bad for a single-process naïve Node.js server...
|
|
432
443
|
We may refine our results further to find at which point from 500 to 1000 rps our server breaks down.
|
|
433
444
|
|
|
434
|
-
But instead let us research how to improve the
|
|
445
|
+
But instead let us research how to improve the result.
|
|
435
446
|
One obvious candidate is to add keep-alive to the requests so we don't have to create
|
|
436
447
|
a new connection for every request.
|
|
437
|
-
The
|
|
448
|
+
The result (with the same test server) is impressive:
|
|
438
449
|
|
|
439
450
|
$ loadtest http://localhost:7357/ -t 20 -c 10 -k
|
|
440
451
|
...
|
|
@@ -473,19 +484,18 @@ thus allowing you to load test your application in your own tests.
|
|
|
473
484
|
To run a load test, just call the exported function `loadTest()` with a set of options and an optional callback:
|
|
474
485
|
|
|
475
486
|
```javascript
|
|
476
|
-
|
|
487
|
+
import {loadTest} from 'loadtest'
|
|
488
|
+
|
|
477
489
|
const options = {
|
|
478
490
|
url: 'http://localhost:8000',
|
|
479
491
|
maxRequests: 1000,
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
{
|
|
483
|
-
|
|
484
|
-
{
|
|
485
|
-
return console.error('Got an error: %s', error);
|
|
492
|
+
}
|
|
493
|
+
loadTest(options, function(error, result) {
|
|
494
|
+
if (error) {
|
|
495
|
+
return console.error('Got an error: %s', error)
|
|
486
496
|
}
|
|
487
|
-
console.log('Tests run successfully')
|
|
488
|
-
})
|
|
497
|
+
console.log('Tests run successfully')
|
|
498
|
+
})
|
|
489
499
|
```
|
|
490
500
|
|
|
491
501
|
The callback `function(error, result)` will be invoked when the max number of requests is reached,
|
|
@@ -588,10 +598,12 @@ Use an agent with 'Connection: Keep-alive'.
|
|
|
588
598
|
Note: Uses [agentkeepalive](https://npmjs.org/package/agentkeepalive),
|
|
589
599
|
which performs better than the default node.js agent.
|
|
590
600
|
|
|
591
|
-
#### `quiet`
|
|
601
|
+
#### `quiet` (deprecated)
|
|
592
602
|
|
|
593
603
|
Do not show any messages.
|
|
594
604
|
|
|
605
|
+
Note: deprecated in version 6+, shows a warning.
|
|
606
|
+
|
|
595
607
|
#### `indexParam`
|
|
596
608
|
|
|
597
609
|
The given string will be replaced in the final URL with a unique index.
|
|
@@ -633,29 +645,29 @@ The TLS/SSL method to use. (e.g. TLSv1_method)
|
|
|
633
645
|
Example:
|
|
634
646
|
|
|
635
647
|
```javascript
|
|
636
|
-
|
|
648
|
+
import {loadTest} from 'loadtest'
|
|
637
649
|
|
|
638
650
|
const options = {
|
|
639
651
|
url: 'https://www.example.com',
|
|
640
652
|
maxRequests: 100,
|
|
641
653
|
secureProtocol: 'TLSv1_method'
|
|
642
|
-
}
|
|
654
|
+
}
|
|
643
655
|
|
|
644
|
-
|
|
656
|
+
loadTest(options, function(error) {
|
|
645
657
|
if (error) {
|
|
646
|
-
return console.error('Got an error: %s', error)
|
|
658
|
+
return console.error('Got an error: %s', error)
|
|
647
659
|
}
|
|
648
|
-
console.log('Tests run successfully')
|
|
649
|
-
})
|
|
660
|
+
console.log('Tests run successfully')
|
|
661
|
+
})
|
|
650
662
|
```
|
|
651
663
|
|
|
652
664
|
#### `statusCallback`
|
|
653
665
|
|
|
654
|
-
If present, this function executes after every request operation completes. Provides immediate access to test
|
|
666
|
+
If present, this function executes after every request operation completes. Provides immediate access to the test result while the
|
|
655
667
|
test batch is still running. This can be used for more detailed custom logging or developing your own spreadsheet or
|
|
656
|
-
statistical analysis of
|
|
668
|
+
statistical analysis of the result.
|
|
657
669
|
|
|
658
|
-
The
|
|
670
|
+
The result and error passed to the callback are in the same format as the result passed to the final callback.
|
|
659
671
|
|
|
660
672
|
In addition, the following three properties are added to the `result` object:
|
|
661
673
|
|
|
@@ -668,28 +680,28 @@ You will need to check if `error` is populated in order to determine which objec
|
|
|
668
680
|
Example:
|
|
669
681
|
|
|
670
682
|
```javascript
|
|
671
|
-
|
|
683
|
+
import {loadTest} from 'loadtest'
|
|
672
684
|
|
|
673
685
|
function statusCallback(error, result, latency) {
|
|
674
|
-
console.log('Current latency %j, result %j, error %j', latency, result, error)
|
|
675
|
-
console.log('----')
|
|
676
|
-
console.log('Request elapsed milliseconds: ', result.requestElapsed)
|
|
677
|
-
console.log('Request index: ', result.requestIndex)
|
|
678
|
-
console.log('Request loadtest() instance index: ', result.instanceIndex)
|
|
686
|
+
console.log('Current latency %j, result %j, error %j', latency, result, error)
|
|
687
|
+
console.log('----')
|
|
688
|
+
console.log('Request elapsed milliseconds: ', result.requestElapsed)
|
|
689
|
+
console.log('Request index: ', result.requestIndex)
|
|
690
|
+
console.log('Request loadtest() instance index: ', result.instanceIndex)
|
|
679
691
|
}
|
|
680
692
|
|
|
681
693
|
const options = {
|
|
682
694
|
url: 'http://localhost:8000',
|
|
683
695
|
maxRequests: 1000,
|
|
684
696
|
statusCallback: statusCallback
|
|
685
|
-
}
|
|
697
|
+
}
|
|
686
698
|
|
|
687
|
-
|
|
699
|
+
loadTest(options, function(error) {
|
|
688
700
|
if (error) {
|
|
689
|
-
return console.error('Got an error: %s', error)
|
|
701
|
+
return console.error('Got an error: %s', error)
|
|
690
702
|
}
|
|
691
|
-
console.log('Tests run successfully')
|
|
692
|
-
})
|
|
703
|
+
console.log('Tests run successfully')
|
|
704
|
+
})
|
|
693
705
|
```
|
|
694
706
|
|
|
695
707
|
|
|
@@ -746,9 +758,9 @@ function contentInspector(result) {
|
|
|
746
758
|
},
|
|
747
759
|
```
|
|
748
760
|
|
|
749
|
-
###
|
|
761
|
+
### Result
|
|
750
762
|
|
|
751
|
-
The latency
|
|
763
|
+
The latency result passed to your callback at the end of the load test contains a full set of data, including:
|
|
752
764
|
mean latency, number of errors and percentiles.
|
|
753
765
|
An example follows:
|
|
754
766
|
|
|
@@ -788,14 +800,20 @@ The second parameter contains info about the current request:
|
|
|
788
800
|
|
|
789
801
|
### Start Test Server
|
|
790
802
|
|
|
791
|
-
To start the test server use the exported function `startServer()` with a set of options
|
|
803
|
+
To start the test server use the exported function `startServer()` with a set of options:
|
|
792
804
|
|
|
793
805
|
```javascript
|
|
794
|
-
|
|
795
|
-
const server =
|
|
806
|
+
import {startServer} from 'loadtest'
|
|
807
|
+
const server = await startServer({port: 8000})
|
|
796
808
|
```
|
|
797
809
|
|
|
798
810
|
This function returns an HTTP server which can be `close()`d when it is no longer useful.
|
|
811
|
+
As a legacy from before promises existed,
|
|
812
|
+
if the optional callback is passed then it will not behave as `async`:
|
|
813
|
+
|
|
814
|
+
```
|
|
815
|
+
const server = startServer({port: 8000}, error => console.error(error))
|
|
816
|
+
```
|
|
799
817
|
|
|
800
818
|
The following options are available.
|
|
801
819
|
|
|
@@ -856,11 +874,13 @@ The expected structure of the file is the following:
|
|
|
856
874
|
}
|
|
857
875
|
```
|
|
858
876
|
|
|
877
|
+
See sample file in `sample/.loadtestrc`.
|
|
878
|
+
|
|
859
879
|
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_.
|
|
860
880
|
|
|
861
881
|
### Complete Example
|
|
862
882
|
|
|
863
|
-
The file `
|
|
883
|
+
The file `test/integration.js` shows a complete example, which is also a full integration test:
|
|
864
884
|
it starts the server, send 1000 requests, waits for the callback and closes down the server.
|
|
865
885
|
|
|
866
886
|
## Versioning
|
package/bin/loadtest.js
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {readFile} from 'fs/promises'
|
|
4
|
+
import * as stdio from 'stdio'
|
|
5
|
+
import {loadTest} from '../lib/loadtest.js'
|
|
6
|
+
import {showResult} from '../lib/show.js'
|
|
8
7
|
|
|
9
|
-
// requires
|
|
10
|
-
const stdio = require('stdio');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const urlLib = require('url');
|
|
14
|
-
const loadTest = require('../lib/loadtest.js');
|
|
15
|
-
const headers = require('../lib/headers.js');
|
|
16
|
-
const packageJson = require(__dirname + '/../package.json');
|
|
17
|
-
const config = require('../lib/config');
|
|
18
8
|
|
|
19
|
-
// init
|
|
20
9
|
const options = stdio.getopt({
|
|
21
10
|
maxRequests: {key: 'n', args: 1, description: 'Number of requests to perform'},
|
|
22
11
|
concurrency: {key: 'c', args: 1, description: 'Number of requests to make'},
|
|
@@ -39,156 +28,41 @@ const options = stdio.getopt({
|
|
|
39
28
|
version: {key: 'V', description: 'Show version number and exit'},
|
|
40
29
|
proxy: {args: 1, description: 'Use a proxy for requests e.g. http://localhost:8080 '},
|
|
41
30
|
rps: {args: 1, description: 'Specify the requests per second for each client'},
|
|
42
|
-
agent: {description: 'Use a keep-alive http agent (deprecated)'},
|
|
43
31
|
index: {args: 1, description: 'Replace the value of given arg with an index in the URL'},
|
|
44
|
-
quiet: {description: 'Do not log any messages'},
|
|
45
|
-
debug: {description: 'Show debug messages'},
|
|
46
32
|
insecure: {description: 'Allow self-signed certificates over https'},
|
|
47
33
|
key: {args: 1, description: 'The client key to use'},
|
|
48
|
-
cert: {args: 1, description: 'The client certificate to use'}
|
|
34
|
+
cert: {args: 1, description: 'The client certificate to use'},
|
|
35
|
+
quiet: {description: 'Do not log any messages'},
|
|
36
|
+
agent: {description: 'Use a keep-alive http agent (deprecated)'},
|
|
37
|
+
debug: {description: 'Show debug messages (deprecated)'}
|
|
49
38
|
});
|
|
50
|
-
if (options.version) {
|
|
51
|
-
console.log('Loadtest version: %s', packageJson.version);
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
// is there an url? if not, break and display help
|
|
55
|
-
if (!options.args || options.args.length < 1) {
|
|
56
|
-
console.error('Missing URL to load-test');
|
|
57
|
-
help();
|
|
58
|
-
} else if (options.args.length > 1) {
|
|
59
|
-
console.error('Too many arguments: %s', options.args);
|
|
60
|
-
help();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const configuration = config.loadConfig(options);
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Allow a post body string in options
|
|
71
|
-
// Ex -P '{"foo": "bar"}'
|
|
72
|
-
if (options.postBody) {
|
|
73
|
-
options.method = 'POST';
|
|
74
|
-
options.body = options.postBody;
|
|
75
|
-
}
|
|
76
|
-
if (options.postFile) {
|
|
77
|
-
options.method = 'POST';
|
|
78
|
-
options.body = readBody(options.postFile, '-p');
|
|
79
|
-
}
|
|
80
|
-
if (options.data) {
|
|
81
|
-
options.body = JSON.parse(options.data);
|
|
82
|
-
}
|
|
83
|
-
if (options.method) {
|
|
84
|
-
const acceptedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'get', 'post', 'put', 'delete', 'patch'];
|
|
85
|
-
if (acceptedMethods.indexOf(options.method) === -1) {
|
|
86
|
-
options.method = 'GET';
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if(options.putFile) {
|
|
90
|
-
options.method = 'PUT';
|
|
91
|
-
options.body = readBody(options.putFile, '-u');
|
|
92
|
-
}
|
|
93
|
-
if (options.patchBody) {
|
|
94
|
-
options.method = 'PATCH';
|
|
95
|
-
options.body = options.patchBody;
|
|
96
|
-
}
|
|
97
|
-
if(options.patchFile) {
|
|
98
|
-
options.method = 'PATCH';
|
|
99
|
-
options.body = readBody(options.patchFile, '-a');
|
|
100
|
-
}
|
|
101
|
-
if(!options.method) {
|
|
102
|
-
options.method = configuration.method;
|
|
103
|
-
}
|
|
104
|
-
if(!options.body) {
|
|
105
|
-
if(configuration.body) {
|
|
106
|
-
options.body = configuration.body;
|
|
107
|
-
} else if(configuration.file) {
|
|
108
|
-
options.body = readBody(configuration.file, 'configuration.request.file');
|
|
40
|
+
async function processAndRun(options) {
|
|
41
|
+
if (options.version) {
|
|
42
|
+
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url)))
|
|
43
|
+
console.log('Loadtest version: %s', packageJson.version);
|
|
44
|
+
process.exit(0);
|
|
109
45
|
}
|
|
110
|
-
|
|
111
|
-
options.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
options.key = fs.readFileSync(options.key);
|
|
117
|
-
}
|
|
118
|
-
if(!options.cert) {
|
|
119
|
-
options.cert = configuration.cert;
|
|
120
|
-
}
|
|
121
|
-
if(options.cert) {
|
|
122
|
-
options.cert = fs.readFileSync(options.cert);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const defaultHeaders = options.headers || !configuration.headers ? {} : configuration.headers;
|
|
126
|
-
defaultHeaders['host'] = urlLib.parse(options.url).host;
|
|
127
|
-
defaultHeaders['user-agent'] = 'loadtest/' + packageJson.version;
|
|
128
|
-
defaultHeaders['accept'] = '*/*';
|
|
129
|
-
|
|
130
|
-
if (options.headers) {
|
|
131
|
-
headers.addHeaders(options.headers, defaultHeaders);
|
|
132
|
-
console.log('headers: %s, %j', typeof defaultHeaders, defaultHeaders);
|
|
133
|
-
}
|
|
134
|
-
options.headers = defaultHeaders;
|
|
135
|
-
|
|
136
|
-
if (!options.requestGenerator) {
|
|
137
|
-
options.requestGenerator = configuration.requestGenerator;
|
|
138
|
-
}
|
|
139
|
-
if (options.requestGenerator) {
|
|
140
|
-
options.requestGenerator = require(path.resolve(options.requestGenerator));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Use configuration file for other values
|
|
144
|
-
if(!options.maxRequests) {
|
|
145
|
-
options.maxRequests = configuration.maxRequests;
|
|
146
|
-
}
|
|
147
|
-
if(!options.concurrency) {
|
|
148
|
-
options.concurrency = configuration.concurrency;
|
|
149
|
-
}
|
|
150
|
-
if(!options.maxSeconds) {
|
|
151
|
-
options.maxSeconds = configuration.maxSeconds;
|
|
152
|
-
}
|
|
153
|
-
if(!options.timeout && configuration.timeout) {
|
|
154
|
-
options.timeout = configuration.timeout;
|
|
155
|
-
}
|
|
156
|
-
if(!options.contentType) {
|
|
157
|
-
options.contentType = configuration.contentType;
|
|
158
|
-
}
|
|
159
|
-
if(!options.cookies) {
|
|
160
|
-
options.cookies = configuration.cookies;
|
|
161
|
-
}
|
|
162
|
-
if(!options.secureProtocol) {
|
|
163
|
-
options.secureProtocol = configuration.secureProtocol;
|
|
164
|
-
}
|
|
165
|
-
if(!options.insecure) {
|
|
166
|
-
options.insecure = configuration.insecure;
|
|
167
|
-
}
|
|
168
|
-
if(!options.recover) {
|
|
169
|
-
options.recover = configuration.recover;
|
|
170
|
-
}
|
|
171
|
-
if(!options.proxy) {
|
|
172
|
-
options.proxy = configuration.proxy;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
loadTest.loadTest(options);
|
|
176
|
-
|
|
177
|
-
function readBody(filename, option) {
|
|
178
|
-
if (typeof filename !== 'string') {
|
|
179
|
-
console.error('Invalid file to open with %s: %s', option, filename);
|
|
46
|
+
// is there an url? if not, break and display help
|
|
47
|
+
if (!options.args || options.args.length < 1) {
|
|
48
|
+
console.error('Missing URL to load-test');
|
|
49
|
+
help();
|
|
50
|
+
} else if (options.args.length > 1) {
|
|
51
|
+
console.error('Too many arguments: %s', options.args);
|
|
180
52
|
help();
|
|
181
53
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
54
|
+
options.url = options.args[0];
|
|
55
|
+
try {
|
|
56
|
+
const result = await loadTest(options)
|
|
57
|
+
showResult(options, result)
|
|
58
|
+
} catch(error) {
|
|
59
|
+
console.error(error.message)
|
|
60
|
+
help()
|
|
185
61
|
}
|
|
186
|
-
|
|
187
|
-
const ret = fs.readFileSync(filename, {encoding: 'utf8'}).replace("\n", "");
|
|
188
|
-
|
|
189
|
-
return ret;
|
|
190
62
|
}
|
|
191
63
|
|
|
64
|
+
await processAndRun(options)
|
|
65
|
+
|
|
192
66
|
/**
|
|
193
67
|
* Show online help.
|
|
194
68
|
*/
|
package/bin/testserver.js
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
3
|
+
import * as stdio from 'stdio'
|
|
4
|
+
import {startServer} from '../lib/testserver.js'
|
|
5
|
+
import {loadConfig} from '../lib/config.js'
|
|
8
6
|
|
|
9
|
-
// requires
|
|
10
|
-
const stdio = require('stdio');
|
|
11
|
-
const testServer = require('../lib/testserver');
|
|
12
|
-
const config = require('../lib/config')
|
|
13
7
|
|
|
14
|
-
// init
|
|
15
8
|
const options = stdio.getopt({
|
|
16
9
|
delay: {key: 'd', args: 1, description: 'Delay the response for the given milliseconds'},
|
|
17
10
|
error: {key: 'e', args: 1, description: 'Return an HTTP error code'},
|
|
18
11
|
percent: {key: 'p', args: 1, description: 'Return an error (default 500) only for some % of requests'},
|
|
19
12
|
});
|
|
20
|
-
const configuration =
|
|
13
|
+
const configuration = loadConfig()
|
|
21
14
|
if (options.args && options.args.length == 1) {
|
|
22
15
|
options.port = parseInt(options.args[0], 10);
|
|
23
16
|
if (!options.port) {
|
|
@@ -45,5 +38,5 @@ if(!options.percent) {
|
|
|
45
38
|
options.percent = configuration.percent
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
|
|
41
|
+
startServer(options);
|
|
49
42
|
|
package/index.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import {loadTest} from './lib/loadtest.js'
|
|
2
|
+
import {startServer} from './lib/testserver.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
* Package contains a load test script and a test server.
|
|
5
|
-
* (C) 2013 Alex Fernández.
|
|
6
|
-
*/
|
|
4
|
+
const loadtest = {loadTest, startServer}
|
|
7
5
|
|
|
6
|
+
export default loadtest
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const testserver = require('./lib/testserver.js');
|
|
12
|
-
|
|
13
|
-
// exports
|
|
14
|
-
exports.loadTest = loadtest.loadTest;
|
|
15
|
-
exports.startServer = testserver.startServer;
|
|
8
|
+
export * from './lib/loadtest.js'
|
|
9
|
+
export * from './lib/testserver.js'
|
|
16
10
|
|
package/lib/baseClient.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import * as urlLib from 'url'
|
|
2
|
+
import {addUserAgent} from './headers.js'
|
|
2
3
|
|
|
3
|
-
const urlLib = require('url');
|
|
4
|
-
const Log = require('log');
|
|
5
|
-
const headers = require('./headers.js');
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
const log = new Log('info');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class BaseClient {
|
|
5
|
+
export class BaseClient {
|
|
12
6
|
constructor(operation, params) {
|
|
13
7
|
this.operation = operation;
|
|
14
8
|
this.params = params;
|
|
@@ -22,14 +16,11 @@ class BaseClient {
|
|
|
22
16
|
return (error, result) => {
|
|
23
17
|
let errorCode = null;
|
|
24
18
|
if (error) {
|
|
25
|
-
log.debug('Connection %s failed: %s', id, error);
|
|
26
19
|
if (result) {
|
|
27
20
|
errorCode = result;
|
|
28
21
|
} else {
|
|
29
22
|
errorCode = '-1';
|
|
30
23
|
}
|
|
31
|
-
} else {
|
|
32
|
-
log.debug('Connection %s ended', id);
|
|
33
24
|
}
|
|
34
25
|
this.operation.latency.end(id, errorCode);
|
|
35
26
|
let callback;
|
|
@@ -56,28 +47,20 @@ class BaseClient {
|
|
|
56
47
|
this.options.agent = false;
|
|
57
48
|
if (this.params.body) {
|
|
58
49
|
if (typeof this.params.body == 'string') {
|
|
59
|
-
log.debug('Received string body');
|
|
60
50
|
this.generateMessage = () => this.params.body;
|
|
61
51
|
} else if (typeof this.params.body == 'object') {
|
|
62
|
-
log.debug('Received JSON body');
|
|
63
52
|
this.generateMessage = () => this.params.body;
|
|
64
53
|
} else if (typeof this.params.body == 'function') {
|
|
65
|
-
log.debug('Received function body');
|
|
66
54
|
this.generateMessage = this.params.body;
|
|
67
55
|
} else {
|
|
68
|
-
|
|
56
|
+
console.error('Unrecognized body: %s', typeof this.params.body);
|
|
69
57
|
}
|
|
70
58
|
this.options.headers['Content-Type'] = this.params.contentType || 'text/plain';
|
|
71
59
|
}
|
|
72
|
-
|
|
60
|
+
addUserAgent(this.options.headers);
|
|
73
61
|
if (this.params.secureProtocol) {
|
|
74
62
|
this.options.secureProtocol = this.params.secureProtocol;
|
|
75
63
|
}
|
|
76
|
-
log.debug('Options: %j',this.options);
|
|
77
64
|
}
|
|
78
65
|
}
|
|
79
66
|
|
|
80
|
-
module.exports = {
|
|
81
|
-
BaseClient,
|
|
82
|
-
}
|
|
83
|
-
|