Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Closure Example

The examples/closure.rs example loads three different pages on a web site. Instead of defining a hard coded Transaction function for each, the paths are passed in via a vector and the TransactionFunction is dynamically created in a closure.

The dashboard's Requests table cut after its avg ms column, with three rows, GET /, GET /about and GET /our-team, at 770, 682 and 619 requests, all with 0 fails and average times from 0.4 to 0.6 ms

cargo run --release --features dashboard --example closure -- --host http://127.0.0.1:8080 -u 200 -r 50 -t 3m --no-reset-metrics --dashboard -q, against a local nginx server that answers /, /about and /our-team with 200, 100 seconds in: one request row for each path in the vector.

Each user pauses 5 to 15 seconds after each transaction, so 200 users make about 20 requests a second; each user loads the three paths in turn, so the counts fall in the vector’s order. The closures carry no names, so the dashboard’s Transactions table lists them by number alone, 1.1 to 1.3; the request names are the paths. With -q Goose does not print the dashboard’s address when it starts: open http://127.0.0.1:5118 while the test runs. If that tab is still open when the run ends, Goose prints the address and waits for Ctrl-C.

Details

The paths to be loaded are first defined in a vector:

#![allow(unused)]
fn main() {
    let paths = vec!["/", "/about", "/our-team"];
}

A transaction function for each path is then dynamically created as a closure:

    for request_path in paths {
        let path = request_path;

        let closure: TransactionFunction = Arc::new(move |user| {
            Box::pin(async move {
                let _ = user.get(path).await?.discard_body().await;

                Ok(())
            })
        });

Complete Source Code

//! Simple Goose load test example using closures.
//!
//! ## License
//!
//! Copyright 2020 Fabian Franz
//!
//! Licensed under the Apache License, Version 2.0 (the "License");
//! you may not use this file except in compliance with the License.
//! You may obtain a copy of the License at
//!
//! <http://www.apache.org/licenses/LICENSE-2.0>
//!
//! Unless required by applicable law or agreed to in writing, software
//! distributed under the License is distributed on an "AS IS" BASIS,
//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//! See the License for the specific language governing permissions and
//! limitations under the License.

use goose::prelude::*;
use std::boxed::Box;
use std::sync::Arc;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), GooseError> {
    let mut scenario = scenario!("WebsiteUser")
        // After each transaction runs, sleep randomly from 5 to 15 seconds.
        .set_wait_time(Duration::from_secs(5), Duration::from_secs(15))?;

    let paths = vec!["/", "/about", "/our-team"];
    for request_path in paths {
        let path = request_path;

        let closure: TransactionFunction = Arc::new(move |user| {
            Box::pin(async move {
                let _ = user.get(path).await?.discard_body().await;

                Ok(())
            })
        });

        let transaction = Transaction::new(closure);
        // We need to do the variable dance as scenario.register_transaction returns self and hence moves
        // self out of `scenario`. By storing it in a new local variable and then moving it over
        // we can avoid that error.
        let new_scenario = scenario.register_transaction(transaction);
        scenario = new_scenario;
    }

    GooseAttack::initialize()?
        // In this example, we only create a single scenario, named "WebsiteUser".
        .register_scenario(scenario)
        .execute()
        .await?;

    Ok(())
}