Skip to content

Commit

Permalink
Merge pull request #160 from jeremyandrews/v0.10
Browse files Browse the repository at this point in the history
Release 0.10
  • Loading branch information
jeremyandrews authored Sep 13, 2020
2 parents 0fa6687 + cc7cbb9 commit 91023b4
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 135 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

## 0.10.0-dev
## 0.10.0 Sep 13, 2020
- default to resetting statistics, disable with `--no-reset-stats`, display spawning statistics before resetting
- only run gaggle integration tests when feature is enabled
- prevent time-drift when launching users and throttling requests
Expand Down Expand Up @@ -67,7 +67,7 @@
- introduce `GooseError` and `GooseTaskError`
- change task function signature, tasks must return a `GooseTaskResult`
- change `GooseAttack` method signatures where an error is possible
- where possible, passs error up the stack instead of calling `exit(1)`
- where possible, pass error up the stack instead of calling `exit(1)`
- introduce `GooseAttack.display()` which consumes the load test state and displays statistics
- `panic!()` on unexpected errors instead of `exit(1)`

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "goose"
version = "0.10.0-dev"
version = "0.10.0"
authors = ["Jeremy Andrews <[email protected]>"]
edition = "2018"
description = "A load testing tool inspired by Locust."
Expand Down
32 changes: 14 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ To create an actual load test, you first have to add the following boilerplate t
use goose::prelude::*;
```

Then create a new load testing function. For our example we're simply going to load the front page of the website we're load-testing. Goose passes all load testing functions a mutable pointer to a GooseUser object, which is used to track metrics and make web requests. Thanks to the Reqwest library, the Goose client manages things like cookies, headers, and sessions for you. Load testing functions must be declared async, which helps ensure that your simulated users don't become CPU-locked.
Then create a new load testing function. For our example we're simply going to load the front page of the website we're load-testing. Goose passes all load testing functions a pointer to a GooseUser object, which is used to track metrics and make web requests. Thanks to the Reqwest library, the Goose client manages things like cookies, headers, and sessions for you. Load testing functions must be declared async, which helps ensure that your simulated users don't become CPU-locked.

In load test functions you typically do not set the host, and instead configure the host at run time, so you can easily run your load test against different environments without recompiling. The following `loadtest_index` function simply loads the front page of our web page:

Expand All @@ -62,9 +62,9 @@ async fn loadtest_index(user: &GooseUser) -> GooseTaskResult {
}
```

The function is declared `async` so that we don't block a CPU-core while loading web pages. All Goose load test functions are passed in a reference to a `GooseUser` object, and return a `GooseTaskResult` which is either an empty `Ok(())` on success, or a `GooseTaskError` on failure. We use the `GooseUser` object to make requests, in this case we make a `GET` request for the front page, `/`. The `.await` tells frees up the CPU-core while we wait for the web page to respond, and the tailing `?` passes up any unexpected errors that may be returned from this request. When the request completes, Goose returns metrics which we store in `_goose_metrics` variable. The variable is prefixed with an underscore (`_`) to tell the compiler we are intentionally not using the results. Finally, after making a single successful request, we return `Ok(())` to let Goose know this task function completed successfully.
The function is declared `async` so that we don't block a CPU-core while loading web pages. All Goose load test functions are passed in a reference to a `GooseUser` object, and return a `GooseTaskResult` which is either an empty `Ok(())` on success, or a `GooseTaskError` on failure. We use the `GooseUser` object to make requests, in this case we make a `GET` request for the front page, `/`. The `.await` frees up the CPU-core while we wait for the web page to respond, and the tailing `?` passes up any unexpected errors that may be returned from this request. When the request completes, Goose returns metrics which we store in the `_goose_metrics` variable. The variable is prefixed with an underscore (`_`) to tell the compiler we are intentionally not using the results. Finally, after making a single successful request, we return `Ok(())` to let Goose know this task function completed successfully.

Finally, we have to tell Goose about our new task function. Edit the `main()` function, setting a return type and replacing the hello world text as follows:
We have to tell Goose about our new task function. Edit the `main()` function, setting a return type and replacing the hello world text as follows:

