Struct goose::goose::GooseUser[][src]

pub struct GooseUser {
    pub started: Instant,
    pub task_sets_index: usize,
    pub client: Client,
    pub base_url: Url,
    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 load_test_hash: u64,
    // some fields omitted
}
Expand description

An individual user state, repeatedly running all GooseTasks 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: Client

Client used to make requests, managing sessions and cookies.

base_url: Url

The base URL to prepend to all relative paths.

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.

load_test_hash: u64

Load test hash.

Implementations

Create a new user state.

Create a new single-use user.

Returns an optional reference to per-GooseUser session data.

Leaves the session data in-place, returning an optional reference to the original session data if existing and of the correct type. Returns None if no session data has been set or the session data set is not of type T.

Example
use goose::prelude::*;

struct Foo(String);

let mut task = task!(get_session_data_function);

/// A very simple task that makes a GET request.
async fn get_session_data_function(user: &mut GooseUser) -> GooseTaskResult {
    let foo = user.get_session_data::<Foo>().expect("Missing session data!");
    println!("Session data: {}", foo.0);

    Ok(())
}

Returns a reference to per-GooseUser session data, without doing any validation that the session data exists and is of the correct type.

Leaves the session data in-place, returning a reference to the original session data. Calling this method on a GooseUser object without session data or with a different type T will panic.

For a safe alternative see GooseUser::get_session_data.

Example
use goose::prelude::*;

struct Foo(String);

let mut task = task!(get_session_data_unchecked_function);

/// A very simple task that makes a GET request.
async fn get_session_data_unchecked_function(user: &mut GooseUser) -> GooseTaskResult {
    let foo = user.get_session_data_unchecked::<Foo>();
    println!("Session data: {}", foo.0);

    Ok(())
}

Returns an optional mutable reference to per-GooseUser session data.

Leaves the session data in-place, returning an optional mutable reference to the original session data if existing and of the correct type. Returns None if no session data has been set or the session data set is not of type T.

Example
use goose::prelude::*;

struct Foo(String);

let mut task = task!(get_session_data_mut_function);

/// A very simple task that makes a GET request.
async fn get_session_data_mut_function(user: &mut GooseUser) -> GooseTaskResult {
    let foo = user.get_session_data_mut::<Foo>().expect("Missing session data!");
    foo.0 = "Bar".to_owned();
    Ok(())
}

Returns a mutable reference to per-GooseUser session data, without doing any validation that the session data exists and is of the correct type.

Leaves the session data in-place, returning a mutable reference to the original session data. Calling this method on a GooseUser object without session data or with a different type T will panic.

For a safe alternative see GooseUser::get_session_data_mut.

Example
use goose::prelude::*;

struct Foo(String);

let mut task = task!(get_session_data_unchecked_mut_function);

/// A very simple task that makes a GET request.
async fn get_session_data_unchecked_mut_function(user: &mut GooseUser) -> GooseTaskResult {
    let foo = user.get_session_data_unchecked_mut::<Foo>();
    foo.0 = "Bar".to_owned();
    Ok(())
}

Sets session data for the current GooseUser.

If session data already exists for the current GooseUser, it will be replaced. Session data must be of a type implementing the GooseUserData trait.

Example
use goose::prelude::*;

struct Foo(String);

let mut task = task!(set_session_data_function);

/// A very simple task that makes a GET request.
async fn set_session_data_function(user: &mut GooseUser) -> GooseTaskResult {
    user.set_session_data(Foo("Foo".to_string()));

    Ok(())
}

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:

  1. --host (host specified on the command line when running load test)
  2. GooseTaskSet.host (default host defined for the current task set)
  3. 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: &mut GooseUser) -> GooseTaskResult {
    let _goose = user.get("/path/to/foo/").await?;

    Ok(())
}

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: &mut 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: &mut GooseUser) -> GooseTaskResult {
    let _goose = user.post("/path/to/foo/", "BODY BEING POSTED").await?;

    Ok(())
}

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: &mut 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: &mut GooseUser) -> GooseTaskResult {
    let _goose = user.head("/path/to/foo/").await?;

    Ok(())
}

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: &mut 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: &mut GooseUser) -> GooseTaskResult {
    let _goose = user.delete("/path/to/foo/").await?;

    Ok(())
}

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: &mut 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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_get("/path/to/foo")?;
    let _goose = user.goose_send(request_builder, None).await?;

    Ok(())
}

pub fn goose_post(&self, path: &str) -> Result<RequestBuilder, GooseTaskError>

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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_post("/path/to/foo")?;
    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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_head("/path/to/foo")?;
    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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_put("/path/to/foo")?;
    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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_patch("/path/to/foo")?;
    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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_delete("/path/to/foo")?;
    let _goose = user.goose_send(request_builder, None).await?;

    Ok(())
}

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: &mut GooseUser) -> GooseTaskResult {
    let request_builder = user.goose_get("/path/to/foo")?;
    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: &mut 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(),
    })
}

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: &mut 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(())
}

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: &mut 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: &mut 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: &mut GooseUser) -> GooseTaskResult {
    // 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,
        &user.base_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::*;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), GooseError> {
    let _goose_metrics = GooseAttack::initialize()?
        .register_taskset(taskset!("LoadtestTasks")
            .set_host("http://foo.example.com/")
            .set_wait_time(Duration::from_secs(0), Duration::from_secs(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()
        .await?;

    Ok(())
}

async fn task_foo(user: &mut GooseUser) -> GooseTaskResult {
    let _goose = user.get("/").await?;

    Ok(())
}

async fn task_bar(user: &mut 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(())
}

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more

Immutably borrows from an owned value. Read more

Mutably borrows from an owned value. Read more

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait. Read more

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait. Read more

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s. Read more

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s. Read more

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait. Read more

Performs the conversion.

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more

Instruments this type with the current Span, returning an Instrumented wrapper. Read more

Performs the conversion.

Should always be Self

The type returned in the event of a conversion error.

Performs the conversion.

The type returned in the event of a conversion error.

Performs the conversion.

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more