Skip to content

Commit

Permalink
Merge pull request #78 from jeremyandrews/user
Browse files Browse the repository at this point in the history
Rename GooseClient to GooseUser
  • Loading branch information
jeremyandrews authored Jun 26, 2020
2 parents 7776528 + a553db9 commit b87b480
Show file tree
Hide file tree
Showing 18 changed files with 627 additions and 634 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Changelog

## 0.7.6-dev
## 0.8.0-dev
- properly subtract previous statistic when handling `set_failure()` and `set_success()`
- detect and track redirects in `GooseRawRequest`
- `--sticky-follow` makes redirect of GooseClient base_url sticky, affecting subsequent requests
- changed `GooseClient` to `GooseUser`

## 0.7.5 June 10, 2020
- store actual URL requested in GooseRawRequest
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.7.6-dev"
version = "0.8.0-dev"
authors = ["Jeremy Andrews <[email protected]>"]
edition = "2018"
description = "A load testing tool inspired by Locust."
Expand Down
55 changes: 28 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ heading:

```toml
[dependencies]
goose = "^0.7"
goose = "^0.8"
```

At this point it's possible to compile all dependencies, though the
Expand All @@ -47,9 +47,9 @@ resulting binary only displays "Hello, world!":
```
$ cargo run
Updating crates.io index
Downloaded goose v0.7.5
Downloaded goose v0.8.0
...
Compiling goose v0.7.5
Compiling goose v0.8.0
Compiling loadtest v0.1.0 (/home/jandrews/devel/rust/loadtest)
Finished dev [unoptimized + debuginfo] target(s) in 52.97s
Running `target/debug/loadtest`
Expand All @@ -65,7 +65,7 @@ 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 GooseClient object, which is used
load testing functions a mutable pointer to a GooseUser object, which is used
to track statistics 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.
Expand All @@ -75,8 +75,8 @@ the host at run time, so you can easily run your load test against different
environments without recompiling:

```rust
async fn loadtest_index(client: &GooseClient) {
let _response = client.get("/").await;
async fn loadtest_index(user: &GooseUser) {
let _response = user.get("/").await;
}
```

Expand Down Expand Up @@ -165,7 +165,7 @@ load tests. For example, pass the `-h` flag to the `simple` example,
`cargo run --example simple -- -h`:

```
client 0.7.5
Goose 0.8.0
CLI options available when launching a Goose load test
USAGE:
Expand All @@ -181,13 +181,12 @@ FLAGS:
--only-summary Only prints summary stats
--reset-stats Resets statistics once hatching has been completed
--status-codes Includes status code counts in console stats
--sticky-follow Client follows redirect of base_url with subsequent requests
--sticky-follow User follows redirect of base_url with subsequent requests
-V, --version Prints version information
-v, --verbose Debug level (-v, -vv, -vvv, etc.)
--worker Enables worker mode
OPTIONS:
-c, --clients <clients> Number of concurrent Goose users (defaults to available CPUs)
--expect-workers <expect-workers>
Required when in manager mode, how many workers to expect [default: 0]
Expand All @@ -200,11 +199,13 @@ OPTIONS:
--manager-port <manager-port> Port manager is listening on [default: 5115]
-t, --run-time <run-time>
Stop after the specified amount of time, e.g. (300s, 20m, 3h, 1h30m, etc.) [default: ]
-u, --users <users> Number of concurrent Goose users (defaults to available CPUs)
```

The `examples/simple.rs` example copies the simple load test documented on the locust.io web page, rewritten in Rust for Goose. It uses minimal advanced functionality, but demonstrates how to GET and POST pages. It defines a single Task Set which has the client log in and then load a couple of pages.
The `examples/simple.rs` example copies the simple load test documented on the locust.io web page, rewritten in Rust for Goose. It uses minimal advanced functionality, but demonstrates how to GET and POST pages. It defines a single Task Set which has the user log in and then load a couple of pages.

Goose can make use of all available CPU cores. By default, it will launch 1 client per core, and it can be configured to launch many more. The following was configured instead to launch 1,024 clients. Each client randomly pauses 5 to 15 seconds after each task is loaded, so it's possible to spin up a large number of clients. Here is a snapshot of `top` when running this example on an 8-core VM with 10G of available RAM -- there were ample resources to launch considerably more "clients", though `ulimit` had to be resized:
Goose can make use of all available CPU cores. By default, it will launch 1 user per core, and it can be configured to launch many more. The following was configured instead to launch 1,024 users. Each user randomly pauses 5 to 15 seconds after each task is loaded, so it's possible to spin up a large number of users. Here is a snapshot of `top` when running this example on an 8-core VM with 10G of available RAM -- there were ample resources to launch considerably more "users", though `ulimit` had to be resized:

```
top - 11:14:57 up 16 days, 4:40, 2 users, load average: 0.00, 0.04, 0.01
Expand All @@ -217,7 +218,7 @@ MiB Swap: 10237.0 total, 10234.7 free, 2.3 used. 8401.5 avail Mem
19776 goose 20 0 9.8g 874688 8252 S 6.3 8.5 0:42.90 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 `-c1024` tells Goose to spin up 1,024 clients. The `-r32` option tells Goose to spin up 32 clients per second. The `-t 10m` option tells Goose to run the load test for 10 minutes, or 600 seconds. The `--print-stats` flag tells Goose to collect statistics during the load test, and the `--status-codes` flag tells it to include statistics about HTTP Status codes returned by the server. Finally, the `--only-summary` flag tells Goose to only display the statistics when the load test finishes, otherwise it would display running statistics 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 spin up 32 users per second. The `-t 10m` option tells Goose to run the load test for 10 minutes, or 600 seconds. The `--print-stats` flag tells Goose to collect statistics during the load test, and the `--status-codes` flag tells it to include statistics about HTTP Status codes returned by the server. Finally, the `--only-summary` flag tells Goose to only display the statistics when the load test finishes, otherwise it would display running statistics every 15 seconds for the duration of the test.

```
$ cargo run --release --example simple -- --host http://apache.fosciana -v -c1024 -r32 -t 10m --print-stats --status-codes --only-summary
Expand All @@ -228,24 +229,24 @@ $ cargo run --release --example simple -- --host http://apache.fosciana -v -c102
18:42:48 [ INFO] Writing to log file: goose.log
18:42:48 [ INFO] run_time = 600
18:42:48 [ INFO] global host configured: http://apache.fosciana
18:42:53 [ INFO] launching client 1 from WebsiteUser...
18:42:53 [ INFO] launching client 2 from WebsiteUser...
18:42:53 [ INFO] launching client 3 from WebsiteUser...
18:42:53 [ INFO] launching client 4 from WebsiteUser...
18:42:53 [ INFO] launching client 5 from WebsiteUser...
18:42:53 [ INFO] launching client 6 from WebsiteUser...
18:42:53 [ INFO] launching client 7 from WebsiteUser...
18:42:53 [ INFO] launching client 8 from WebsiteUser...
18:42:53 [ INFO] launching user 1 from WebsiteUser...
18:42:53 [ INFO] launching user 2 from WebsiteUser...
18:42:53 [ INFO] launching user 3 from WebsiteUser...
18:42:53 [ INFO] launching user 4 from WebsiteUser...
18:42:53 [ INFO] launching user 5 from WebsiteUser...
18:42:53 [ INFO] launching user 6 from WebsiteUser...
18:42:53 [ INFO] launching user 7 from WebsiteUser...
18:42:53 [ INFO] launching user 8 from WebsiteUser...
```
...
```
18:43:25 [ INFO] launching client 1022 from WebsiteUser...
18:43:25 [ INFO] launching client 1023 from WebsiteUser...
18:43:25 [ INFO] launching client 1024 from WebsiteUser...
18:43:25 [ INFO] launched 1024 clients...
18:43:25 [ INFO] launching user 1022 from WebsiteUser...
18:43:25 [ INFO] launching user 1023 from WebsiteUser...
18:43:25 [ INFO] launching user 1024 from WebsiteUser...
18:43:25 [ INFO] launched 1024 users...
18:53:26 [ INFO] stopping after 600 seconds...
18:53:26 [ INFO] waiting for clients to exit
18:53:26 [ INFO] waiting for users to exit
------------------------------------------------------------------------------
Name | # reqs | # fails | req/s | fail/s
-----------------------------------------------------------------------------
Expand Down Expand Up @@ -305,7 +306,7 @@ feature in the `dependencies` section of your `Cargo.toml`, for example:

```toml
[dependencies]
goose = { version = "^0.7", features = ["gaggle"] }
goose = { version = "^0.8", features = ["gaggle"] }
```

### Goose Manager
Expand Down Expand Up @@ -362,7 +363,7 @@ The `--no-stats`, `--only-summary`, `--reset-stats`, `--status-codes`, and `--no
* `--manager-host <manager-host>`: configures the host that the worker will talk to the manager on. By default, a Goose worker will connect to the localhost, or `127.0.0.1`. In a distributed load test, this must be set to the IP of the Goose manager.
* `--manager-port <manager-port>`: configures the port that a worker will talk to the manager on. By default, a Goose worker will connect to port `5115`.

The `--clients`, `--hatch-rate`, `--host`, and `--run-time` options must be set on the manager. Workers inheret these options from the manager.
The `--users`, `--hatch-rate`, `--host`, and `--run-time` options must be set on the manager. Workers inheret these options from the manager.

### Technical Details

Expand Down
50 changes: 25 additions & 25 deletions examples/drupal_loadtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ fn main() {
}

/// View the front page.
async fn drupal_loadtest_front_page(client: &GooseClient) {
let mut response = client.get("/").await;
async fn drupal_loadtest_front_page(user: &GooseUser) {
let mut response = user.get("/").await;

// Grab some static assets from the front page.
match response.response {
Expand All @@ -97,36 +97,36 @@ async fn drupal_loadtest_front_page(client: &GooseClient) {
}
}
for index in 0..urls.len() {
client.get_named(&urls[index], "static asset").await;
user.get_named(&urls[index], "static asset").await;
}
}
Err(e) => {
eprintln!("failed to parse front page: {}", e);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
}
},
Err(e) => {
eprintln!("unexpected error when loading front page: {}", e);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
}
}
}

