loadtest 6.4.0 → 7.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/README.md CHANGED
@@ -18,17 +18,9 @@ On Ubuntu or Mac OS X systems install using sudo:
18
18
 
19
19
  $ sudo npm install -g loadtest
20
20
 
21
- For access to the API just add package `loadtest` to your `package.json` devDependencies:
21
+ For access to the API just install it in your `npm` package as a dev dependency:
22
22
 
23
- ```json
24
- {
25
- ...
26
- "devDependencies": {
27
- "loadtest": "*"
28
- },
29
- ...
30
- }
31
- ```
23
+ $ npm install --save-dev loadtest
32
24
 
33
25
  ### Compatibility
34
26
 
@@ -36,8 +28,9 @@ Versions 6 and later should be used at least with Node.js v16 or later:
36
28
 
37
29
  * Node.js v16 or later: ^6.0.0
38
30
  * Node.js v10 or later: ^5.0.0
39
- * Node.js v8 or later: 4.x.y.
40
- * Node.js v6 or earlier: ^3.1.0.
31
+ * Node.js v8 or later: 4.x.y
32
+ * Node.js v6 or earlier: ^3.1.0
33
+ * ES5 support (no `let`, `const` or arrow functions): ^2.0.0.
41
34
 
42
35
  ## Usage
43
36
 
@@ -88,24 +81,32 @@ so that you can abort deployment e.g. if 99% of the requests don't finish in 10
88
81
 
89
82
  ### Usage Don'ts
90
83
 
