Struct goose::goose::GooseUser [−][src]
pub struct GooseUser {Show 17 fields
pub started: Instant,
pub task_sets_index: usize,
pub client: Arc<Mutex<Client>>,
pub position: Arc<AtomicUsize>,
pub base_url: Arc<RwLock<Url>>,
pub min_wait: usize,
pub max_wait: usize,
pub config: GooseConfiguration,
pub logger: Option<Sender<Option<GooseLog>>>,
pub throttle: Option<Sender<bool>>,
pub is_throttled: bool,
pub channel_to_parent: Option<Sender<GooseMetric>>,
pub weighted_users_index: usize,
pub weighted_on_start_tasks: Vec<(usize, String)>,
pub weighted_tasks: Vec<(usize, String)>,
pub weighted_on_stop_tasks: Vec<(usize, String)>,
pub load_test_hash: u64,
// some fields omitted
}
Expand description
An individual user state, repeatedly running all GooseTask
s
in a specific GooseTaskSet
.
Fields
started: Instant
The Instant when this GooseUser
client started.
task_sets_index: usize
An index into the internal GooseAttack
.task_sets
vector, indicating which GooseTaskSet
is running.
client: Arc<Mutex<Client>>
Client used to make requests, managing sessions and cookies.
position: Arc<AtomicUsize>
Integer value tracking the current task the user is running.
base_url: Arc<RwLock<Url>>
The base URL to prepend to all relative paths.
min_wait: usize
Minimum amount of time to sleep after running a task.
max_wait: usize
Maximum amount of time to sleep after running a task.
config: GooseConfiguration
A local copy of the global GooseConfiguration
.
logger: Option<Sender<Option<GooseLog>>>
Channel to logger.
throttle: Option<Sender<bool>>
Channel to throttle.
is_throttled: bool
Normal tasks are optionally throttled,
test_start
and
test_stop
tasks are not.
channel_to_parent: Option<Sender<GooseMetric>>
Channel to parent.
weighted_users_index: usize
An index into the internal GooseAttack
.weighted_users
vector, indicating which weighted GooseUser
is running.
weighted_on_start_tasks: Vec<(usize, String)>
A weighted list of all tasks that run when the user first starts.
weighted_tasks: Vec<(usize, String)>
A weighted list of all tasks that this user runs once started.
weighted_on_stop_tasks: Vec<(usize, String)>
A weighted list of all tasks that run when the user stops.
load_test_hash: u64
Load test hash.
Implementations
pub fn new(
task_sets_index: usize,
base_url: Url,
min_wait: usize,
max_wait: usize,
configuration: &GooseConfiguration,
load_test_hash: u64
) -> Result<Self, GooseError>
pub fn new(
task_sets_index: usize,
base_url: Url,
min_wait: usize,
max_wait: usize,
configuration: &GooseConfiguration,
load_test_hash: u64
) -> Result<Self, GooseError>
Create a new user state.
Create a new single-use user.
A helper that prepends a base_url
to all relative paths.
A base_url
is determined per user thread, using the following order
of precedence:
--host
(host specified on the command line when running load test)GooseTaskSet
.host
(default host defined for the current task set)GooseDefault::Host
(default host defined for the current load test)
A helper to make a GET
request of a path and collect relevant metrics.
Automatically prepends the correct host.
(If you need to set headers, change timeouts, or otherwise make use of the
reqwest::RequestBuilder
object, you can instead call goose_get
which returns a
RequestBuilder
,
then call
goose_send
to invoke the request.)
Calls to get()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(get_function);
/// A very simple task that makes a GET request.
async fn get_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.get("/path/to/foo/").await?;
Ok(())
}
pub async fn get_named(
&self,
path: &str,
request_name: &str
) -> Result<GooseResponse, GooseTaskError>
pub async fn get_named(
&self,
path: &str,
request_name: &str
) -> Result<GooseResponse, GooseTaskError>
A helper to make a named GET
request of a path and collect relevant metrics.
Automatically prepends the correct host. Naming a request only affects collected
metrics.
Calls to get_named()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(get_function);
/// A very simple task that makes a GET request.
async fn get_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.get_named("/path/to/foo/", "foo").await?;
Ok(())
}
A helper to make a POST
request of a path and collect relevant metrics.
Automatically prepends the correct host.
(If you need to set headers, change timeouts, or otherwise make use of the
reqwest::RequestBuilder
object, you can instead call goose_post
which returns a
RequestBuilder
,
then call goose_send
to invoke the request.)
Calls to post()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(post_function);
/// A very simple task that makes a POST request.
async fn post_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.post("/path/to/foo/", "BODY BEING POSTED").await?;
Ok(())
}
pub async fn post_named(
&self,
path: &str,
request_name: &str,
body: &str
) -> Result<GooseResponse, GooseTaskError>
pub async fn post_named(
&self,
path: &str,
request_name: &str,
body: &str
) -> Result<GooseResponse, GooseTaskError>
A helper to make a named POST
request of a path and collect relevant metrics.
Automatically prepends the correct host. Naming a request only affects collected
metrics.
Calls to post_named()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(post_function);
/// A very simple task that makes a POST request.
async fn post_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.post_named("/path/to/foo/", "foo", "BODY BEING POSTED").await?;
Ok(())
}
A helper to make a HEAD
request of a path and collect relevant metrics.
Automatically prepends the correct host.
(If you need to set headers, change timeouts, or otherwise make use of the
reqwest::RequestBuilder
object, you can instead call goose_head
which returns a
RequestBuilder
,
then call goose_send
to invoke the request.)
Calls to head()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(head_function);
/// A very simple task that makes a HEAD request.
async fn head_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.head("/path/to/foo/").await?;
Ok(())
}
pub async fn head_named(
&self,
path: &str,
request_name: &str
) -> Result<GooseResponse, GooseTaskError>
pub async fn head_named(
&self,
path: &str,
request_name: &str
) -> Result<GooseResponse, GooseTaskError>
A helper to make a named HEAD
request of a path and collect relevant metrics.
Automatically prepends the correct host. Naming a request only affects collected
metrics.
Calls to head_named()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(head_function);
/// A very simple task that makes a HEAD request.
async fn head_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.head_named("/path/to/foo/", "foo").await?;
Ok(())
}
A helper to make a DELETE
request of a path and collect relevant metrics.
Automatically prepends the correct host.
(If you need to set headers, change timeouts, or otherwise make use of the
reqwest::RequestBuilder
object, you can instead call goose_delete
which returns a
RequestBuilder
,
then call goose_send
to invoke the request.)
Calls to delete()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(delete_function);
/// A very simple task that makes a DELETE request.
async fn delete_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.delete("/path/to/foo/").await?;
Ok(())
}
pub async fn delete_named(
&self,
path: &str,
request_name: &str
) -> Result<GooseResponse, GooseTaskError>
pub async fn delete_named(
&self,
path: &str,
request_name: &str
) -> Result<GooseResponse, GooseTaskError>
A helper to make a named DELETE
request of a path and collect relevant metrics.
Automatically prepends the correct host. Naming a request only affects collected
metrics.
Calls to delete_named()
return a GooseResponse
object which
contains a copy of the request you made (GooseRequestMetric
),
and the response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(delete_function);
/// A very simple task that makes a DELETE request.
async fn delete_function(user: &GooseUser) -> GooseTaskResult {
let _goose = user.delete_named("/path/to/foo/", "foo").await?;
Ok(())
}
Prepends the correct host on the path, then prepares a
reqwest::RequestBuilder
object for making a GET
request.
(You must then call goose_send
on this
object to actually execute the request.)
Example
use goose::prelude::*;
let mut task = task!(get_function);
/// A simple task that makes a GET request, exposing the Reqwest
/// request builder.
async fn get_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_get("/path/to/foo").await?;
let _goose = user.goose_send(request_builder, None).await?;
Ok(())
}
Prepends the correct host on the path, then prepares a
reqwest::RequestBuilder
object for making a POST
request.
(You must then call goose_send
on this
object to actually execute the request.)
Example
use goose::prelude::*;
let mut task = task!(post_function);
/// A simple task that makes a POST request, exposing the Reqwest
/// request builder.
async fn post_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_post("/path/to/foo").await?;
let _goose = user.goose_send(request_builder, None).await?;
Ok(())
}
Prepends the correct host on the path, then prepares a
reqwest::RequestBuilder
object for making a HEAD
request.
(You must then call goose_send
on this
object to actually execute the request.)
Example
use goose::prelude::*;
let mut task = task!(head_function);
/// A simple task that makes a HEAD request, exposing the Reqwest
/// request builder.
async fn head_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_head("/path/to/foo").await?;
let _goose = user.goose_send(request_builder, None).await?;
Ok(())
}
Prepends the correct host on the path, then prepares a
reqwest::RequestBuilder
object for making a PUT
request.
(You must then call goose_send
on this
object to actually execute the request.)
Example
use goose::prelude::*;
let mut task = task!(put_function);
/// A simple task that makes a PUT request, exposing the Reqwest
/// request builder.
async fn put_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_put("/path/to/foo").await?;
let _goose = user.goose_send(request_builder, None).await?;
Ok(())
}
Prepends the correct host on the path, then prepares a
reqwest::RequestBuilder
object for making a PATCH
request.
(You must then call goose_send
on this
object to actually execute the request.)
Example
use goose::prelude::*;
let mut task = task!(patch_function);
/// A simple task that makes a PUT request, exposing the Reqwest
/// request builder.
async fn patch_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_patch("/path/to/foo").await?;
let _goose = user.goose_send(request_builder, None).await?;
Ok(())
}
Prepends the correct host on the path, then prepares a
reqwest::RequestBuilder
object for making a DELETE
request.
(You must then call goose_send
on this
object to actually execute the request.)
Example
use goose::prelude::*;
let mut task = task!(delete_function);
/// A simple task that makes a DELETE request, exposing the Reqwest
/// request builder.
async fn delete_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_delete("/path/to/foo").await?;
let _goose = user.goose_send(request_builder, None).await?;
Ok(())
}
pub async fn goose_send(
&self,
request_builder: RequestBuilder,
request_name: Option<&str>
) -> Result<GooseResponse, GooseTaskError>
pub async fn goose_send(
&self,
request_builder: RequestBuilder,
request_name: Option<&str>
) -> Result<GooseResponse, GooseTaskError>
Builds the provided
reqwest::RequestBuilder
object and then executes the response. If metrics are being displayed, it
also captures request metrics.
It is possible to build and execute a
reqwest::RequestBuilder
object directly with reqwest
without using this helper
function, but then Goose is unable to capture metrics.
Calls to goose_send()
returns a Result
containing a
GooseResponse
on success, and a
flume::SendError
<bool>
,
on failure. Failure only happens when --throttle-requests
is enabled and the load test
completes. The GooseResponse
object contains a copy of
the request you made (GooseRequestMetric
), and the
response (reqwest::Response
).
Example
use goose::prelude::*;
let mut task = task!(get_function);
/// A simple task that makes a GET request, exposing the Reqwest
/// request builder.
async fn get_function(user: &GooseUser) -> GooseTaskResult {
let request_builder = user.goose_get("/path/to/foo").await?;
let goose = user.goose_send(request_builder, None).await?;
// Do stuff with goose.request and/or goose.response here.
Ok(())
}
Manually mark a request as a success.
By default, Goose will consider any response with a 2xx status code as a success. It may be valid in your test for a non-2xx HTTP status code to be returned. A copy of your original request is returned with the response, and a mutable copy must be included when setting a request as a success.
Example
use goose::prelude::*;
let mut task = task!(get_function);
/// A simple task that makes a GET request.
async fn get_function(user: &GooseUser) -> GooseTaskResult {
let mut goose = user.get("/404").await?;
if let Ok(response) = &goose.response {
// We expect a 404 here.
if response.status() == 404 {
return user.set_success(&mut goose.request);
}
}
Err(GooseTaskError::RequestFailed {
raw_request: goose.request.clone(),
})
}
pub fn set_failure(
&self,
tag: &str,
request: &mut GooseRequestMetric,
headers: Option<&HeaderMap>,
body: Option<&str>
) -> GooseTaskResult
pub fn set_failure(
&self,
tag: &str,
request: &mut GooseRequestMetric,
headers: Option<&HeaderMap>,
body: Option<&str>
) -> GooseTaskResult
Manually mark a request as a failure.
By default, Goose will consider any response with a 2xx status code as a success. You may require more advanced logic, in which a 2xx status code is actually a failure. A copy of your original request is returned with the response, and a mutable copy must be included when setting a request as a failure.
Calls to set_failure
must include four parameters. The first, tag
, is an
arbitrary string identifying the reason for the failure, used when logging. The
second, request
, is a mutable reference to the
(GooseRequestMetric
) object of the request being
identified as a failure (the contained success
field will be set to false
,
and the update
field will be set to true
). The last two parameters, header
and body
, are optional and used to provide more detail in logs.
The value of tag
will normally be collected into the errors summary table if
metrics are being displayed. However, if set_failure
is called multiple times,
or is called on a request that was already an error, only the first error will
be collected.
This also calls GooseUser::log_debug
.
Example
use goose::prelude::*;
let mut task = task!(loadtest_index_page);
async fn loadtest_index_page(user: &GooseUser) -> GooseTaskResult {
let mut goose = user.get_named("/", "index").await?;
if let Ok(response) = goose.response {
// We only need to check pages that returned a success status code.
if response.status().is_success() {
match response.text().await {
Ok(text) => {
// If the expected string doesn't exist, this page load
// was a failure.
if !text.contains("this string must exist") {
// As this is a named request, pass in the name not the URL
return user.set_failure("string missing", &mut goose.request, None, None);
}
}
// Empty page, this is a failure.
Err(_) => {
return user.set_failure("empty page", &mut goose.request, None, None);
}
}
}
};
Ok(())
}
pub fn log_debug(
&self,
tag: &str,
request: Option<&GooseRequestMetric>,
headers: Option<&HeaderMap>,
body: Option<&str>
) -> GooseTaskResult
pub fn log_debug(
&self,
tag: &str,
request: Option<&GooseRequestMetric>,
headers: Option<&HeaderMap>,
body: Option<&str>
) -> GooseTaskResult
Write to debug_file
if enabled.
This function provides a mechanism for optional debug logging when a load test is running. This can be especially helpful when writing a load test. Each entry must include a tag, which is an arbitrary string identifying the debug message. It may also optionally include references to the GooseRequestMetric made, the headers returned by the server, and the response body returned by the server,
As the response body can be large, the --no-debug-body
option (or
GooseDefault::NoDebugBody
default)
can be set to prevent the debug log from including the response body. When this option
is enabled, the body will always show up as null
in the debug log.
Calls to GooseUser::set_failure
automatically invoke log_debug
.
To enable the debug log, a load test must be run with the --debug-log-file=foo
option set, where foo
is either a relative or an absolute path of the log file
to create. Any existing file will be overwritten.
In the following example, we are logging debug messages whenever there are errors.
Example
use goose::prelude::*;
let mut task = task!(loadtest_index_page);
async fn loadtest_index_page(user: &GooseUser) -> GooseTaskResult {
let mut goose = user.get("/").await?;
match goose.response {
Ok(response) => {
// Grab a copy of the headers so we can include them when logging errors.
let headers = &response.headers().clone();
// We only need to check pages that returned a success status code.
if !response.status().is_success() {
match response.text().await {
Ok(html) => {
// Server returned an error code, log everything.
user.log_debug(
"error loading /",
Some(&goose.request),
Some(headers),
Some(&html),
);
},
Err(e) => {
// No body was returned, log everything else.
user.log_debug(
&format!("error loading /: {}", e),
Some(&goose.request),
Some(headers),
None,
);
}
}
}
},
// No response from server.
Err(e) => {
user.log_debug(
"no response from server when loading /",
Some(&goose.request),
None,
None,
);
}
}
Ok(())
}
Manually build a
reqwest::Client
.
By default, Goose configures two options when building a
reqwest::Client
. The first
configures Goose to report itself as the
user_agent
requesting web pages (ie goose/0.11.2
). The second option configures
reqwest
to
store cookies,
which is generally necessary if you aim to simulate logged in users.
Default configuration:
use reqwest::Client;
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
let builder = Client::builder()
.user_agent(APP_USER_AGENT)
.cookie_store(true)
.gzip(true);
Alternatively, you can use this function to manually build a
reqwest::Client
.
with custom configuration. Available options are found in the
reqwest::ClientBuilder
documentation.
When manually building a
reqwest::Client
,
there are a few things to be aware of:
- Manually building a client in
test_start
will only affect requests made during test setup; - Manually building a client in
test_stop
will only affect requests made during test teardown; - A manually built client is specific to a single Goose thread – if you are
generating a large load test with many users, each will need to manually build their
own client (typically you’d do this in a Task that is registered with
GooseTask::set_on_start()
in each Task Set requiring a custom client; - Manually building a client will completely replace the automatically built client with a brand new one, so any configuration, cookies or headers set in the previously built client will be gone;
- You must include all desired configuration, as you are completely replacing Goose
defaults. For example, if you want Goose clients to store cookies, you will have to
include
.cookie_store(true)
.
In the following example, the Goose client is configured with a different user agent, sets a default header on every request, stores cookies, and supports gzip compression.
Example
use goose::prelude::*;
task!(setup_custom_client).set_on_start();
async fn setup_custom_client(user: &GooseUser) -> GooseTaskResult {
use reqwest::{Client, header};
// Build a custom HeaderMap to include with all requests made by this client.
let mut headers = header::HeaderMap::new();
headers.insert("X-Custom-Header", header::HeaderValue::from_str("custom value").unwrap());
// Build a custom client.
let builder = Client::builder()
.default_headers(headers)
.user_agent("custom user agent")
.cookie_store(true)
.gzip(true);
// Assign the custom client to this GooseUser.
user.set_client_builder(builder).await?;
Ok(())
}
Alternative Compression Algorithms
Reqwest also supports
brotli
and
deflate
compression.
To enable either, you must enable the features in your load test’s Cargo.toml
, for example:
reqwest = { version = "^0.11.4", default-features = false, features = [
"brotli",
"cookies",
"deflate",
"gzip",
"json",
] }
Once enabled, you can add .brotli(true)
and/or .deflate(true)
to your custom
reqwest::Client::builder()
, following the documentation above.
Custom Cookies
Custom cookies can also be manually set when building a custom reqwest::Client
. This requires
loading the GooseUser::base_url
being load tested in order to properly build the cookie. Then
a custom reqwest::cookie::Jar
is created and the custom cookie is added with
reqwest::cookie::Jar::add_cookie_str
. Finally, the new cookie jar must be specified as the
reqwest::ClientBuilder::cookie_provider
for the custom client.
Example
use reqwest::{cookie::Jar, Client};
use std::sync::Arc;
use goose::prelude::*;
task!(custom_cookie_with_custom_client).set_on_start();
async fn custom_cookie_with_custom_client(user: &GooseUser) -> GooseTaskResult {
// Get the base_url that is being load tested, use when building the cookie.
let url = user.base_url.read().await;
// Prepare the contents of a custom cookie.
let cookie = "my-custom-cookie=custom-value";
// Pre-load one or more cookies into a custom cookie jar to use with this client.
let jar = Jar::default();
jar.add_cookie_str(
cookie,
&url,
);
// Build a custom client.
let builder = Client::builder()
.user_agent("example-loadtest")
.cookie_store(true)
.cookie_provider(Arc::new(jar))
.gzip(true);
// Assign the custom client to this GooseUser.
user.set_client_builder(builder).await?;
Ok(())
}
Some websites use multiple domains to serve traffic, redirecting depending on
the user’s roll. For this reason, Goose needs to respect a redirect of the
base_url
and subsequent paths should be built from the redirect domain.
For example, if the base_url
(ie --host
) is set to foo.example.com
and the
load test requests /login
, thereby loading http://foo.example.com/login
and
this request gets redirected by the server to http://foo-secure.example.com/
,
subsequent requests made by this user need to be against the new
foo-secure.example.com domain
. (Further, if the base_url
is again redirected,
such as when loading http://foo-secure.example.com/logout
, the user should
again follow for subsequent requests, perhaps in this case back to
foo.example.com
.)
Load tests can also request absolute URLs, and if these URLs are redirected
it does not affect the base_url
of the load test. For example, if
foo.example.com
is the base url, and the load test requests
http://bar.example.com
(a different domain) and this request gets redirected
to http://other.example.com
, subsequent relative requests would still be made
against foo.example.com
.
This functionality is used internally by Goose to follow redirects of the
base_url
when --sticky-follow
is specified at run time, or
set_default
(
GooseDefault::StickyFollow
, true)
is enabled. It is also
available to be manually invoked from a load test such as in the following
example.
Example
use goose::prelude::*;
fn main() -> Result<(), GooseError> {
let _goose_metrics = GooseAttack::initialize()?
.register_taskset(taskset!("LoadtestTasks")
.set_host("http://foo.example.com/")
.set_wait_time(0, 3)?
.register_task(task!(task_foo).set_weight(10)?)
.register_task(task!(task_bar))
)
// Set a default run time so this test runs to completion.
.set_default(GooseDefault::RunTime, 1)?
.execute()?;
Ok(())
}
async fn task_foo(user: &GooseUser) -> GooseTaskResult {
let _goose = user.get("/").await?;
Ok(())
}
async fn task_bar(user: &GooseUser) -> GooseTaskResult {
// Before this task runs, all requests are being made against
// http://foo.example.com, after this task runs all subsequent
// requests are made against http://bar.example.com/.
user.set_base_url("http://bar.example.com/");
let _goose = user.get("/").await?;
Ok(())
}
Trait Implementations
Auto Trait Implementations
impl !RefUnwindSafe for GooseUser
impl !UnwindSafe for GooseUser
Blanket Implementations
Mutably borrows from an owned value. Read more
Instruments this type with the provided Span
, returning an
Instrumented
wrapper. Read more
type Output = T
type Output = T
Should always be Self