/// View a node from 1 to 10,000, created by preptest.sh.
async fn drupal_loadtest_node_page(client: &GooseClient) {
async fn drupal_loadtest_node_page(user: &GooseUser) {
let nid = rand::thread_rng().gen_range(1, 10_000);
let _response = client.get(format!("/node/{}", &nid).as_str()).await;
let _response = user.get(format!("/node/{}", &nid).as_str()).await;
}

/// View a profile from 2 to 5,001, created by preptest.sh.
async fn drupal_loadtest_profile_page(client: &GooseClient) {
async fn drupal_loadtest_profile_page(user: &GooseUser) {
let uid = rand::thread_rng().gen_range(2, 5_001);
let _response = client.get(format!("/user/{}", &uid).as_str()).await;
let _response = user.get(format!("/user/{}", &uid).as_str()).await;
}

/// Log in.
async fn drupal_loadtest_login(client: &GooseClient) {
let mut response = client.get("/user").await;
async fn drupal_loadtest_login(user: &GooseUser) {
let mut response = user.get("/user").await;
match response.response {
Ok(r) => {
match r.text().await {
Expand All @@ -136,7 +136,7 @@ async fn drupal_loadtest_login(client: &GooseClient) {
Some(f) => f,
None => {
eprintln!("no form_build_id on page: /user page");
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
return;
}
};
Expand All @@ -151,13 +151,13 @@ async fn drupal_loadtest_login(client: &GooseClient) {
("form_id", "user_login"),
("op", "Log+in"),
];
let request_builder = client.goose_post("/user").await;
let _response = client.goose_send(request_builder.form(&params), None).await;
let request_builder = user.goose_post("/user").await;
let _response = user.goose_send(request_builder.form(&params), None).await;
// @TODO: verify that we actually logged in.
}
Err(e) => {
eprintln!("unexpected error when loading /user page: {}", e);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
}
}
}
Expand All @@ -167,11 +167,11 @@ async fn drupal_loadtest_login(client: &GooseClient) {
}

/// Post a comment.
async fn drupal_loadtest_post_comment(client: &GooseClient) {
async fn drupal_loadtest_post_comment(user: &GooseUser) {
let nid: i32 = rand::thread_rng().gen_range(1, 10_000);
let node_path = format!("node/{}", &nid);
let comment_path = format!("/comment/reply/{}", &nid);
let mut response = client.get(&node_path).await;
let mut response = user.get(&node_path).await;
match response.response {
Ok(r) => {
match r.text().await {
Expand All @@ -182,7 +182,7 @@ async fn drupal_loadtest_post_comment(client: &GooseClient) {
Some(f) => f,
None => {
eprintln!("no form_build_id found on {}", &node_path);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
return;
}
};
Expand All @@ -192,7 +192,7 @@ async fn drupal_loadtest_post_comment(client: &GooseClient) {
Some(f) => f,
None => {
eprintln!("no form_token found on {}", &node_path);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
return;
}
};
Expand All @@ -202,7 +202,7 @@ async fn drupal_loadtest_post_comment(client: &GooseClient) {
Some(f) => f,
None => {
eprintln!("no form_id found on {}", &node_path);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
return;
}
};
Expand All @@ -218,8 +218,8 @@ async fn drupal_loadtest_post_comment(client: &GooseClient) {
("form_id", &form_id[1]),
("op", "Save"),
];
let request_builder = client.goose_post(&comment_path).await;
let mut response = client.goose_send(request_builder.form(&params), None).await;
let request_builder = user.goose_post(&comment_path).await;
let mut response = user.goose_send(request_builder.form(&params), None).await;
match response.response {
Ok(r) => match r.text().await {
Ok(html) => {
Expand All @@ -228,15 +228,15 @@ async fn drupal_loadtest_post_comment(client: &GooseClient) {
"no comment showed up after posting to {}",
&comment_path
);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
}
}
Err(e) => {
eprintln!(
"unexpected error when posting to {}: {}",
&comment_path, e
);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
}
},
// Goose will catch this error.
Expand All @@ -245,7 +245,7 @@ async fn drupal_loadtest_post_comment(client: &GooseClient) {
}
Err(e) => {
eprintln!("unexpected error when loading {} page: {}", &node_path, e);
client.set_failure(&mut response.request);
user.set_failure(&mut response.request);
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn main() {
taskset!("WebsiteUser")
// After each task runs, sleep randomly from 5 to 15 seconds.
.set_wait_time(5, 15)
// This task only runs one time when the client first starts.
// This task only runs one time when the user first starts.
.register_task(task!(website_login).set_on_start())
// These next two tasks run repeatedly as long as the load test is running.
.register_task(task!(website_index))
Expand All @@ -35,22 +35,22 @@ fn main() {
.execute();
}

/// Demonstrates how to log in when a client starts. We flag this task as an
/// Demonstrates how to log in when a user starts. We flag this task as an
/// on_start task when registering it above. This means it only runs one time
/// per client, when the client thread first starts.
async fn website_login(client: &GooseClient) {
let request_builder = client.goose_post("/login").await;
/// per user, when the user thread first starts.
async fn website_login(user: &GooseUser) {
let request_builder = user.goose_post("/login").await;
// https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form
let params = [("username", "test_user"), ("password", "")];
let _response = client.goose_send(request_builder.form(&params), None).await;
let _response = user.goose_send(request_builder.form(&params), None).await;
}

/// A very simple task that simply loads the front page.
async fn website_index(client: &GooseClient) {
let _response = client.get("/").await;
async fn website_index(user: &GooseUser) {
let _response = user.get("/").await;
}

/// A very simple task that simply loads the about page.
async fn website_about(client: &GooseClient) {
let _response = client.get("/about/").await;
async fn website_about(user: &GooseUser) {
let _response = user.get("/about/").await;
}
Loading

0 comments on commit b87b480

Please sign in to comment.