91
- `loadtest` saturates a single CPU pretty quickly.
92
- Do not use `loadtest` in this mode
93
- if the Node.js process is above 100% usage in `top`, which happens approx. when your load is above 1000~4000 rps.
84
+ `loadtest` performance has improved significantly,
85
+ but it is still limited.
86
+ `loadtest` saturates a single CPU pretty quickly,
87
+ so it uses half the available cores in your processor.
88
+ If you see that the Node.js processes are above 100% usage in `top`,
89
+ which happens approx. when your load is above 4000~5000 rps per core,
90
+ please adjust the number of cores.
91
+ So for instance with eight cores you can expect to get a maximum performance of
92
+ 8 * 5000 ~ 40 krps.
94
93
  (You can measure the practical limits of `loadtest` on your specific test machines by running it against a simple
95
94
  [test server](#test-server)
96
95
  and seeing when it reaches 100% CPU.)
97
- In this case try using in multi-process mode using the `--cores` parameter,
98
- see below.
99
96
 
100
- There are better tools for that use case:
97
+ If you have reached the limits of `loadtest` even after using all cores,
98
+ there are other tools that you can try.
101
99
 
100
+ * [AutoCannon](https://www.npmjs.com/package/autocannon): also an `npm` package,
101
+ awesome tool with an interface similar to `wrk`.
102
102
  * [Apache `ab`](http://httpd.apache.org/docs/2.2/programs/ab.html)
103
103
  has great performance, but it is also limited by a single CPU performance.
104
104
  Its practical limit is somewhere around ~40 krps.
105
105
  * [weighttp](http://redmine.lighttpd.net/projects/weighttp/wiki) is also `ab`-compatible
106
106
  and is supposed to be very fast (the author has not personally used it).
107
- * [wrk](https://github.com/wg/wrk) is multithreaded and fit for use when multiple CPUs are required or available.
107
+ * [wrk](https://github.com/wg/wrk) is multithreaded and highly performance.
108
108
  It may need installing from source though, and its interface is not `ab`-compatible.
109
+ * [wrk2](https://github.com/giltene/wrk2): evolution of `wrk`.
109
110
 
110
111
  ### Regular Usage
111
112
 
@@ -239,12 +240,12 @@ to provide the body of each request.
239
240
  This is useful if you want to generate request bodies dynamically and vary them for each request.
240
241
  For examples see above for `-p`.
241
242
 
242
- ##### `-r`
243
+ ##### `-r recover`
243
244
 
244
245
  Recover from errors. Always active: loadtest does not stop on errors.
245
246
  After the tests are finished, if there were errors a report with all error codes will be shown.
246
247
 
247
- #### `-s`
248
+ #### `-s secureProtocol`
248
249
 
249
250
  The TLS/SSL method to use. (e.g. TLSv1_method)
250
251
 
@@ -252,7 +253,7 @@ Example:
252
253
 
253
254
  $ loadtest -n 1000 -s TLSv1_method https://www.example.com
254
255
 
255
- #### `-V`
256
+ #### `-V version`
256
257
 
257
258
  Show version number and exit.
258
259
 
@@ -283,15 +284,19 @@ Note: --rps is not supported for websockets.
283
284
  #### `--cores number`
284
285
 
285
286
  Start `loadtest` in multi-process mode on a number of cores simultaneously.
286
- Useful when a single CPU is saturated.
287
287
  Forks the requested number of processes using the
288
288
  [Node.js cluster module](https://nodejs.org/api/cluster.html).
289
+ Default: half the available CPUs on the machine.
289
290
 
290
- In this mode the total number of requests and the rps rate are shared among all processes.
291
- The result returned is the aggregation of results from all cores.
291
+ The total number of requests and the rps rate are shared among all processes.
292
+ The result shown is the aggregation of results from all cores.
292
293
 
293
294
  Note: this option is not available in the API,
294
- where it runs just in the provided process.
295
+ since it runs just within the calling process.
296
+
297
+ **Warning**: the default value for `--cores` has changed in version 7+,
298
+ from 1 to half the available CPUs on the machine.
299
+ Set to 1 to get the previous single-process mode.
295
300
 
296
301
  #### `--timeout milliseconds`
297
302
 
@@ -496,351 +501,51 @@ However, it you try to push it beyond that, at 3 krps it will fail miserably.
496
501
 
497
502
  `loadtest` is not limited to running from the command line; it can be controlled using an API,
498
503
  thus allowing you to load test your application in your own tests.
504
+ A short introduction follows; see [complete docs for API](doc/api.md).
499
505
 
500
506
  ### Invoke Load Test
501
507
 
502
- To run a load test, just `await` for the exported function `loadTest()` with the desired options, described below:
508
+ To run a load test, invoke the exported function `loadTest()` with the desired options:
503
509
 
504
510
  ```javascript
505
511
  import {loadTest} from 'loadtest'
506
512
 
507
513
  const options = {
508
- url: 'http://localhost:8000',
509
- maxRequests: 1000,
514
+ url: 'http://localhost:8000',
515
+ maxRequests: 1000,
510
516
  }
511
517
  const result = await loadTest(options)
512
518
  result.show()
513
519
  console.log('Tests run successfully')
514
520
  ```
515
521
 
516
- The call returns a `Result` object that contains all info about the load test, also described below.
517
- Call `result.show()` to display the results in the standard format on the console.
518
-
519
- As a legacy from before promises existed,
520
- if an optional callback is passed as second parameter then it will not behave as `async`:
521
- the callback `function(error, result)` will be invoked when the max number of requests is reached,
522
- or when the max number of seconds has elapsed.
523
-
524
- ```javascript
525
- import {loadTest} from 'loadtest'
526
-
527
- const options = {
528
- url: 'http://localhost:8000',
529
- maxRequests: 1000,
530
- }
531
- loadTest(options, function(error, result) {
532
- if (error) {
533
- return console.error('Got an error: %s', error)
534
- }
535
- result.show()
536
- console.log('Tests run successfully')
537
- })
538
- ```
539
-
540
-
541
- Beware: if there are no `maxRequests` and no `maxSeconds`, then tests will run forever
542
- and will not call the callback.
543
-
544
- ### Result
545
-
546
- The latency result returned at the end of the load test contains a full set of data, including:
547
- mean latency, number of errors and percentiles.
548
- A simplified example follows:
549
-
550
- ```javascript
551
- {
552
- url: 'http://localhost:80/',
553
- maxRequests: 1000,
554
- maxSeconds: 0,
555
- concurrency: 10,
556
- agent: 'none',
557
- requestsPerSecond: undefined,
558
- totalRequests: 1000,
559
- percentiles: {
560
- '50': 7,
561
- '90': 10,
562
- '95': 11,
563
- '99': 15
564
- },
565
- effectiveRps: 2824,
566
- elapsedSeconds: 0.354108,
567
- meanLatencyMs: 7.72,
568
- maxLatencyMs: 20,
569
- totalErrors: 3,
570
- errorCodes: {
571
- '0': 1,
572
- '500': 2
573
- },
574
- }
575
- ```
576
-
577
- The `result` object also has a `result.show()` function
578
- that displays the results on the console in the standard format.
579
-
580
- ### Options
581
-
582
- All options but `url` are, as their name implies, optional.
583
-
584
- #### `url`
585
-
586
- The URL to invoke. Mandatory.
587
-
588
- #### `concurrency`
589
-
590
- How many clients to start in parallel.
591
-
592
- #### `maxRequests`
593
-
594
- A max number of requests; after they are reached the test will end.
595
-
596
- Note: the actual number of requests sent can be bigger if there is a concurrency level;
597
- loadtest will report just on the max number of requests.
598
-
599
- #### `maxSeconds`
600
-
601
- Max number of seconds to run the tests.
602
-
603
- Note: after the given number of seconds `loadtest` will stop sending requests,
604
- but may continue receiving tests afterwards.
605
-
606
- #### `timeout`
607
-
608
- Timeout for each generated request in milliseconds. Setting this to 0 disables timeout (default).
609
-
610
- #### `cookies`
611
-
612
- An array of cookies to send. Each cookie should be a string of the form name=value.
613
-
614
- #### `headers`
615
-
616
- A map of headers. Each header should be an entry in the map with the value given as a string.
617
- If you want to have several values for a header, write a single value separated by semicolons,
618
- like this:
619
-
620
- {
621
- accept: "text/plain;text/html"
622
- }
623
-
624
- Note: when using the API, the "host" header is not inferred from the URL but needs to be sent
625
- explicitly.
626
-
627
- #### `method`
628
-
629
- The method to use: POST, PUT. Default: GET.
630
-
631
- #### `body`
632
-
633
- The contents to send in the body of the message, for POST or PUT requests.
634
- Can be a string or an object (which will be converted to JSON).
635
-
636
- #### `contentType`
637
-
638
- The MIME type to use for the body. Default content type is `text/plain`.
639
-
640
- #### `requestsPerSecond`
641
-
642
- How many requests each client will send per second.
643
-
644
- #### `requestGenerator`
645
-
646
- Use a custom request generator function.
647
- The request needs to be generated synchronously and returned when this function is invoked.
648
-
649
- Example request generator function could look like this:
650
-
651
- ```javascript
652
- function(params, options, client, callback) {
653
- const message = generateMessage();
654
- const request = client(options, callback);
655
- options.headers['Content-Length'] = message.length;
656
- options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
657
- request.write(message);
658
- request.end();
659
- return request;
660
- }
661
- ```
662
-
663
- See [`sample/request-generator.js`](sample/request-generator.js) for some sample code including a body
664
- (or [`sample/request-generator.ts`](sample/request-generator.ts) for ES6/TypeScript).
665
-
666
- #### `agentKeepAlive`
667
-
668
- Use an agent with 'Connection: Keep-alive'.
669
-
670
- Note: Uses [agentkeepalive](https://npmjs.org/package/agentkeepalive),
671
- which performs better than the default node.js agent.
672
-
673
- #### `quiet` (deprecated)
674
-
675
- Do not show any messages.
676
-
677
- Note: deprecated in version 6+, shows a warning.
678
-
679
- #### `indexParam`
680
-
681
- The given string will be replaced in the final URL with a unique index.
682
- E.g.: if URL is `http://test.com/value` and `indexParam=value`, then the URL
683
- will be:
684
-
685
- * http://test.com/1
686
- * http://test.com/2
687
- * ...
688
- * body will also be replaced `body:{ userid: id_value }` will be `body:{ userid: id_1 }`
689
-
690
- #### `indexParamCallback`
691
-
692
- A function that would be executed to replace the value identified through `indexParam` through a custom value generator.
693
-
694
- E.g.: if URL is `http://test.com/value` and `indexParam=value` and
695
- ```javascript
696
- indexParamCallback: function customCallBack() {
697
- return Math.floor(Math.random() * 10); //returns a random integer from 0 to 9
698
- }
699
- ```
700
- then the URL could be:
701
-
702
- * http://test.com/1 (Randomly generated integer 1)
703
- * http://test.com/5 (Randomly generated integer 5)
704
- * http://test.com/6 (Randomly generated integer 6)
705
- * http://test.com/8 (Randomly generated integer 8)
706
- * ...
707
- * body will also be replaced `body:{ userid: id_value }` will be `body:{ userid: id_<value from callback> }`
708
-
709
- #### `insecure`
710
-
711
- Allow invalid and self-signed certificates over https.
712
-
713
- #### `secureProtocol`
714
-
715
- The TLS/SSL method to use. (e.g. TLSv1_method)
716
-
717
- Example:
718
-
719
- ```javascript
720
- import {loadTest} from 'loadtest'
721
-
722
- const options = {
723
- url: 'https://www.example.com',
724
- maxRequests: 100,
725
- secureProtocol: 'TLSv1_method'
726
- }
727
-
728
- loadTest(options, function(error) {
729
- if (error) {
730
- return console.error('Got an error: %s', error)
731
- }
732
- console.log('Tests run successfully')
733
- })
734
- ```
735
-
736
- #### `statusCallback`
737
-
738
- If present, this function executes after every request operation completes. Provides immediate access to the test result while the
739
- test batch is still running. This can be used for more detailed custom logging or developing your own spreadsheet or
740
- statistical analysis of the result.
741
-
742
- The result and error passed to the callback are in the same format as the result passed to the final callback.
743
-
744
- In addition, the following three properties are added to the `result` object:
745
-
746
- - `requestElapsed`: time in milliseconds it took to complete this individual request.
747
- - `requestIndex`: 0-based index of this particular request in the sequence of all requests to be made.
748
- - `instanceIndex`: the `loadtest(...)` instance index. This is useful if you call `loadtest()` more than once.
749
-
750
- You will need to check if `error` is populated in order to determine which object to check for these properties.
751
-
752
- The second parameter contains info about the current request:
753
-
754
- ```javascript
755
- {
756
- host: 'localhost',
757
- path: '/',
758
- method: 'GET',
759
- statusCode: 200,
760
- body: '<html><body>hi</body></html>',
761
- headers: [...]
762
- }
763
- ```
764
-
765
- Example:
766
-
767
- ```javascript
768
- import {loadTest} from 'loadtest'
769
-
770
- function statusCallback(error, result, latency) {
771
- console.log('Current latency %j, result %j, error %j', latency, result, error)
772
- console.log('----')
773
- console.log('Request elapsed milliseconds: ', result.requestElapsed)
774
- console.log('Request index: ', result.requestIndex)
775
- console.log('Request loadtest() instance index: ', result.instanceIndex)
776
- }
777
-
778
- const options = {
779
- url: 'http://localhost:8000',
780
- maxRequests: 1000,
781
- statusCallback: statusCallback
782
- }
783
-
784
- loadTest(options, function(error) {
785
- if (error) {
786
- return console.error('Got an error: %s', error)
787
- }
788
- console.log('Tests run successfully')
789
- })
790
- ```
791
-
792
- In some situations request data needs to be available in the statusCallBack.
793
- This data can be assigned to `request.labels` in the requestGenerator:
794
- ```javascript
795
- const options = {
796
- // ...
797
- requestGenerator: (params, options, client, callback) => {
798
- // ...
799
- const randomInputData = Math.random().toString().substr(2, 8);
800
- const message = JSON.stringify({ randomInputData })
801
- const request = client(options, callback);
802
- request.labels = randomInputData;
803
- request.write(message);
804
- return request;
805
- }
806
- };
807
- ```
808
-
809
- Then in statusCallBack the labels can be accessed through `result.labels`:
810
- ```javascript
811
- function statusCallback(error, result, latency) {
812
- console.log(result.labels);
813
- }
814
- ```
815
-
816
- **Warning**: The format for `statusCallback` has changed in version 2.0.0 onwards.
817
- It used to be `statusCallback(latency, result, error)`,
818
- it has been changed to conform to the usual Node.js standard.
819
-
820
- #### `contentInspector`
821
-
822
- A function that would be executed after every request before its status be added to the final statistics.
823
-
824
- The is can be used when you want to mark some result with 200 http status code to be failed or error.
825
-
826
- The `result` object passed to this callback function has the same fields as the `result` object passed to `statusCallback`.
827
-
828
- `customError` can be added to mark this result as failed or error. `customErrorCode` will be provided in the final statistics, in addtion to the http status code.
829
-
830
- Example:
831
-
832
- ```javascript
833
- function contentInspector(result) {
834
- if (result.statusCode == 200) {
835
- const body = JSON.parse(result.body)
836
- // how to examine the body depends on the content that the service returns
837
- if (body.status.err_code !== 0) {
838
- result.customError = body.status.err_code + " " + body.status.msg
839
- result.customErrorCode = body.status.err_code
840
- }
841
- }
842
- },
843
- ```
522
+ Beware: if there are no `maxRequests` and no `maxSeconds`, the test will run forever.
523
+
524
+ ### `loadTest()` Parameters
525
+
526
+ A simplified list of parameters is shown below;
527
+ see [doc/api.md](doc/api.md) for the full explanations with examples.
528
+
529
+ * `url`: URL to invoke, mandatory.
530
+ * `concurrency`: how many clients to start in parallel.
531
+ * `maxRequests`: max number of requests; after they are reached the test will end.
532
+ * `maxSeconds`: max number of seconds to run the tests.
533
+ * `timeout`: timeout for each generated request in milliseconds, set to 0 to disable (default).
534
+ * `cookies`: array of cookies to send, of the form `name=value`.
535
+ * `headers`: object with headers, each with the value as string. Separate by semicolons to have multiple values.
536
+ * `method`: HTTP method to use, default `GET`.
537
+ * `body`: contents to send in the body of the message.
538
+ * `contentType`: MIME type to use for the body, default `text/plain`.
539
+ * `requestsPerSecond`: how many requests will be sent per second.
540
+ * `requestGenerator`: custom request generator function.
541
+ * `agentKeepAlive`: if true, will use 'Connection: Keep-alive'.
542
+ * `quiet`: if true, do not show any messages.
543
+ * `indexParam`: parameter to replace in URL and body with a unique index.
544
+ * `indexParamCallback`: function to generate unique indexes.
545
+ * `insecure`: allow invalid and self-signed certificates over https.
546
+ * `secureProtocol`: TLS/SSL method to use.
547
+ * `statusCallback(error, result)`: function to call after every request is completed.
548
+ * `contentInspector(result)`: function to call before aggregating statistics.
844
549
 
845
550
  ### Start Test Server
846
551
 
@@ -853,40 +558,14 @@ const server = await startServer({port: 8000})
853
558
  await server.close()
854
559
  ```
855
560
 
856
- This function returns when the server is up and running,
857
- with an HTTP server which can be `close()`d when it is no longer useful.
858
- As a legacy from before promises existed,
859
- if an optional callback is passed as second parameter then it will not behave as `async`:
860
-
861
- ```
862
- const server = startServer({port: 8000}, error => console.error(error))
863
- ```
864
-
865
- The following options are available.
866
-
867
- #### `port`
868
-
869
- Optional port to use for the server.
870
-
871
- Note: the default port is 7357, since port 80 requires special privileges.
561
+ The following options are available,
562
+ see [doc/api.md](doc/api.md) for details.
872
563
 
873
- #### `delay`
874
-
875
- Wait the given number of milliseconds to answer each request.
876
-
877
- #### `error`
878
-
879
- Return an HTTP error code.
880
-
881
- #### `percent`
882
-
883
- Return an HTTP error code only for the given % of requests.
884
- If no error code was specified, default is 500.
885
-
886
- #### `logger`
887
-
888
- A function to be called as `logger(request, response)` after every request served by the test server.
889
- Where `request` and `response` are the usual HTTP objects.
564
+ * `port`: optional port to use for the server, default 7357.
565
+ * `delay`: milliseconds to wait before answering each request.
566
+ * `error`: HTTP status code to return, default 200 (no error).
567
+ * `percent`: return error only for the given % of requests.
568
+ * `logger(request, response)`: function to call after every request.
890
569
 
891
570
  ### Configuration file
892
571
 
@@ -932,14 +611,8 @@ For more information about the actual configuration file name, read the [confino
932
611
 
933
612
  ### Complete Example
934
613
 
935
- The file `test/integration.js` shows a complete example, which is also a full integration test:
936
- it starts the server, send 1000 requests, waits for the callback and closes down the server.
937
-
938
- ## Versioning
939
-
940
- Version 3.x uses ES2015 (ES6) features,
941
- such as `const` or `let` and arrow functions.
942
- For ES5 support please use versions 2.x.
614
+ The file `test/integration.js` contains complete examples, which are also a full integration test suite:
615
+ they start the server with different options, send requests, waits for finalization and close down the server.
943
616
 
944
617
  ## Licensed under The MIT License
945
618
 
package/bin/loadtest.js CHANGED
@@ -5,6 +5,7 @@ import * as stdio from 'stdio'
5
5
  import {loadTest} from '../lib/loadtest.js'
6
6
  import {runTask} from '../lib/cluster.js'
7
7
  import {Result} from '../lib/result.js'
8
+ import {getHalfCores} from '../lib/cluster.js'
8
9
 
9
10
 
10
11
  const options = stdio.getopt({
@@ -34,7 +35,7 @@ const options = stdio.getopt({
34
35
  key: {args: 1, description: 'The client key to use'},
35
36
  cert: {args: 1, description: 'The client certificate to use'},
36
37
  quiet: {description: 'Do not log any messages'},
37
- cores: {args: 1, description: 'Number of cores to use', default: 1},
38
+ cores: {args: 1, description: 'Number of cores to use', default: getHalfCores()},
38
39
  agent: {description: 'Use a keep-alive http agent (deprecated)'},
39
40
  debug: {description: 'Show debug messages (deprecated)'},
40
41
  });