```rust
fn main() -> Result<(), GooseError> {
Expand Down Expand Up @@ -161,11 +161,11 @@ The per-task metrics are displayed first, starting with the name of our Task Set
Next comes the per-request metrics. Our single task makes a `GET` request for the `/` path, so it shows up in the metrics as `GET /`. Comparing the per-task metrics collected for `1: ` to the per-request metrics collected for `GET /`, you can see that they are the same.
There are two common tables found in each type of metrics. The first shows the total number of requests made (1,274), how many of those failed (0), the average number of requests per second (424), and the average number of failed requests per second (0).
There are two common tables found in each type of metrics. The first shows the total number of requests made (2,054), how many of those failed (0), the average number of requests per second (410.8), and the average number of failed requests per second (0).
The second table shows the average time required to load a page (20 milliseconds), the minimum time to load a page (8 ms), the maximum time to load a page (213 ms) and the median time to load a page (19 ms).
The second table shows the average time required to load a page (20.68 milliseconds), the minimum time to load a page (7 ms), the maximum time to load a page (254 ms) and the median time to load a page (19 ms).
The per-request metrics include a third table, showing the slowest page load time for a range of percentiles. In our example, in the 50% fastest page loads, the slowest page loaded in 18 ms. In the 75% fastest page loads, the slowest page loaded in 21 ms, etc.
The per-request metrics include a third table, showing the slowest page load time for a range of percentiles. In our example, in the 50% fastest page loads, the slowest page loaded in 19 ms. In the 75% fastest page loads, the slowest page loaded in 21 ms, etc.
In real load tests, you'll most likely have multiple task sets each with multiple tasks, and Goose will show you metrics for each along with an aggregate of them all together.
Expand All @@ -181,7 +181,7 @@ optimized code. This can generate considerably more load test traffic.
## Simple Example
The `-h` flag will show all run-time configuration options available to Goose load tests. For example, pass the `-h` flag to the `simple` example, `cargo run --example simple -- -h`:
The `-h` flag will show all run-time configuration options available to Goose load tests. For example, you can pass the `-h` flag to the `simple` example as follows, `cargo run --example simple -- -h`:
```
Usage: target/debug/examples/simple [OPTIONS]
Expand Down Expand Up @@ -243,7 +243,7 @@ MiB Swap: 10237.0 total, 10237.0 free, 0.0 used. 8606.9 avail Mem
1339 goose 20 0 1235480 758292 8984 R 3.0 7.4 0:06.56 simple
```
Here's the output of running the loadtest. The `-v` flag sends `INFO` and more critical messages to stdout (in addition to the log file). The `-u1024` tells Goose to spin up 1,024 users. The `-r32` option tells Goose to hatch 32 users per second. The `-t10m` option tells Goose to run the load test for 10 minutes, or 600 seconds. The `--status-codes` flag tells Goose to track metrics about HTTP Status codes returned by the server, in addition to the default per-task and per-request metrics. The `--no-reset-metrics` flag tells Goose to start tracking the 10m run-time from when the first user starts, instead of the default which is to flush all metrics and start timing after all users have started. And finally, the `--only-summary` flag tells Goose to only display the final metrics after the load test finishes, otherwise it would display running metrics every 15 seconds for the duration of the test.
Here's the output of running the loadtest. The `-v` flag sends `INFO` and more critical messages to stdout (in addition to the log file). The `-u1024` tells Goose to spin up 1,024 users. The `-r32` option tells Goose to hatch 32 users per second. The `-t10m` option tells Goose to run the load test for 10 minutes, or 600 seconds. The `--status-codes` flag tells Goose to track metrics about HTTP status codes returned by the server, in addition to the default per-task and per-request metrics. The `--no-reset-metrics` flag tells Goose to start tracking the 10m run-time from when the first user starts, instead of the default which is to flush all metrics and start timing after all users have started. And finally, the `--only-summary` flag tells Goose to only display the final metrics after the load test finishes, otherwise it would display running metrics every 15 seconds for the duration of the test.
```
$ cargo run --release --example simple -- --host http://local.dev -v -u1024 -r32 -t10m --status-codes --no-reset-metrics --only-summary
Expand Down Expand Up @@ -434,8 +434,7 @@ By default, logs are written in JSON Lines format. For example:
```
Logs include the entire `GooseRawRequest` object as defined in `src/goose.rs`, which are created on all requests. This object includes the following fields:
- `elapsed`: total milliseconds between when the `GooseUser` thread started and this
request was made;
- `elapsed`: total milliseconds between when the `GooseUser` thread started and this request was made;
- `method`: the type of HTTP request made;
- `name`: the name of the request;
- `url`: the URL that was requested;
Expand All @@ -444,10 +443,7 @@ Logs include the entire `GooseRawRequest` object as defined in `src/goose.rs`, w
- `response_time`: how many milliseconds the request took;
- `status_code`: the HTTP response code returned for this request;
- `success`: true or false if this was a successful request;
- `update`: true or false if this is a recurrence of a previous log entry, but with
`success` toggling between `true` and `false`. This happens when a load test calls
`set_success()` on a request that Goose previously interpreted as a failure, or
`set_failure()` on a request previously interpreted as a success;
- `update`: true or false if this is a recurrence of a previous log entry, but with `success` toggling between `true` and `false`. This happens when a load test calls `set_success()` on a request that Goose previously interpreted as a failure, or `set_failure()` on a request previously interpreted as a success;
- `user`: an integer value indicating which `GooseUser` thread made this request.
In the first line of the above example, `GooseUser` thread 0 made a `POST` request to `/login` and was successfully redirected to `/user/42` in 220 milliseconds. The second line is the same `GooseUser` thread which then made a `GET` request to `/` in 3 milliseconds. The third and fourth lines are a second `GooseUser` thread doing the same thing, first logging in and then loading the front page.
Expand All @@ -465,11 +461,11 @@ elapsed,method,name,url,final_url,redirected,response_time,status_code,success,u
## Load Test Debug Logging
Goose can optionally log details about requests and responses for debug purposes. When writing a load test you must invoke `client.log_debug(tag, Option<request>, Option<headers>, Option<body>)` where `tag` is an arbitrary string to identify where in the load test and/or why debug is being written, `request` is a `GooseRawRequest` object, `headers` are the HTTP headers returned by the server, and `body` is the web page body returned by the server.
Goose can optionally log details about requests and responses for debug purposes. When writing a load test you must invoke `client.log_debug(tag, Option<request>, Option<headers>, Option<body>)` where `tag` is an arbitrary string to identify where in the load test and/or why debug is being written, `request` is an optional reference to a `GooseRawRequest` object, `headers` are an optional reference to the HTTP headers returned by the server, and `body` is an optionial reference to the web page body returned by the server.
For an example on how to correctly use `client.log_debug()`, including how to obtain the response headers and body, see `examples/drupal_loadtest`.
If the load test is run with the `--debug-log-file=foo` command line option, where `foo` is either a relative or an absolute path, Goose will log all debug generated by calls to `client.log_debug()` to this file. Debug is logged in JSON Lines format. For example:
If the load test is run with the `--debug-log-file=foo` command line option, where `foo` is either a relative or an absolute path, Goose will log all debug generated by calls to `client.log_debug()` to this file. For example:
```json
{"body":"<!DOCTYPE html>\n<html>\n <head>\n <title>503 Backend fetch failed</title>\n </head>\n <body>\n <h1>Error 503 Backend fetch failed</h1>\n <p>Backend fetch failed</p>\n <h3>Guru Meditation:</h3>\n <p>XID: 923425</p>\n <hr>\n <p>Varnish cache server</p>\n </body>\n</html>\n","header":"{\"date\": \"Wed, 01 Jul 2020 10:27:31 GMT\", \"server\": \"Varnish\", \"content-type\": \"text/html; charset=utf-8\", \"retry-after\": \"5\", \"x-varnish\": \"923424\", \"age\": \"0\", \"via\": \"1.1 varnish (Varnish/6.1)\", \"x-varnish-cache\": \"MISS\", \"x-varnish-cookie\": \"SESSd7e04cba6a8ba148c966860632ef3636=hejsW1mQnnsHlua0AicCjEpUjnCRTkOLubwL33UJXRU\", \"content-length\": \"283\", \"connection\": \"keep-alive\"}","request":{"elapsed":4192,"final_url":"http://local.dev/node/3247","method":"GET","name":"(Auth) comment form","redirected":false,"response_time":8,"status_code":503,"success":false,"update":false,"url":"http://local.dev/node/3247","user":4},"tag":"post_comment: no form_build_id found on node/3247"}
Expand All @@ -495,7 +491,7 @@ When writing load test applications, you can default to compiling in the Gaggle
```toml
[dependencies]
goose = { version = "^0.9", features = ["gaggle"] }
goose = { version = "^0.10", features = ["gaggle"] }
```
### Gaggle Manager
Expand Down Expand Up @@ -555,5 +551,5 @@ By default Reqwest (and therefore Goose) uses the system-native transport layer
```toml
[dependencies]
goose = { version = "^0.9", default-features = false, features = ["rustls"] }
goose = { version = "^0.10", default-features = false, features = ["rustls"] }
```
26 changes: 13 additions & 13 deletions src/goose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
//!
//! let mut a_task = task!(task_function);
//!
//! /// A very simple task that simply loads the front page.
//! /// A very simple task that loads the front page.
//! async fn task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/").await?;
//!
Expand All @@ -93,7 +93,7 @@
//!
//! let mut a_task = task!(task_function).set_name("a");
//!
//! /// A very simple task that simply loads the front page.
//! /// A very simple task that loads the front page.
//! async fn task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/").await?;
//!
Expand All @@ -117,14 +117,14 @@
//! Ok(())
//! }
//!
//! /// A very simple task that simply loads the "a" page.
//! /// A very simple task that loads the "a" page.
//! async fn a_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/a/").await?;
//!
//! Ok(())
//! }
//!
//! /// Another very simple task that simply loads the "b" page.
//! /// Another very simple task that loads the "b" page.
//! async fn b_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/b/").await?;
//!
Expand All @@ -139,7 +139,7 @@
//! be applied to sequenced tasks, so for example a task with a weight of `2` and a sequence
//! of `1` will run two times before a task with a sequence of `2`. Task sets can contain
//! tasks with sequence values and without sequence values, and in this case all tasks with
//! a sequence value will run before tasks without a sequence value. In the folllowing example,
//! a sequence value will run before tasks without a sequence value. In the following example,
//! `a_task` runs before `b_task`, which runs before `c_task`:
//!
//! ```rust
Expand All @@ -149,21 +149,21 @@
//! let mut b_task = task!(b_task_function).set_sequence(2);
//! let mut c_task = task!(c_task_function);
//!
//! /// A very simple task that simply loads the "a" page.
//! /// A very simple task that loads the "a" page.
//! async fn a_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/a/").await?;
//!
//! Ok(())
//! }
//!
//! /// Another very simple task that simply loads the "b" page.
//! /// Another very simple task that loads the "b" page.
//! async fn b_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/b/").await?;
//!
//! Ok(())
//! }
//!
//! /// Another very simple task that simply loads the "c" page.
//! /// Another very simple task that loads the "c" page.
//! async fn c_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/c/").await?;
//!
Expand All @@ -184,7 +184,7 @@
//!
//! let mut a_task = task!(a_task_function).set_sequence(1).set_on_start();
//!
//! /// A very simple task that simply loads the "a" page.
//! /// A very simple task that loads the "a" page.
//! async fn a_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/a/").await?;
//!
Expand All @@ -195,7 +195,7 @@
//! ### Task On Stop
//!
//! Tasks can be flagged to only run when a user stops. This can be useful if you'd like your
//! load test to simluate a user logging out when it finishes. It is possible to assign sequences
//! load test to simulate a user logging out when it finishes. It is possible to assign sequences
//! and weights to `on_stop` functions if you want to have multiple tasks run in a specific order
//! at stop time, and/or the tasks to run multiple times. A task can be flagged to run both on
//! start and on stop.
Expand All @@ -205,7 +205,7 @@
//!
//! let mut b_task = task!(b_task_function).set_sequence(2).set_on_stop();
//!
//! /// Another very simple task that simply loads the "b" page.
//! /// Another very simple task that loads the "b" page.
//! async fn b_task_function(user: &GooseUser) -> GooseTaskResult {
//! let _goose = user.get("/b/").await?;
//!
Expand Down Expand Up @@ -243,7 +243,7 @@
//! }
//! ```
//!
//! The returned response is a [`reqwest::blocking::Response`](https://docs.rs/reqwest/*/reqwest/blocking/struct.Response.html)
//! The returned response is a [`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)
//! struct. You can use it as you would any Reqwest Response.
//!
//!
Expand Down Expand Up @@ -508,7 +508,7 @@ impl GooseTaskSet {
/// let mut example_tasks = taskset!("ExampleTasks");
/// example_tasks.register_task(task!(a_task_function));
///
/// /// A very simple task that simply loads the "a" page.
/// /// A very simple task that loads the "a" page.
/// async fn a_task_function(user: &GooseUser) -> GooseTaskResult {
/// let _goose = user.get("/a/").await?;
///
Expand Down
Loading

0 comments on commit 91023b4

Please sign in to comment.