From 792c3fb1716e49ea2a362128175e9c8094f2cf27 Mon Sep 17 00:00:00 2001 From: David Laban Date: Tue, 12 May 2020 22:58:06 +0100 Subject: [PATCH 01/18] have a go at making goose async It's a bit of a shitshow because of lifetimes, and the fact that async functions can't be used as function pointers (because the return value is not sized predictably in a dynamic context). This thread was really hepful to me: https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343/4 All that's left to do is: * Fix the doctests * Actually try out the examples and see if they are still working/performant. * Go hunting for places where explicit threads are used which could be turned into tasks. --- Cargo.toml | 2 + examples/drupal_loadtest.rs | 41 +++++++----- examples/simple.rs | 14 +++- src/goose.rs | 130 ++++++++++++++++++++---------------- src/lib.rs | 35 +++++++++- 5 files changed, 145 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 461eafad..67e117f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ ctrlc = "3.1" http = "0.2" lazy_static = "1.4" log = "0.4" +macro_rules_attribute = "*" num_cpus = "1.0" num-format = "0.4" rand = "0.7" @@ -26,6 +27,7 @@ serde = { version = "1.0", features = ["derive"] } serde_cbor = "0.11" simplelog = "0.7" structopt = "0.3" +tokio = "0.2.20" url = "2.1" # optional dependencies diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index 72046034..c20e2bc0 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,10 +22,14 @@ //! See the License for the specific language governing permissions and //! limitations under the License. +#[macro_use] +extern crate macro_rules_attribute; + use rand::Rng; use regex::Regex; use goose::GooseAttack; +use goose::dyn_async; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; fn main() { @@ -72,13 +76,14 @@ fn main() { } /// View the front page. -fn drupal_loadtest_front_page(client: &mut GooseClient) { - let response = client.get("/"); +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_front_page<'fut>(client: &'fut mut GooseClient) -> () { + let response = client.get("/").await; // Grab some static assets from the front page. match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(t) => { let re = Regex::new(r#"src="(.*?)""#).unwrap(); for url in re.captures_iter(&t) { @@ -101,23 +106,26 @@ fn drupal_loadtest_front_page(client: &mut GooseClient) { } /// View a node from 1 to 10,000, created by preptest.sh. -fn drupal_loadtest_node_page(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_node_page<'fut>(client: &'fut mut GooseClient) -> () { let nid = rand::thread_rng().gen_range(1, 10_000); - let _response = client.get(format!("/node/{}", &nid).as_str()); + let _response = client.get(format!("/node/{}", &nid).as_str()).await; } /// View a profile from 2 to 5,001, created by preptest.sh. -fn drupal_loadtest_profile_page(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_profile_page<'fut>(client: &'fut mut GooseClient) -> () { let uid = rand::thread_rng().gen_range(2, 5_001); - let _response = client.get(format!("/user/{}", &uid).as_str()); + let _response = client.get(format!("/user/{}", &uid).as_str()).await; } /// Log in. -fn drupal_loadtest_login(client: &mut GooseClient) { - let response = client.get("/user"); +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { + let response = client.get("/user").await; match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(html) => { let re = Regex::new( r#"name="form_build_id" value=['"](.*?)['"]"#).unwrap(); let form_build_id = match re.captures(&html) { @@ -155,12 +163,13 @@ fn drupal_loadtest_login(client: &mut GooseClient) { } /// Post a comment. -fn drupal_loadtest_post_comment(client: &mut GooseClient) { - let nid = rand::thread_rng().gen_range(1, 10_000); - let response = client.get(format!("/node/{}", &nid).as_str()); +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_post_comment<'fut>(client: &'fut mut GooseClient) -> () { + let nid: i32 = rand::thread_rng().gen_range(1, 10_000); + let response = client.get(format!("/node/{}", &nid).as_str()).await; match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(html) => { // Extract the form_build_id from the user login form. let re = Regex::new( r#"name="form_build_id" value=['"](.*?)['"]"#).unwrap(); @@ -205,10 +214,10 @@ fn drupal_loadtest_post_comment(client: &mut GooseClient) { ("op", "Save"), ]; let request_builder = client.goose_post(format!("/comment/reply/{}", &nid).as_str()); - let response = client.goose_send(request_builder.form(¶ms)); + let response = client.goose_send(request_builder.form(¶ms)).await; match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(html) => { if !html.contains(&comment_body) { eprintln!("no comment showed up after posting to comment/reply/{}", &nid); diff --git a/examples/simple.rs b/examples/simple.rs index 2672e7be..16fe6e9a 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,6 +17,11 @@ //! See the License for the specific language governing permissions and //! limitations under the License. + +#[macro_use] +extern crate macro_rules_attribute; + +use goose::dyn_async; use goose::GooseAttack; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; @@ -38,7 +43,8 @@ fn main() { /// Demonstrates how to log in when a client 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. -fn website_task_login(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn website_task_login<'fut>(client: &'fut mut GooseClient) -> () { let request_builder = client.goose_post("/login"); // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form let params = [("username", "test_user"), ("password", "")]; @@ -46,11 +52,13 @@ fn website_task_login(client: &mut GooseClient) { } /// A very simple task that simply loads the front page. -fn website_task_index(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn website_task_index<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/"); } /// A very simple task that simply loads the about page. -fn website_task_about(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn website_task_about<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/about/"); } diff --git a/src/goose.rs b/src/goose.rs index 13401535..fb3d4b45 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -72,7 +72,7 @@ //! let mut a_task = GooseTask::new(task_function); //! //! /// A very simple task that simply loads the front page. -//! fn task_function(client: &mut GooseClient) { +//! fn task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/"); //! } //! ``` @@ -88,7 +88,7 @@ //! let mut a_task = GooseTask::new(task_function).set_name("a"); //! //! /// A very simple task that simply loads the front page. -//! fn task_function(client: &mut GooseClient) { +//! fn task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/"); //! } //! ``` @@ -106,12 +106,12 @@ //! let mut b_task = GooseTask::new(b_task_function).set_weight(3); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function(client: &mut GooseClient) { +//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/a/"); //! } //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function(client: &mut GooseClient) { +//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/b/"); //! } //! ``` @@ -134,17 +134,17 @@ //! let mut c_task = GooseTask::new(c_task_function); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function(client: &mut GooseClient) { +//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/a/"); //! } //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function(client: &mut GooseClient) { +//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/b/"); //! } //! //! /// Another very simple task that simply loads the "c" page. -//! fn c_task_function(client: &mut GooseClient) { +//! fn c_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/c/"); //! } //! ``` @@ -163,7 +163,7 @@ //! let mut a_task = GooseTask::new(a_task_function).set_sequence(1).set_on_start(); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function(client: &mut GooseClient) { +//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/a/"); //! } //! ``` @@ -182,7 +182,7 @@ //! let mut b_task = GooseTask::new(b_task_function).set_sequence(2).set_on_stop(); //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function(client: &mut GooseClient) { +//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/b/"); //! } //! ``` @@ -210,7 +210,7 @@ //! let mut task = GooseTask::new(get_function); //! //! /// A very simple task that makes a GET request. -//! fn get_function(client: &mut GooseClient) { +//! fn get_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/path/to/foo/"); //! } //! ``` @@ -231,7 +231,7 @@ //! let mut task = GooseTask::new(post_function); //! //! /// A very simple task that makes a POST request. -//! fn post_function(client: &mut GooseClient) { +//! fn post_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.post("/path/to/foo/", "string value to post".to_string()); //! } //! ``` @@ -253,12 +253,12 @@ //! limitations under the License. use std::collections::{HashMap, BTreeMap}; -use std::time::Instant; use std::hash::{Hash, Hasher}; +use std::{pin::Pin, time::Instant, future::Future}; use http::StatusCode; use http::method::Method; -use reqwest::blocking::{Client, Response, RequestBuilder}; +use reqwest::{Client, Response, RequestBuilder}; use reqwest::Error; use serde::{Serialize, Deserialize}; use url::Url; @@ -329,7 +329,7 @@ impl GooseTaskSet { /// example_tasks.register_task(GooseTask::new(a_task_function)); /// /// /// A very simple task that simply loads the "a" page. - /// fn a_task_function(client: &mut GooseClient) { + /// fn a_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/a/"); /// } /// ``` @@ -683,7 +683,7 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A very simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.set_request_name("foo").get("/path/to/foo"); /// } /// ``` @@ -696,7 +696,7 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A very simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.set_request_name("foo").get("/path/to/foo"); /// let _response = client.get("/path/to/foo"); /// } @@ -790,13 +790,13 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A very simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/path/to/foo/"); /// } /// ``` - pub fn get(&mut self, path: &str) -> Result { + pub async fn get(&mut self, path: &str) -> Result { let request_builder = self.goose_get(path); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -815,13 +815,13 @@ impl GooseClient { /// let mut task = GooseTask::new(post_function); /// /// /// A very simple task that makes a POST request. - /// fn post_function(client: &mut GooseClient) { + /// fn post_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.post("/path/to/foo/", "BODY BEING POSTED".to_string()); /// } /// ``` - pub fn post(&mut self, path: &str, body: String) -> Result { + pub async fn post(&mut self, path: &str, body: String) -> Result { let request_builder = self.goose_post(path).body(body); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -840,13 +840,13 @@ impl GooseClient { /// let mut task = GooseTask::new(head_function); /// /// /// A very simple task that makes a HEAD request. - /// fn head_function(client: &mut GooseClient) { + /// fn head_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.head("/path/to/foo/"); /// } /// ``` - pub fn head(&mut self, path: &str) -> Result { + pub async fn head(&mut self, path: &str) -> Result { let request_builder = self.goose_head(path); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -865,13 +865,13 @@ impl GooseClient { /// let mut task = GooseTask::new(delete_function); /// /// /// A very simple task that makes a DELETE request. - /// fn delete_function(client: &mut GooseClient) { + /// fn delete_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.delete("/path/to/foo/"); /// } /// ``` - pub fn delete(&mut self, path: &str) -> Result { + pub async fn delete(&mut self, path: &str) -> Result { let request_builder = self.goose_delete(path); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -889,7 +889,7 @@ impl GooseClient { /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_get("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -913,7 +913,7 @@ impl GooseClient { /// /// /// A simple task that makes a POST request, exposing the Reqwest /// /// request builder. - /// fn post_function(client: &mut GooseClient) { + /// fn post_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_post("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -937,7 +937,7 @@ impl GooseClient { /// /// /// A simple task that makes a HEAD request, exposing the Reqwest /// /// request builder. - /// fn head_function(client: &mut GooseClient) { + /// fn head_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_head("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -961,7 +961,7 @@ impl GooseClient { /// /// /// A simple task that makes a PUT request, exposing the Reqwest /// /// request builder. - /// fn put_function(client: &mut GooseClient) { + /// fn put_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_put("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -985,7 +985,7 @@ impl GooseClient { /// /// /// A simple task that makes a PUT request, exposing the Reqwest /// /// request builder. - /// fn patch_function(client: &mut GooseClient) { + /// fn patch_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_patch("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -1009,7 +1009,7 @@ impl GooseClient { /// /// /// A simple task that makes a DELETE request, exposing the Reqwest /// /// request builder. - /// fn delete_function(client: &mut GooseClient) { + /// fn delete_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_delete("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -1036,12 +1036,12 @@ impl GooseClient { /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_get("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } /// ``` - pub fn goose_send(&mut self, request_builder: RequestBuilder) -> Result { + pub async fn goose_send(&mut self, request_builder: RequestBuilder) -> Result { let started = Instant::now(); let request = match request_builder.build() { Ok(r) => r, @@ -1063,7 +1063,7 @@ impl GooseClient { self.previous_request_name = self.request_name.clone(); // Make the actual request. - let response = self.client.execute(request); + let response = self.client.execute(request).await; let elapsed = started.elapsed(); if !self.config.no_stats { @@ -1147,7 +1147,7 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let response = client.get("/404"); /// match &response { /// Ok(r) => { @@ -1183,7 +1183,7 @@ impl GooseClient { /// /// let mut task = GooseTask::new(loadtest_index_page); /// - /// fn loadtest_index_page(client: &mut GooseClient) { + /// fn loadtest_index_page<'fut>(client: &'fut mut GooseClient) { /// let response = client.set_request_name("index").get("/"); /// // Extract the response Result. /// match response { @@ -1221,6 +1221,20 @@ impl GooseClient { } } + +// TODO: https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343/4 +// * make a macro to mark dyn async functions +// * mark all callbacks with this macro when defining them +// * pass them into GooseTask::new() as function pointers +type AsyncTaskCallbackFunction = + fn (&'_ mut GooseClient) -> + Pin // future API / pollable + + Send // required by non-single-threaded executors + + '_ // may capture `client`, which is only valid for the `'_` lifetime + >> +; + /// An individual task within a `GooseTaskSet`. #[derive(Clone)] pub struct GooseTask { @@ -1237,10 +1251,10 @@ pub struct GooseTask { /// A flag indicating that this task runs when the client stops. pub on_stop: bool, /// A required function that is executed each time this task runs. - pub function: fn(&mut GooseClient), + pub function: AsyncTaskCallbackFunction, } impl GooseTask { - pub fn new(function: fn(&mut GooseClient)) -> Self { + pub fn new(function: AsyncTaskCallbackFunction) -> Self { trace!("new task"); let task = GooseTask { tasks_index: usize::max_value(), @@ -1266,7 +1280,7 @@ impl GooseTask { /// /// GooseTask::new(my_task_function).set_name("foo"); /// - /// fn my_task_function(client: &mut GooseClient) { + /// fn my_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1292,7 +1306,7 @@ impl GooseTask { /// /// GooseTask::new(my_on_start_function).set_on_start(); /// - /// fn my_on_start_function(client: &mut GooseClient) { + /// fn my_on_start_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1318,7 +1332,7 @@ impl GooseTask { /// /// GooseTask::new(my_on_stop_function).set_on_stop(); /// - /// fn my_on_stop_function(client: &mut GooseClient) { + /// fn my_on_stop_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1338,7 +1352,7 @@ impl GooseTask { /// /// GooseTask::new(task_function).set_weight(3); /// - /// fn task_function(client: &mut GooseClient) { + /// fn task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1371,15 +1385,15 @@ impl GooseTask { /// let runs_second = GooseTask::new(second_task_function).set_sequence(5835); /// let runs_last = GooseTask::new(third_task_function); /// - /// fn first_task_function(client: &mut GooseClient) { + /// fn first_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/1"); /// } /// - /// fn second_task_function(client: &mut GooseClient) { + /// fn second_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/2"); /// } /// - /// fn third_task_function(client: &mut GooseClient) { + /// fn third_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/3"); /// } /// ``` @@ -1395,15 +1409,15 @@ impl GooseTask { /// let runs_second = GooseTask::new(second_task_function_a).set_sequence(2); /// let also_runs_second = GooseTask::new(second_task_function_b).set_sequence(2).set_weight(2); /// - /// fn first_task_function(client: &mut GooseClient) { + /// fn first_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/1"); /// } /// - /// fn second_task_function_a(client: &mut GooseClient) { + /// fn second_task_function_a<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/2a"); /// } /// - /// fn second_task_function_b(client: &mut GooseClient) { + /// fn second_task_function_b<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/2b"); /// } /// ``` @@ -1430,16 +1444,19 @@ impl Hash for GooseTask { #[cfg(test)] mod tests { use super::*; + use crate::dyn_async; #[test] fn goose_task_set() { // Simplistic test task functions. - fn test_task_function_a(client: &mut GooseClient) { - let _response = client.get("/a/"); + #[macro_rules_attribute(dyn_async!)] + async fn test_task_function_a<'fut>(client: &'fut mut GooseClient) -> () { + let _response = client.get("/a/").await; } - fn test_task_function_b(client: &mut GooseClient) { - let _response = client.get("/b/"); + #[macro_rules_attribute(dyn_async!)] + async fn test_task_function_b<'fut>(client: &'fut mut GooseClient) -> () { + let _response = client.get("/b/").await; } let mut task_set = GooseTaskSet::new("foo"); @@ -1531,7 +1548,8 @@ mod tests { #[test] fn goose_task() { // Simplistic test task functions. - fn test_task_function_a(client: &mut GooseClient) { + #[macro_rules_attribute(dyn_async!)] + async fn test_task_function_a <'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/a/"); } diff --git a/src/lib.rs b/src/lib.rs index 93abd27f..f67512e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,6 +272,9 @@ #[macro_use] extern crate log; +#[cfg(test)] +#[macro_use] +extern crate macro_rules_attribute; //#[macro_use] //extern crate goose_codegen; @@ -331,6 +334,29 @@ pub fn get_worker_id() -> usize { /// Socket used for coordinating a Gaggle, a distributed load test. pub struct Socket {} +// FIXME: For some reason this borks if you don't specify -> () +#[macro_export] +macro_rules! dyn_async {( + $( #[$attr:meta] )* // includes doc strings + $pub:vis + async + fn $fname:ident<$lt:lifetime> ( $($args:tt)* ) $(-> $Ret:ty)? + { + $($body:tt)* + } +) => ( + $( #[$attr] )* + #[allow(unused_parens)] + $pub + fn $fname<$lt> ( $($args)* ) -> ::std::pin::Pin<::std::boxed::Box< + dyn ::std::future::Future + + ::std::marker::Send + $lt + >> + { + ::std::boxed::Box::pin(async move { $($body)* }) + } +)} + /// Internal global state for load test. #[derive(Clone)] pub struct GooseAttack { @@ -653,7 +679,12 @@ impl GooseAttack { /// let _response = client.get("/bar"); /// } /// ``` - pub fn execute(mut self) { + pub fn execute(self) { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(self.execute_async()) + } + + async fn execute_async(mut self) { // At least one task set is required. if self.task_sets.len() <= 0 { error!("No task sets defined."); @@ -1488,4 +1519,4 @@ mod test { assert_eq!(is_valid_host("foo://example.com"), true); assert_eq!(is_valid_host("file:///path/to/file"), true); } -} \ No newline at end of file +} From b09c68164d3c4181098141aa00a534f5a9110377 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 13 May 2020 06:53:30 +0100 Subject: [PATCH 02/18] make it actually do something --- Cargo.toml | 3 +-- examples/simple.rs | 6 +++--- src/client.rs | 10 +++++----- src/lib.rs | 8 ++++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67e117f9..8acc370a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,7 @@ reqwest = { version = "0.10", features = ["blocking", "cookies", "json"] } serde = { version = "1.0", features = ["derive"] } serde_cbor = "0.11" simplelog = "0.7" -structopt = "0.3" -tokio = "0.2.20" +tokio = { version = "0.2.20", features = ["rt-core", "time"] } url = "2.1" # optional dependencies diff --git a/examples/simple.rs b/examples/simple.rs index 16fe6e9a..ba8ffbdd 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -48,17 +48,17 @@ async fn website_task_login<'fut>(client: &'fut mut GooseClient) -> () { let request_builder = client.goose_post("/login"); // 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(¶ms)); + let _response = client.goose_send(request_builder.form(¶ms)).await; } /// A very simple task that simply loads the front page. #[macro_rules_attribute(dyn_async!)] async fn website_task_index<'fut>(client: &'fut mut GooseClient) -> () { - let _response = client.get("/"); + let _response = client.get("/").await; } /// A very simple task that simply loads the about page. #[macro_rules_attribute(dyn_async!)] async fn website_task_about<'fut>(client: &'fut mut GooseClient) -> () { - let _response = client.get("/about/"); + let _response = client.get("/about/").await; } diff --git a/src/client.rs b/src/client.rs index 70bb07b0..35a730ca 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,12 +4,12 @@ use std::sync::mpsc; use rand::thread_rng; use rand::seq::SliceRandom; use rand::Rng; -use std::{thread, time}; +use std::time; use crate::get_worker_id; use crate::goose::{GooseTaskSet, GooseClient, GooseClientMode, GooseClientCommand}; -pub fn client_main( +pub async fn client_main( thread_number: usize, thread_task_set: GooseTaskSet, mut thread_client: GooseClient, @@ -73,7 +73,7 @@ pub fn client_main( thread_client.task_request_name = Some(thread_task_name.to_string()); } // Invoke the task function. - function(&mut thread_client); + function(&mut thread_client).await; // Prepare to sleep for a random value from min_wait to max_wait. let wait_time: usize; @@ -116,7 +116,7 @@ pub fn client_main( if thread_continue && thread_client.max_wait > 0 { let sleep_duration = time::Duration::from_secs(1); debug!("client {} from {} sleeping {:?} second...", thread_number, thread_task_set.name, sleep_duration); - thread::sleep(sleep_duration); + tokio::time::delay_for(sleep_duration).await; slept += 1; if slept > wait_time { in_sleep_loop = false; @@ -159,4 +159,4 @@ pub fn client_main( else { info!("exiting client {} from {}...", thread_number, thread_task_set.name); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index f67512e5..7ab9e0d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -933,9 +933,9 @@ impl GooseAttack { let is_worker = self.configuration.worker; // Launch a new client. - let client = thread::spawn(move || { - client::client_main(thread_number, thread_task_set, thread_client, thread_receiver, thread_sender, is_worker) - }); + let client = tokio::spawn( + client::client_main(thread_number, thread_task_set, thread_client, thread_receiver, thread_sender) + ); clients.push(client); self.active_clients += 1; @@ -1080,7 +1080,7 @@ impl GooseAttack { info!("waiting for clients to exit"); } for client in clients { - let _ = client.join(); + let _ = client.await; } debug!("all clients exited"); From 1a82300f3fe4d32a657a800b98a247f56eaaff66 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 13 May 2020 07:02:55 +0100 Subject: [PATCH 03/18] another missing .await --- examples/drupal_loadtest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index c20e2bc0..f15e5eef 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -148,7 +148,7 @@ async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { ("op", "Log+in"), ]; let request_builder = client.goose_post("/user"); - let _response = client.goose_send(request_builder.form(¶ms)); + let _response = client.goose_send(request_builder.form(¶ms)).await; // @TODO: verify that we actually logged in. } Err(e) => { From 03e3be8339a0b554d19acf33c3c841376209de66 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sat, 16 May 2020 07:20:19 +0200 Subject: [PATCH 04/18] add mising await calls --- src/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 35a730ca..ef22dde6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -42,7 +42,7 @@ pub async fn client_main( thread_client.task_request_name = Some(thread_task_name.to_string()); } // Invoke the task function. - function(&mut thread_client); + function(&mut thread_client).await; } } } @@ -146,7 +146,7 @@ pub async fn client_main( thread_client.task_request_name = Some(thread_task_name.to_string()); } // Invoke the task function. - function(&mut thread_client); + function(&mut thread_client).await; } } } From e54e1f451d46ad69d83002fcad93f8103fd76d31 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sat, 16 May 2020 08:22:59 +0200 Subject: [PATCH 05/18] move async to launch_clients() --- Cargo.toml | 1 + src/lib.rs | 14 +++++--------- src/worker.rs | 3 ++- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8acc370a..638037e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ reqwest = { version = "0.10", features = ["blocking", "cookies", "json"] } serde = { version = "1.0", features = ["derive"] } serde_cbor = "0.11" simplelog = "0.7" +structopt = "0.3" tokio = { version = "0.2.20", features = ["rt-core", "time"] } url = "2.1" diff --git a/src/lib.rs b/src/lib.rs index 7ab9e0d9..c5179129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -679,12 +679,7 @@ impl GooseAttack { /// let _response = client.get("/bar"); /// } /// ``` - pub fn execute(self) { - let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(self.execute_async()) - } - - async fn execute_async(mut self) { + pub fn execute(mut self) { // At least one task set is required. if self.task_sets.len() <= 0 { error!("No task sets defined."); @@ -879,7 +874,8 @@ impl GooseAttack { } // Start goose in single-process mode. else { - self = self.launch_clients(started, sleep_duration, None); + let mut rt = tokio::runtime::Runtime::new().unwrap(); + self = rt.block_on(self.launch_clients(started, sleep_duration, None)); } if !self.configuration.no_stats && !self.configuration.worker { @@ -887,8 +883,8 @@ impl GooseAttack { } } - /// Called internally in single-process mode and distributed load test gaggle-mode. - pub fn launch_clients(mut self, mut started: time::Instant, sleep_duration: time::Duration, socket: Option) -> GooseAttack { + /// Called internally in local-mode and gaggle-mode. + async fn launch_clients(mut self, mut started: time::Instant, sleep_duration: time::Duration, socket: Option) -> GooseAttack { trace!("launch clients: started({:?}) sleep_duration({:?}) socket({:?})", started, sleep_duration, socket); // Collect client threads in a vector for when we want to stop them later. let mut clients = vec![]; diff --git a/src/worker.rs b/src/worker.rs index db69c9af..430c24a9 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -191,7 +191,8 @@ pub fn worker_main(goose_attack: &GooseAttack) { } worker_goose_attack.weighted_clients = weighted_clients; worker_goose_attack.configuration.worker = true; - worker_goose_attack.launch_clients(started, sleep_duration, Some(manager)); + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(worker_goose_attack.launch_clients(started, sleep_duration, Some(manager))); } pub fn push_stats_to_manager(manager: &Socket, requests: &HashMap, get_response: bool) -> bool { From b55b10afe37279c970ff8fdab98cb03d1b103fe6 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 17 May 2020 08:50:18 +0200 Subject: [PATCH 06/18] when async lands goose will be 0.7.x --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 638037e2..aa82416d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "goose" -version = "0.6.3-dev" +version = "0.7.0-dev" authors = ["Jeremy Andrews "] edition = "2018" description = "A load testing tool inspired by Locust." From 2744399ab3406621a639a79dc8936af2ee8e8827 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Mon, 18 May 2020 07:52:56 +0200 Subject: [PATCH 07/18] trying to simplify --- Cargo.toml | 1 - examples/drupal_loadtest.rs | 8 -------- examples/simple.rs | 7 ------- src/client.rs | 6 +++--- src/goose.rs | 26 +++++++------------------- src/lib.rs | 23 ----------------------- 6 files changed, 10 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa82416d..ce785ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ ctrlc = "3.1" http = "0.2" lazy_static = "1.4" log = "0.4" -macro_rules_attribute = "*" num_cpus = "1.0" num-format = "0.4" rand = "0.7" diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index f15e5eef..98bf1d4b 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,9 +22,6 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -#[macro_use] -extern crate macro_rules_attribute; - use rand::Rng; use regex::Regex; @@ -76,7 +73,6 @@ fn main() { } /// View the front page. -#[macro_rules_attribute(dyn_async!)] async fn drupal_loadtest_front_page<'fut>(client: &'fut mut GooseClient) -> () { let response = client.get("/").await; @@ -106,21 +102,18 @@ async fn drupal_loadtest_front_page<'fut>(client: &'fut mut GooseClient) -> () { } /// View a node from 1 to 10,000, created by preptest.sh. -#[macro_rules_attribute(dyn_async!)] async fn drupal_loadtest_node_page<'fut>(client: &'fut mut GooseClient) -> () { let nid = rand::thread_rng().gen_range(1, 10_000); let _response = client.get(format!("/node/{}", &nid).as_str()).await; } /// View a profile from 2 to 5,001, created by preptest.sh. -#[macro_rules_attribute(dyn_async!)] async fn drupal_loadtest_profile_page<'fut>(client: &'fut mut GooseClient) -> () { let uid = rand::thread_rng().gen_range(2, 5_001); let _response = client.get(format!("/user/{}", &uid).as_str()).await; } /// Log in. -#[macro_rules_attribute(dyn_async!)] async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { let response = client.get("/user").await; match response { @@ -163,7 +156,6 @@ async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { } /// Post a comment. -#[macro_rules_attribute(dyn_async!)] async fn drupal_loadtest_post_comment<'fut>(client: &'fut mut GooseClient) -> () { let nid: i32 = rand::thread_rng().gen_range(1, 10_000); let response = client.get(format!("/node/{}", &nid).as_str()).await; diff --git a/examples/simple.rs b/examples/simple.rs index ba8ffbdd..cc5547e4 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -18,10 +18,6 @@ //! limitations under the License. -#[macro_use] -extern crate macro_rules_attribute; - -use goose::dyn_async; use goose::GooseAttack; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; @@ -43,7 +39,6 @@ fn main() { /// Demonstrates how to log in when a client 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. -#[macro_rules_attribute(dyn_async!)] async fn website_task_login<'fut>(client: &'fut mut GooseClient) -> () { let request_builder = client.goose_post("/login"); // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form @@ -52,13 +47,11 @@ async fn website_task_login<'fut>(client: &'fut mut GooseClient) -> () { } /// A very simple task that simply loads the front page. -#[macro_rules_attribute(dyn_async!)] async fn website_task_index<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/").await; } /// A very simple task that simply loads the about page. -#[macro_rules_attribute(dyn_async!)] async fn website_task_about<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/about/").await; } diff --git a/src/client.rs b/src/client.rs index ef22dde6..4fcb54c2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -36,7 +36,7 @@ pub async fn client_main( for task_index in &sequence { // Determine which task we're going to run next. let thread_task_name = &thread_task_set.tasks[*task_index].name; - let function = thread_task_set.tasks[*task_index].function; + let function = &thread_task_set.tasks[*task_index].function; debug!("launching on_start {} task from {}", thread_task_name, thread_task_set.name); if thread_task_name != "" { thread_client.task_request_name = Some(thread_task_name.to_string()); @@ -66,7 +66,7 @@ pub async fn client_main( // Determine which task we're going to run next. let thread_weighted_task = thread_client.weighted_tasks[thread_client.weighted_bucket][thread_client.weighted_bucket_position]; let thread_task_name = &thread_task_set.tasks[thread_weighted_task].name; - let function = thread_task_set.tasks[thread_weighted_task].function; + let function = &thread_task_set.tasks[thread_weighted_task].function; debug!("launching {} task from {}", thread_task_name, thread_task_set.name); // If task name is set, it will be used for storing request statistics instead of the raw url. if thread_task_name != "" { @@ -140,7 +140,7 @@ pub async fn client_main( for task_index in &sequence { // Determine which task we're going to run next. let thread_task_name = &thread_task_set.tasks[*task_index].name; - let function = thread_task_set.tasks[*task_index].function; + let function = &thread_task_set.tasks[*task_index].function; debug!("launching on_stop {} task from {}", thread_task_name, thread_task_set.name); if thread_task_name != "" { thread_client.task_request_name = Some(thread_task_name.to_string()); diff --git a/src/goose.rs b/src/goose.rs index fb3d4b45..2986671c 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -254,8 +254,7 @@ use std::collections::{HashMap, BTreeMap}; use std::hash::{Hash, Hasher}; -use std::{pin::Pin, time::Instant, future::Future}; - +use std::{time::Instant, future::Future, pin::Pin}; use http::StatusCode; use http::method::Method; use reqwest::{Client, Response, RequestBuilder}; @@ -267,6 +266,8 @@ use crate::GooseConfiguration; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); +type GooseTaskFn = Box Pin>>>; + /// An individual task set. #[derive(Clone, Hash)] pub struct GooseTaskSet { @@ -333,6 +334,7 @@ impl GooseTaskSet { /// let _response = client.get("/a/"); /// } /// ``` + //pub fn register_task(mut self, mut task: GooseTask>) -> Self { pub fn register_task(mut self, mut task: GooseTask) -> Self { trace!("{} register_task: {}", self.name, task.name); task.tasks_index = self.tasks.len(); @@ -1221,20 +1223,6 @@ impl GooseClient { } } - -// TODO: https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343/4 -// * make a macro to mark dyn async functions -// * mark all callbacks with this macro when defining them -// * pass them into GooseTask::new() as function pointers -type AsyncTaskCallbackFunction = - fn (&'_ mut GooseClient) -> - Pin // future API / pollable - + Send // required by non-single-threaded executors - + '_ // may capture `client`, which is only valid for the `'_` lifetime - >> -; - /// An individual task within a `GooseTaskSet`. #[derive(Clone)] pub struct GooseTask { @@ -1251,10 +1239,10 @@ pub struct GooseTask { /// A flag indicating that this task runs when the client stops. pub on_stop: bool, /// A required function that is executed each time this task runs. - pub function: AsyncTaskCallbackFunction, + pub function: GooseTaskFn<>, } impl GooseTask { - pub fn new(function: AsyncTaskCallbackFunction) -> Self { + pub fn new(function: fn(GooseClient)) -> Self { trace!("new task"); let task = GooseTask { tasks_index: usize::max_value(), @@ -1263,7 +1251,7 @@ impl GooseTask { sequence: 0, on_start: false, on_stop: false, - function: function, + function: Box::new(function), }; task } diff --git a/src/lib.rs b/src/lib.rs index c5179129..46a577e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -334,29 +334,6 @@ pub fn get_worker_id() -> usize { /// Socket used for coordinating a Gaggle, a distributed load test. pub struct Socket {} -// FIXME: For some reason this borks if you don't specify -> () -#[macro_export] -macro_rules! dyn_async {( - $( #[$attr:meta] )* // includes doc strings - $pub:vis - async - fn $fname:ident<$lt:lifetime> ( $($args:tt)* ) $(-> $Ret:ty)? - { - $($body:tt)* - } -) => ( - $( #[$attr] )* - #[allow(unused_parens)] - $pub - fn $fname<$lt> ( $($args)* ) -> ::std::pin::Pin<::std::boxed::Box< - dyn ::std::future::Future - + ::std::marker::Send + $lt - >> - { - ::std::boxed::Box::pin(async move { $($body)* }) - } -)} - /// Internal global state for load test. #[derive(Clone)] pub struct GooseAttack { From d3b58b7c7ccd35f2b7497fe5da1141c70c4b67a2 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sat, 23 May 2020 07:28:54 +0200 Subject: [PATCH 08/18] box and pin tasks manually --- examples/simple.rs | 27 ++++++++++++++++++--------- src/goose.rs | 9 +++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index cc5547e4..eb2fa341 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,6 +17,9 @@ //! See the License for the specific language governing permissions and //! limitations under the License. +use std::boxed::Box; +use std::pin::Pin; +use std::future::Future; use goose::GooseAttack; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; @@ -39,19 +42,25 @@ fn main() { /// Demonstrates how to log in when a client 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_task_login<'fut>(client: &'fut mut GooseClient) -> () { - let request_builder = client.goose_post("/login"); - // 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(¶ms)).await; +async fn website_task_login<'r>(client: &'r mut GooseClient) -> Pin + 'r>> { + Box::pin(async move { + let request_builder = client.goose_post("/login"); + // 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(¶ms)).await; + }) } /// A very simple task that simply loads the front page. -async fn website_task_index<'fut>(client: &'fut mut GooseClient) -> () { - let _response = client.get("/").await; +async fn website_task_index<'r>(client: &'r mut GooseClient) -> Pin + 'r>> { + Box::pin(async move { + let _response = client.get("/").await; + }) } /// A very simple task that simply loads the about page. -async fn website_task_about<'fut>(client: &'fut mut GooseClient) -> () { - let _response = client.get("/about/").await; +async fn website_task_about<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(async move { + let _response = client.get("/about/").await; + }) } diff --git a/src/goose.rs b/src/goose.rs index 2986671c..2909c807 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -266,8 +266,6 @@ use crate::GooseConfiguration; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); -type GooseTaskFn = Box Pin>>>; - /// An individual task set. #[derive(Clone, Hash)] pub struct GooseTaskSet { @@ -334,7 +332,6 @@ impl GooseTaskSet { /// let _response = client.get("/a/"); /// } /// ``` - //pub fn register_task(mut self, mut task: GooseTask>) -> Self { pub fn register_task(mut self, mut task: GooseTask) -> Self { trace!("{} register_task: {}", self.name, task.name); task.tasks_index = self.tasks.len(); @@ -1239,10 +1236,10 @@ pub struct GooseTask { /// A flag indicating that this task runs when the client stops. pub on_stop: bool, /// A required function that is executed each time this task runs. - pub function: GooseTaskFn<>, + pub function: for<'r> fn(&'r mut GooseClient) -> Pin + Send + 'r>>, } impl GooseTask { - pub fn new(function: fn(GooseClient)) -> Self { + pub fn new(function: for<'r> fn(&'r mut GooseClient) -> Pin + Send + 'r>>) -> Self { trace!("new task"); let task = GooseTask { tasks_index: usize::max_value(), @@ -1251,7 +1248,7 @@ impl GooseTask { sequence: 0, on_start: false, on_stop: false, - function: Box::new(function), + function: function, }; task } From 5928252412f60c5c38e332a426c1f5dcb19469ed Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sat, 23 May 2020 08:11:51 +0200 Subject: [PATCH 09/18] get examples working --- examples/drupal_loadtest.rs | 46 ++++++++++++++++++++++++++----------- examples/simple.rs | 39 ++++++++++++++++--------------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index 98bf1d4b..c5d75c78 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,49 +22,52 @@ //! See the License for the specific language governing permissions and //! limitations under the License. +use std::boxed::Box; +use std::pin::Pin; +use std::future::Future; + use rand::Rng; use regex::Regex; use goose::GooseAttack; -use goose::dyn_async; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; fn main() { GooseAttack::initialize() .register_taskset(GooseTaskSet::new("AnonBrowsingUser") .set_weight(4) - .register_task(GooseTask::new(drupal_loadtest_front_page) + .register_task(GooseTask::new(drupal_loadtest_front_page_async) .set_weight(15) .set_name("(Anon) front page") ) - .register_task(GooseTask::new(drupal_loadtest_node_page) + .register_task(GooseTask::new(drupal_loadtest_node_page_async) .set_weight(10) .set_name("(Anon) node page") ) - .register_task(GooseTask::new(drupal_loadtest_profile_page) + .register_task(GooseTask::new(drupal_loadtest_profile_page_async) .set_weight(3) .set_name("(Anon) user page") ) ) .register_taskset(GooseTaskSet::new("AuthBrowsingUser") .set_weight(1) - .register_task(GooseTask::new(drupal_loadtest_login) + .register_task(GooseTask::new(drupal_loadtest_login_async) .set_on_start() .set_name("(Auth) login") ) - .register_task(GooseTask::new(drupal_loadtest_front_page) + .register_task(GooseTask::new(drupal_loadtest_front_page_async) .set_weight(15) .set_name("(Auth) front page") ) - .register_task(GooseTask::new(drupal_loadtest_node_page) + .register_task(GooseTask::new(drupal_loadtest_node_page_async) .set_weight(10) .set_name("(Auth) node page") ) - .register_task(GooseTask::new(drupal_loadtest_profile_page) + .register_task(GooseTask::new(drupal_loadtest_profile_page_async) .set_weight(3) .set_name("(Auth) user page") ) - .register_task(GooseTask::new(drupal_loadtest_post_comment) + .register_task(GooseTask::new(drupal_loadtest_post_comment_async) .set_weight(3) .set_name("(Auth) comment form") ) @@ -73,7 +76,7 @@ fn main() { } /// View the front page. -async fn drupal_loadtest_front_page<'fut>(client: &'fut mut GooseClient) -> () { +async fn drupal_loadtest_front_page<'r>(client: &'r mut GooseClient) { let response = client.get("/").await; // Grab some static assets from the front page. @@ -100,21 +103,30 @@ async fn drupal_loadtest_front_page<'fut>(client: &'fut mut GooseClient) -> () { }, } } +fn drupal_loadtest_front_page_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(drupal_loadtest_front_page(client)) +} /// View a node from 1 to 10,000, created by preptest.sh. -async fn drupal_loadtest_node_page<'fut>(client: &'fut mut GooseClient) -> () { +async fn drupal_loadtest_node_page<'r>(client: &'r mut GooseClient) { let nid = rand::thread_rng().gen_range(1, 10_000); let _response = client.get(format!("/node/{}", &nid).as_str()).await; } +fn drupal_loadtest_node_page_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(drupal_loadtest_node_page(client)) +} /// View a profile from 2 to 5,001, created by preptest.sh. -async fn drupal_loadtest_profile_page<'fut>(client: &'fut mut GooseClient) -> () { +async fn drupal_loadtest_profile_page<'r>(client: &'r mut GooseClient) { let uid = rand::thread_rng().gen_range(2, 5_001); let _response = client.get(format!("/user/{}", &uid).as_str()).await; } +fn drupal_loadtest_profile_page_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(drupal_loadtest_profile_page(client)) +} /// Log in. -async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { +async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { let response = client.get("/user").await; match response { Ok(r) => { @@ -154,9 +166,12 @@ async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { Err(_) => (), } } +fn drupal_loadtest_login_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(drupal_loadtest_login(client)) +} /// Post a comment. -async fn drupal_loadtest_post_comment<'fut>(client: &'fut mut GooseClient) -> () { +async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { let nid: i32 = rand::thread_rng().gen_range(1, 10_000); let response = client.get(format!("/node/{}", &nid).as_str()).await; match response { @@ -236,3 +251,6 @@ async fn drupal_loadtest_post_comment<'fut>(client: &'fut mut GooseClient) -> () Err(_) => (), } } +fn drupal_loadtest_post_comment_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(drupal_loadtest_post_comment(client)) +} diff --git a/examples/simple.rs b/examples/simple.rs index eb2fa341..90a3e19d 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -31,10 +31,10 @@ fn main() { // 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. - .register_task(GooseTask::new(website_task_login).set_on_start()) + .register_task(GooseTask::new(website_task_login_async).set_on_start()) // These next two tasks run repeatedly as long as the load test is running. - .register_task(GooseTask::new(website_task_index)) - .register_task(GooseTask::new(website_task_about)) + .register_task(GooseTask::new(website_task_index_async)) + .register_task(GooseTask::new(website_task_about_async)) ) .execute(); } @@ -42,25 +42,28 @@ fn main() { /// Demonstrates how to log in when a client 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_task_login<'r>(client: &'r mut GooseClient) -> Pin + 'r>> { - Box::pin(async move { - let request_builder = client.goose_post("/login"); - // 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(¶ms)).await; - }) +async fn website_task_login<'r>(client: &'r mut GooseClient) { + let request_builder = client.goose_post("/login"); + // 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(¶ms)).await; +} +fn website_task_login_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(website_task_login(client)) } /// A very simple task that simply loads the front page. -async fn website_task_index<'r>(client: &'r mut GooseClient) -> Pin + 'r>> { - Box::pin(async move { - let _response = client.get("/").await; - }) +async fn website_task_index<'r>(client: &'r mut GooseClient) { + let _response = client.get("/"); +} +fn website_task_index_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(website_task_index(client)) } /// A very simple task that simply loads the about page. -async fn website_task_about<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(async move { - let _response = client.get("/about/").await; - }) +async fn website_task_about<'r>(client: &'r mut GooseClient) { + let _response = client.get("/about/"); } +fn website_task_about_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin(website_task_about(client)) +} \ No newline at end of file From c277e07ab1033a6693a57a47e004883b3a2baba5 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sat, 23 May 2020 08:22:13 +0200 Subject: [PATCH 10/18] fix rebase failure --- CHANGELOG.md | 3 +++ src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 345576a8..4b2dd1cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.7.0-dev + - initial async support + ## 0.6.3-dev - nng does not support udp as a transport protocol, and tcp overhead isn't problematic; remove to-do to add udp, hard-code tcp diff --git a/src/lib.rs b/src/lib.rs index 46a577e5..55892256 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -907,7 +907,7 @@ impl GooseAttack { // Launch a new client. let client = tokio::spawn( - client::client_main(thread_number, thread_task_set, thread_client, thread_receiver, thread_sender) + client::client_main(thread_number, thread_task_set, thread_client, thread_receiver, thread_sender, is_worker) ); clients.push(client); From 9c785b74d96acae8e37ee3f32105b7ec4c65c1fc Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 24 May 2020 07:34:05 +0200 Subject: [PATCH 11/18] use macro to wrap async functions --- examples/drupal_loadtest.rs | 42 +++++++++++++------------------------ examples/simple.rs | 34 ++++++++++++------------------ src/goose.rs | 9 ++++++++ 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index c5d75c78..e7c794c6 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,52 +22,50 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -use std::boxed::Box; -use std::pin::Pin; -use std::future::Future; +use std::{boxed::Box, pin::Pin, future::Future}; use rand::Rng; use regex::Regex; -use goose::GooseAttack; +use goose::{GooseAttack, task}; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; fn main() { GooseAttack::initialize() .register_taskset(GooseTaskSet::new("AnonBrowsingUser") .set_weight(4) - .register_task(GooseTask::new(drupal_loadtest_front_page_async) + .register_task(GooseTask::new(drupal_loadtest_front_page_task) .set_weight(15) .set_name("(Anon) front page") ) - .register_task(GooseTask::new(drupal_loadtest_node_page_async) + .register_task(GooseTask::new(drupal_loadtest_node_page_task) .set_weight(10) .set_name("(Anon) node page") ) - .register_task(GooseTask::new(drupal_loadtest_profile_page_async) + .register_task(GooseTask::new(drupal_loadtest_profile_page_task) .set_weight(3) .set_name("(Anon) user page") ) ) .register_taskset(GooseTaskSet::new("AuthBrowsingUser") .set_weight(1) - .register_task(GooseTask::new(drupal_loadtest_login_async) + .register_task(GooseTask::new(drupal_loadtest_login_task) .set_on_start() .set_name("(Auth) login") ) - .register_task(GooseTask::new(drupal_loadtest_front_page_async) + .register_task(GooseTask::new(drupal_loadtest_front_page_task) .set_weight(15) .set_name("(Auth) front page") ) - .register_task(GooseTask::new(drupal_loadtest_node_page_async) + .register_task(GooseTask::new(drupal_loadtest_node_page_task) .set_weight(10) .set_name("(Auth) node page") ) - .register_task(GooseTask::new(drupal_loadtest_profile_page_async) + .register_task(GooseTask::new(drupal_loadtest_profile_page_task) .set_weight(3) .set_name("(Auth) user page") ) - .register_task(GooseTask::new(drupal_loadtest_post_comment_async) + .register_task(GooseTask::new(drupal_loadtest_post_comment_task) .set_weight(3) .set_name("(Auth) comment form") ) @@ -103,27 +101,21 @@ async fn drupal_loadtest_front_page<'r>(client: &'r mut GooseClient) { }, } } -fn drupal_loadtest_front_page_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(drupal_loadtest_front_page(client)) -} +task!(drupal_loadtest_front_page, drupal_loadtest_front_page_task); /// View a node from 1 to 10,000, created by preptest.sh. async fn drupal_loadtest_node_page<'r>(client: &'r mut GooseClient) { let nid = rand::thread_rng().gen_range(1, 10_000); let _response = client.get(format!("/node/{}", &nid).as_str()).await; } -fn drupal_loadtest_node_page_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(drupal_loadtest_node_page(client)) -} +task!(drupal_loadtest_node_page, drupal_loadtest_node_page_task); /// View a profile from 2 to 5,001, created by preptest.sh. async fn drupal_loadtest_profile_page<'r>(client: &'r mut GooseClient) { let uid = rand::thread_rng().gen_range(2, 5_001); let _response = client.get(format!("/user/{}", &uid).as_str()).await; } -fn drupal_loadtest_profile_page_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(drupal_loadtest_profile_page(client)) -} +task!(drupal_loadtest_profile_page, drupal_loadtest_profile_page_task); /// Log in. async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { @@ -166,9 +158,7 @@ async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { Err(_) => (), } } -fn drupal_loadtest_login_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(drupal_loadtest_login(client)) -} +task!(drupal_loadtest_login, drupal_loadtest_login_task); /// Post a comment. async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { @@ -251,6 +241,4 @@ async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { Err(_) => (), } } -fn drupal_loadtest_post_comment_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(drupal_loadtest_post_comment(client)) -} +task!(drupal_loadtest_post_comment, drupal_loadtest_post_comment_task); diff --git a/examples/simple.rs b/examples/simple.rs index 90a3e19d..a25a4985 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,11 +17,9 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -use std::boxed::Box; -use std::pin::Pin; -use std::future::Future; +use std::{boxed::Box, pin::Pin, future::Future}; -use goose::GooseAttack; +use goose::{GooseAttack, task}; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; fn main() { @@ -31,10 +29,10 @@ fn main() { // 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. - .register_task(GooseTask::new(website_task_login_async).set_on_start()) + .register_task(GooseTask::new(website_login_task).set_on_start()) // These next two tasks run repeatedly as long as the load test is running. - .register_task(GooseTask::new(website_task_index_async)) - .register_task(GooseTask::new(website_task_about_async)) + .register_task(GooseTask::new(website_index_task)) + .register_task(GooseTask::new(website_about_task)) ) .execute(); } @@ -42,28 +40,22 @@ fn main() { /// Demonstrates how to log in when a client 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_task_login<'r>(client: &'r mut GooseClient) { +async fn website_login<'r>(client: &'r mut GooseClient) { let request_builder = client.goose_post("/login"); // 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(¶ms)).await; } -fn website_task_login_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(website_task_login(client)) -} +task!(website_login, website_login_task); /// A very simple task that simply loads the front page. -async fn website_task_index<'r>(client: &'r mut GooseClient) { - let _response = client.get("/"); -} -fn website_task_index_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(website_task_index(client)) +async fn website_index<'r>(client: &'r mut GooseClient) { + let _response = client.get("/").await; } +task!(website_index, website_index_task); /// A very simple task that simply loads the about page. -async fn website_task_about<'r>(client: &'r mut GooseClient) { - let _response = client.get("/about/"); +async fn website_about<'r>(client: &'r mut GooseClient) { + let _response = client.get("/about/").await; } -fn website_task_about_async<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin(website_task_about(client)) -} \ No newline at end of file +task!(website_about, website_about_task); diff --git a/src/goose.rs b/src/goose.rs index 2909c807..3eebacfa 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -266,6 +266,15 @@ use crate::GooseConfiguration; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); +#[macro_export] +macro_rules! task { + ($task_func:ident, $task_func_wrapped:ident) => { + fn $task_func_wrapped<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { + Box::pin($task_func(client)) + } + }; +} + /// An individual task set. #[derive(Clone, Hash)] pub struct GooseTaskSet { From e98e2974cd67b031824a56ff9fe90ac542cdb304 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 24 May 2020 07:57:22 +0200 Subject: [PATCH 12/18] streamline load test --- examples/drupal_loadtest.rs | 21 +++++++-------------- examples/simple.rs | 15 +++++---------- src/goose.rs | 12 ++++++++++++ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index e7c794c6..f681f86e 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,14 +22,12 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -use std::{boxed::Box, pin::Pin, future::Future}; +use goose::boilerplate; +boilerplate!(); use rand::Rng; use regex::Regex; -use goose::{GooseAttack, task}; -use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; - fn main() { GooseAttack::initialize() .register_taskset(GooseTaskSet::new("AnonBrowsingUser") @@ -100,22 +98,19 @@ async fn drupal_loadtest_front_page<'r>(client: &'r mut GooseClient) { client.set_failure(); }, } -} -task!(drupal_loadtest_front_page, drupal_loadtest_front_page_task); +} task!(drupal_loadtest_front_page, drupal_loadtest_front_page_task); /// View a node from 1 to 10,000, created by preptest.sh. async fn drupal_loadtest_node_page<'r>(client: &'r mut GooseClient) { let nid = rand::thread_rng().gen_range(1, 10_000); let _response = client.get(format!("/node/{}", &nid).as_str()).await; -} -task!(drupal_loadtest_node_page, drupal_loadtest_node_page_task); +} task!(drupal_loadtest_node_page, drupal_loadtest_node_page_task); /// View a profile from 2 to 5,001, created by preptest.sh. async fn drupal_loadtest_profile_page<'r>(client: &'r mut GooseClient) { let uid = rand::thread_rng().gen_range(2, 5_001); let _response = client.get(format!("/user/{}", &uid).as_str()).await; -} -task!(drupal_loadtest_profile_page, drupal_loadtest_profile_page_task); +} task!(drupal_loadtest_profile_page, drupal_loadtest_profile_page_task); /// Log in. async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { @@ -157,8 +152,7 @@ async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { // Goose will catch this error. Err(_) => (), } -} -task!(drupal_loadtest_login, drupal_loadtest_login_task); +} task!(drupal_loadtest_login, drupal_loadtest_login_task); /// Post a comment. async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { @@ -240,5 +234,4 @@ async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { // Goose will catch this error. Err(_) => (), } -} -task!(drupal_loadtest_post_comment, drupal_loadtest_post_comment_task); +} task!(drupal_loadtest_post_comment, drupal_loadtest_post_comment_task); diff --git a/examples/simple.rs b/examples/simple.rs index a25a4985..1ad994b6 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,10 +17,8 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -use std::{boxed::Box, pin::Pin, future::Future}; - -use goose::{GooseAttack, task}; -use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; +use goose::boilerplate; +boilerplate!(); fn main() { GooseAttack::initialize() @@ -45,17 +43,14 @@ async fn website_login<'r>(client: &'r mut GooseClient) { // 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(¶ms)).await; -} -task!(website_login, website_login_task); +} task!(website_login, website_login_task); /// A very simple task that simply loads the front page. async fn website_index<'r>(client: &'r mut GooseClient) { let _response = client.get("/").await; -} -task!(website_index, website_index_task); +} task!(website_index, website_index_task); /// A very simple task that simply loads the about page. async fn website_about<'r>(client: &'r mut GooseClient) { let _response = client.get("/about/").await; -} -task!(website_about, website_about_task); +} task!(website_about, website_about_task); diff --git a/src/goose.rs b/src/goose.rs index 3eebacfa..bf195673 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -266,6 +266,18 @@ use crate::GooseConfiguration; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); +/// All load tests require the following use definitions. It can be done +/// manually, or using the boilerplate!() macro. +#[macro_export] +macro_rules! boilerplate { + () => { + use std::{boxed::Box, pin::Pin, future::Future}; + use goose::{GooseAttack, task}; + use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; + } +} + +/// Async tasks must be boxed and pinned for Goose to store and invoke them. #[macro_export] macro_rules! task { ($task_func:ident, $task_func_wrapped:ident) => { From cf870697790916fc3499d1a93e0e61247840b189 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 24 May 2020 08:57:35 +0200 Subject: [PATCH 13/18] fix tests --- src/goose.rs | 21 ++++++++++----------- src/lib.rs | 3 --- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/goose.rs b/src/goose.rs index bf195673..ade50d1b 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -1450,18 +1450,15 @@ impl Hash for GooseTask { #[cfg(test)] mod tests { use super::*; - use crate::dyn_async; #[test] fn goose_task_set() { // Simplistic test task functions. - #[macro_rules_attribute(dyn_async!)] - async fn test_task_function_a<'fut>(client: &'fut mut GooseClient) -> () { + async fn test_function_a<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/a/").await; } - #[macro_rules_attribute(dyn_async!)] - async fn test_task_function_b<'fut>(client: &'fut mut GooseClient) -> () { + async fn test_function_b<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/b/").await; } @@ -1478,7 +1475,8 @@ mod tests { assert_eq!(task_set.weighted_on_stop_tasks.len(), 0); // Registering a task adds it to tasks, but doesn't update weighted_tasks. - task_set = task_set.register_task(GooseTask::new(test_task_function_a)); + task!(test_function_a, test_function_a_task); + task_set = task_set.register_task(GooseTask::new(test_function_a_task)); assert_eq!(task_set.tasks.len(), 1); assert_eq!(task_set.weighted_tasks.len(), 0); assert_eq!(task_set.task_sets_index, usize::max_value()); @@ -1488,7 +1486,8 @@ mod tests { assert_eq!(task_set.host, None); // Different task can be registered. - task_set = task_set.register_task(GooseTask::new(test_task_function_b)); + task!(test_function_b, test_function_b_task); + task_set = task_set.register_task(GooseTask::new(test_function_b_task)); assert_eq!(task_set.tasks.len(), 2); assert_eq!(task_set.weighted_tasks.len(), 0); assert_eq!(task_set.task_sets_index, usize::max_value()); @@ -1498,7 +1497,7 @@ mod tests { assert_eq!(task_set.host, None); // Same task can be registered again. - task_set = task_set.register_task(GooseTask::new(test_task_function_a)); + task_set = task_set.register_task(GooseTask::new(test_function_a_task)); assert_eq!(task_set.tasks.len(), 3); assert_eq!(task_set.weighted_tasks.len(), 0); assert_eq!(task_set.task_sets_index, usize::max_value()); @@ -1554,13 +1553,13 @@ mod tests { #[test] fn goose_task() { // Simplistic test task functions. - #[macro_rules_attribute(dyn_async!)] - async fn test_task_function_a <'fut>(client: &'fut mut GooseClient) -> () { + async fn test_function_a <'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/a/"); } // Initialize task set. - let mut task = GooseTask::new(test_task_function_a); + task!(test_function_a, test_function_a_task); + let mut task = GooseTask::new(test_function_a_task); assert_eq!(task.tasks_index, usize::max_value()); assert_eq!(task.name, "".to_string()); assert_eq!(task.weight, 1); diff --git a/src/lib.rs b/src/lib.rs index 55892256..6383d191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,9 +272,6 @@ #[macro_use] extern crate log; -#[cfg(test)] -#[macro_use] -extern crate macro_rules_attribute; //#[macro_use] //extern crate goose_codegen; From 54e879b714dc1a2765e794a8ed86184e77f48d94 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 24 May 2020 14:11:05 +0200 Subject: [PATCH 14/18] simplified inline task!() macro --- examples/drupal_loadtest.rs | 26 +++++++++++++------------- examples/simple.rs | 12 ++++++------ src/goose.rs | 17 ++++++----------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index f681f86e..3877dff2 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -32,38 +32,38 @@ fn main() { GooseAttack::initialize() .register_taskset(GooseTaskSet::new("AnonBrowsingUser") .set_weight(4) - .register_task(GooseTask::new(drupal_loadtest_front_page_task) + .register_task(GooseTask::new(task!(drupal_loadtest_front_page)) .set_weight(15) .set_name("(Anon) front page") ) - .register_task(GooseTask::new(drupal_loadtest_node_page_task) + .register_task(GooseTask::new(task!(drupal_loadtest_node_page)) .set_weight(10) .set_name("(Anon) node page") ) - .register_task(GooseTask::new(drupal_loadtest_profile_page_task) + .register_task(GooseTask::new(task!(drupal_loadtest_profile_page)) .set_weight(3) .set_name("(Anon) user page") ) ) .register_taskset(GooseTaskSet::new("AuthBrowsingUser") .set_weight(1) - .register_task(GooseTask::new(drupal_loadtest_login_task) + .register_task(GooseTask::new(task!(drupal_loadtest_login)) .set_on_start() .set_name("(Auth) login") ) - .register_task(GooseTask::new(drupal_loadtest_front_page_task) + .register_task(GooseTask::new(task!(drupal_loadtest_front_page)) .set_weight(15) .set_name("(Auth) front page") ) - .register_task(GooseTask::new(drupal_loadtest_node_page_task) + .register_task(GooseTask::new(task!(drupal_loadtest_node_page)) .set_weight(10) .set_name("(Auth) node page") ) - .register_task(GooseTask::new(drupal_loadtest_profile_page_task) + .register_task(GooseTask::new(task!(drupal_loadtest_profile_page)) .set_weight(3) .set_name("(Auth) user page") ) - .register_task(GooseTask::new(drupal_loadtest_post_comment_task) + .register_task(GooseTask::new(task!(drupal_loadtest_post_comment)) .set_weight(3) .set_name("(Auth) comment form") ) @@ -98,19 +98,19 @@ async fn drupal_loadtest_front_page<'r>(client: &'r mut GooseClient) { client.set_failure(); }, } -} task!(drupal_loadtest_front_page, drupal_loadtest_front_page_task); +} /// View a node from 1 to 10,000, created by preptest.sh. async fn drupal_loadtest_node_page<'r>(client: &'r mut GooseClient) { let nid = rand::thread_rng().gen_range(1, 10_000); let _response = client.get(format!("/node/{}", &nid).as_str()).await; -} task!(drupal_loadtest_node_page, drupal_loadtest_node_page_task); +} /// View a profile from 2 to 5,001, created by preptest.sh. async fn drupal_loadtest_profile_page<'r>(client: &'r mut GooseClient) { let uid = rand::thread_rng().gen_range(2, 5_001); let _response = client.get(format!("/user/{}", &uid).as_str()).await; -} task!(drupal_loadtest_profile_page, drupal_loadtest_profile_page_task); +} /// Log in. async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { @@ -152,7 +152,7 @@ async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { // Goose will catch this error. Err(_) => (), } -} task!(drupal_loadtest_login, drupal_loadtest_login_task); +} /// Post a comment. async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { @@ -234,4 +234,4 @@ async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { // Goose will catch this error. Err(_) => (), } -} task!(drupal_loadtest_post_comment, drupal_loadtest_post_comment_task); +} diff --git a/examples/simple.rs b/examples/simple.rs index 1ad994b6..1bc2cbbb 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -27,10 +27,10 @@ fn main() { // 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. - .register_task(GooseTask::new(website_login_task).set_on_start()) + .register_task(GooseTask::new(task!(website_login)).set_on_start()) // These next two tasks run repeatedly as long as the load test is running. - .register_task(GooseTask::new(website_index_task)) - .register_task(GooseTask::new(website_about_task)) + .register_task(GooseTask::new(task!(website_index))) + .register_task(GooseTask::new(task!(website_about))) ) .execute(); } @@ -43,14 +43,14 @@ async fn website_login<'r>(client: &'r mut GooseClient) { // 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(¶ms)).await; -} task!(website_login, website_login_task); +} /// A very simple task that simply loads the front page. async fn website_index<'r>(client: &'r mut GooseClient) { let _response = client.get("/").await; -} task!(website_index, website_index_task); +} /// A very simple task that simply loads the about page. async fn website_about<'r>(client: &'r mut GooseClient) { let _response = client.get("/about/").await; -} task!(website_about, website_about_task); +} diff --git a/src/goose.rs b/src/goose.rs index ade50d1b..7d1a618a 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -280,10 +280,8 @@ macro_rules! boilerplate { /// Async tasks must be boxed and pinned for Goose to store and invoke them. #[macro_export] macro_rules! task { - ($task_func:ident, $task_func_wrapped:ident) => { - fn $task_func_wrapped<'r>(client: &'r mut GooseClient) -> Pin + Send + 'r>> { - Box::pin($task_func(client)) - } + ($task_func:ident) => { + move |s| Box::pin($task_func(s)) }; } @@ -1475,8 +1473,7 @@ mod tests { assert_eq!(task_set.weighted_on_stop_tasks.len(), 0); // Registering a task adds it to tasks, but doesn't update weighted_tasks. - task!(test_function_a, test_function_a_task); - task_set = task_set.register_task(GooseTask::new(test_function_a_task)); + task_set = task_set.register_task(GooseTask::new(task!(test_function_a))); assert_eq!(task_set.tasks.len(), 1); assert_eq!(task_set.weighted_tasks.len(), 0); assert_eq!(task_set.task_sets_index, usize::max_value()); @@ -1486,8 +1483,7 @@ mod tests { assert_eq!(task_set.host, None); // Different task can be registered. - task!(test_function_b, test_function_b_task); - task_set = task_set.register_task(GooseTask::new(test_function_b_task)); + task_set = task_set.register_task(GooseTask::new(task!(test_function_b))); assert_eq!(task_set.tasks.len(), 2); assert_eq!(task_set.weighted_tasks.len(), 0); assert_eq!(task_set.task_sets_index, usize::max_value()); @@ -1497,7 +1493,7 @@ mod tests { assert_eq!(task_set.host, None); // Same task can be registered again. - task_set = task_set.register_task(GooseTask::new(test_function_a_task)); + task_set = task_set.register_task(GooseTask::new(task!(test_function_a))); assert_eq!(task_set.tasks.len(), 3); assert_eq!(task_set.weighted_tasks.len(), 0); assert_eq!(task_set.task_sets_index, usize::max_value()); @@ -1558,8 +1554,7 @@ mod tests { } // Initialize task set. - task!(test_function_a, test_function_a_task); - let mut task = GooseTask::new(test_function_a_task); + let mut task = GooseTask::new(task!(test_function_a)); assert_eq!(task.tasks_index, usize::max_value()); assert_eq!(task.name, "".to_string()); assert_eq!(task.weight, 1); From 3812e10efbf2433b7c1c5e0a23971f4537792484 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 24 May 2020 16:27:04 +0200 Subject: [PATCH 15/18] remove no-longer-needed lifetimes --- examples/drupal_loadtest.rs | 10 +++++----- examples/simple.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index 3877dff2..5fd4a149 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -72,7 +72,7 @@ fn main() { } /// View the front page. -async fn drupal_loadtest_front_page<'r>(client: &'r mut GooseClient) { +async fn drupal_loadtest_front_page(client: &mut GooseClient) { let response = client.get("/").await; // Grab some static assets from the front page. @@ -101,19 +101,19 @@ async fn drupal_loadtest_front_page<'r>(client: &'r mut GooseClient) { } /// View a node from 1 to 10,000, created by preptest.sh. -async fn drupal_loadtest_node_page<'r>(client: &'r mut GooseClient) { +async fn drupal_loadtest_node_page(client: &mut GooseClient) { let nid = rand::thread_rng().gen_range(1, 10_000); let _response = client.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<'r>(client: &'r mut GooseClient) { +async fn drupal_loadtest_profile_page(client: &mut GooseClient) { let uid = rand::thread_rng().gen_range(2, 5_001); let _response = client.get(format!("/user/{}", &uid).as_str()).await; } /// Log in. -async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { +async fn drupal_loadtest_login(client: &mut GooseClient) { let response = client.get("/user").await; match response { Ok(r) => { @@ -155,7 +155,7 @@ async fn drupal_loadtest_login<'r>(client: &'r mut GooseClient) { } /// Post a comment. -async fn drupal_loadtest_post_comment<'r>(client: &'r mut GooseClient) { +async fn drupal_loadtest_post_comment(client: &mut GooseClient) { let nid: i32 = rand::thread_rng().gen_range(1, 10_000); let response = client.get(format!("/node/{}", &nid).as_str()).await; match response { diff --git a/examples/simple.rs b/examples/simple.rs index 1bc2cbbb..c634582a 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -38,7 +38,7 @@ fn main() { /// Demonstrates how to log in when a client 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<'r>(client: &'r mut GooseClient) { +async fn website_login(client: &mut GooseClient) { let request_builder = client.goose_post("/login"); // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form let params = [("username", "test_user"), ("password", "")]; @@ -46,11 +46,11 @@ async fn website_login<'r>(client: &'r mut GooseClient) { } /// A very simple task that simply loads the front page. -async fn website_index<'r>(client: &'r mut GooseClient) { +async fn website_index(client: &mut GooseClient) { let _response = client.get("/").await; } /// A very simple task that simply loads the about page. -async fn website_about<'r>(client: &'r mut GooseClient) { +async fn website_about(client: &mut GooseClient) { let _response = client.get("/about/").await; } From 7cfb6ff5e4f14c895a550af30175b6d1489d20c6 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sun, 24 May 2020 16:35:09 +0200 Subject: [PATCH 16/18] join_all clients --- Cargo.toml | 1 + src/lib.rs | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce785ba0..570bf755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ license = "Apache-2.0" [dependencies] #goose_codegen = { path = "goose_codegen" } ctrlc = "3.1" +futures = "0.3" http = "0.2" lazy_static = "1.4" log = "0.4" diff --git a/src/lib.rs b/src/lib.rs index 6383d191..c0803e4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -524,15 +524,15 @@ impl GooseAttack { /// /// # Example /// ```rust,no_run - /// use goose::GooseAttack; + /// use goose::GooseAttack, task; /// use goose::goose::{GooseTaskSet, GooseTask, GooseClient}; /// /// GooseAttack::initialize() /// .register_taskset(GooseTaskSet::new("ExampleTasks") - /// .register_task(GooseTask::new(example_task)) + /// .register_task(GooseTask::new(task!(example_task))) /// ) /// .register_taskset(GooseTaskSet::new("OtherTasks") - /// .register_task(GooseTask::new(other_task)) + /// .register_task(GooseTask::new(task!(other_task))) /// ); /// /// fn example_task(client: &mut GooseClient) { @@ -1049,9 +1049,7 @@ impl GooseAttack { else { info!("waiting for clients to exit"); } - for client in clients { - let _ = client.await; - } + futures::future::join_all(clients).await; debug!("all clients exited"); // If we're printing statistics, collect the final messages received from clients From 63b093eea0405d4f8faac193fdbdf9d699f2475b Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Mon, 25 May 2020 07:56:28 +0200 Subject: [PATCH 17/18] fix doc-tests --- examples/drupal_loadtest.rs | 8 +- examples/simple.rs | 8 +- src/goose.rs | 299 ++++++++++++++++++++++++------------ src/lib.rs | 22 ++- 4 files changed, 230 insertions(+), 107 deletions(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index 5fd4a149..f9ef1543 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,12 +22,16 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -use goose::boilerplate; -boilerplate!(); +use goose::{GooseAttack, task}; +use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; + +// Needed to wrap and store async functions. +use std::boxed::Box; use rand::Rng; use regex::Regex; + fn main() { GooseAttack::initialize() .register_taskset(GooseTaskSet::new("AnonBrowsingUser") diff --git a/examples/simple.rs b/examples/simple.rs index c634582a..9c27df6f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,8 +17,12 @@ //! See the License for the specific language governing permissions and //! limitations under the License. -use goose::boilerplate; -boilerplate!(); +use goose::{GooseAttack, task}; +use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; + +// Needed to wrap and store async functions. +use std::boxed::Box; + fn main() { GooseAttack::initialize() diff --git a/src/goose.rs b/src/goose.rs index 7d1a618a..608658af 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -68,13 +68,17 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; //! -//! let mut a_task = GooseTask::new(task_function); +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; +//! +//! let mut a_task = GooseTask::new(task!(task_function)); //! //! /// A very simple task that simply loads the front page. -//! fn task_function<'fut>(client: &'fut mut GooseClient) { -//! let _response = client.get("/"); -//! } +//! async fn task_function(client: &mut GooseClient) { +//! let _response = client.get("/"); +//! } //! ``` //! //! ### Task Name @@ -84,11 +88,15 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; //! -//! let mut a_task = GooseTask::new(task_function).set_name("a"); +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; +//! +//! let mut a_task = GooseTask::new(task!(task_function)).set_name("a"); //! //! /// A very simple task that simply loads the front page. -//! fn task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn task_function(client: &mut GooseClient) { //! let _response = client.get("/"); //! } //! ``` @@ -101,17 +109,21 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; +//! +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; //! -//! let mut a_task = GooseTask::new(a_task_function).set_weight(9); -//! let mut b_task = GooseTask::new(b_task_function).set_weight(3); +//! let mut a_task = GooseTask::new(task!(a_task_function)).set_weight(9); +//! let mut b_task = GooseTask::new(task!(b_task_function)).set_weight(3); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn a_task_function(client: &mut GooseClient) { //! let _response = client.get("/a/"); //! } //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn b_task_function(client: &mut GooseClient) { //! let _response = client.get("/b/"); //! } //! ``` @@ -128,23 +140,27 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; //! -//! let mut a_task = GooseTask::new(a_task_function).set_sequence(1); -//! let mut b_task = GooseTask::new(b_task_function).set_sequence(2); -//! let mut c_task = GooseTask::new(c_task_function); +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; +//! +//! let mut a_task = GooseTask::new(task!(a_task_function)).set_sequence(1); +//! let mut b_task = GooseTask::new(task!(b_task_function)).set_sequence(2); +//! let mut c_task = GooseTask::new(task!(c_task_function)); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn a_task_function(client: &mut GooseClient) { //! let _response = client.get("/a/"); //! } //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn b_task_function(client: &mut GooseClient) { //! let _response = client.get("/b/"); //! } //! //! /// Another very simple task that simply loads the "c" page. -//! fn c_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn c_task_function(client: &mut GooseClient) { //! let _response = client.get("/c/"); //! } //! ``` @@ -159,11 +175,15 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; +//! +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; //! -//! let mut a_task = GooseTask::new(a_task_function).set_sequence(1).set_on_start(); +//! let mut a_task = GooseTask::new(task!(a_task_function)).set_sequence(1).set_on_start(); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn a_task_function(client: &mut GooseClient) { //! let _response = client.get("/a/"); //! } //! ``` @@ -178,11 +198,15 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; +//! +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; //! -//! let mut b_task = GooseTask::new(b_task_function).set_sequence(2).set_on_stop(); +//! let mut b_task = GooseTask::new(task!(b_task_function)).set_sequence(2).set_on_stop(); //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { +//! async fn b_task_function(client: &mut GooseClient) { //! let _response = client.get("/b/"); //! } //! ``` @@ -206,11 +230,15 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; //! -//! let mut task = GooseTask::new(get_function); +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; +//! +//! let mut task = GooseTask::new(task!(get_function)); //! //! /// A very simple task that makes a GET request. -//! fn get_function<'fut>(client: &'fut mut GooseClient) { +//! async fn get_function(client: &mut GooseClient) { //! let _response = client.get("/path/to/foo/"); //! } //! ``` @@ -227,11 +255,15 @@ //! //! ```rust //! use goose::goose::{GooseTask, GooseClient}; +//! use goose::task; +//! +//! // Needed to wrap and store async functions. +//! use std::boxed::Box; //! -//! let mut task = GooseTask::new(post_function); +//! let mut task = GooseTask::new(task!(post_function)); //! //! /// A very simple task that makes a POST request. -//! fn post_function<'fut>(client: &'fut mut GooseClient) { +//! async fn post_function(client: &mut GooseClient) { //! let _response = client.post("/path/to/foo/", "string value to post".to_string()); //! } //! ``` @@ -266,17 +298,6 @@ use crate::GooseConfiguration; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); -/// All load tests require the following use definitions. It can be done -/// manually, or using the boilerplate!() macro. -#[macro_export] -macro_rules! boilerplate { - () => { - use std::{boxed::Box, pin::Pin, future::Future}; - use goose::{GooseAttack, task}; - use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; - } -} - /// Async tasks must be boxed and pinned for Goose to store and invoke them. #[macro_export] macro_rules! task { @@ -342,12 +363,16 @@ impl GooseTaskSet { /// # Example /// ```rust /// use goose::goose::{GooseTaskSet, GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// /// let mut example_tasks = GooseTaskSet::new("ExampleTasks"); - /// example_tasks.register_task(GooseTask::new(a_task_function)); + /// example_tasks.register_task(GooseTask::new(task!(a_task_function))); /// /// /// A very simple task that simply loads the "a" page. - /// fn a_task_function<'fut>(client: &'fut mut GooseClient) { + /// async fn a_task_function(client: &mut GooseClient) { /// let _response = client.get("/a/"); /// } /// ``` @@ -697,11 +722,15 @@ impl GooseClient { /// In this example, the request will show up as "GET foo": /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let mut task = GooseTask::new(get_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// + /// let mut task = GooseTask::new(task!(get_function)); /// /// /// A very simple task that makes a GET request. - /// fn get_function<'fut>(client: &'fut mut GooseClient) { + /// async fn get_function(client: &mut GooseClient) { /// let _response = client.set_request_name("foo").get("/path/to/foo"); /// } /// ``` @@ -710,13 +739,17 @@ impl GooseClient { /// second request will show up as "GET /path/to/foo". /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(get_function); + /// let mut task = GooseTask::new(task!(get_function)); /// /// /// A very simple task that makes a GET request. - /// fn get_function<'fut>(client: &'fut mut GooseClient) { - /// let _response = client.set_request_name("foo").get("/path/to/foo"); - /// let _response = client.get("/path/to/foo"); + /// async fn get_function(client: &mut GooseClient) { + /// let _response = client.set_request_name("foo").get("/path/to/foo").await; + /// let _response = client.get("/path/to/foo").await; /// } /// ``` pub fn set_request_name(&mut self, name: &str) -> &mut Self { @@ -804,11 +837,15 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(get_function); + /// let mut task = GooseTask::new(task!(get_function)); /// /// /// A very simple task that makes a GET request. - /// fn get_function<'fut>(client: &'fut mut GooseClient) { + /// async fn get_function(client: &mut GooseClient) { /// let _response = client.get("/path/to/foo/"); /// } /// ``` @@ -829,11 +866,15 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let mut task = GooseTask::new(post_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// + /// let mut task = GooseTask::new(task!(post_function)); /// /// /// A very simple task that makes a POST request. - /// fn post_function<'fut>(client: &'fut mut GooseClient) { + /// async fn post_function(client: &mut GooseClient) { /// let _response = client.post("/path/to/foo/", "BODY BEING POSTED".to_string()); /// } /// ``` @@ -854,11 +895,15 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(head_function); + /// let mut task = GooseTask::new(task!(head_function)); /// /// /// A very simple task that makes a HEAD request. - /// fn head_function<'fut>(client: &'fut mut GooseClient) { + /// async fn head_function(client: &mut GooseClient) { /// let _response = client.head("/path/to/foo/"); /// } /// ``` @@ -879,11 +924,15 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(delete_function); + /// let mut task = GooseTask::new(task!(delete_function)); /// /// /// A very simple task that makes a DELETE request. - /// fn delete_function<'fut>(client: &'fut mut GooseClient) { + /// async fn delete_function(client: &mut GooseClient) { /// let _response = client.delete("/path/to/foo/"); /// } /// ``` @@ -902,12 +951,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let mut task = GooseTask::new(get_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// + /// let mut task = GooseTask::new(task!(get_function)); /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. - /// fn get_function<'fut>(client: &'fut mut GooseClient) { + /// async fn get_function(client: &mut GooseClient) { /// let request_builder = client.goose_get("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -926,12 +979,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(post_function); + /// let mut task = GooseTask::new(task!(post_function)); /// /// /// A simple task that makes a POST request, exposing the Reqwest /// /// request builder. - /// fn post_function<'fut>(client: &'fut mut GooseClient) { + /// async fn post_function(client: &mut GooseClient) { /// let request_builder = client.goose_post("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -950,12 +1007,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let mut task = GooseTask::new(head_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// + /// let mut task = GooseTask::new(task!(head_function)); /// /// /// A simple task that makes a HEAD request, exposing the Reqwest /// /// request builder. - /// fn head_function<'fut>(client: &'fut mut GooseClient) { + /// async fn head_function(client: &mut GooseClient) { /// let request_builder = client.goose_head("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -974,12 +1035,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(put_function); + /// let mut task = GooseTask::new(task!(put_function)); /// /// /// A simple task that makes a PUT request, exposing the Reqwest /// /// request builder. - /// fn put_function<'fut>(client: &'fut mut GooseClient) { + /// async fn put_function(client: &mut GooseClient) { /// let request_builder = client.goose_put("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -998,12 +1063,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let mut task = GooseTask::new(patch_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// + /// let mut task = GooseTask::new(task!(patch_function)); /// /// /// A simple task that makes a PUT request, exposing the Reqwest /// /// request builder. - /// fn patch_function<'fut>(client: &'fut mut GooseClient) { + /// async fn patch_function(client: &mut GooseClient) { /// let request_builder = client.goose_patch("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -1022,12 +1091,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(delete_function); + /// let mut task = GooseTask::new(task!(delete_function)); /// /// /// A simple task that makes a DELETE request, exposing the Reqwest /// /// request builder. - /// fn delete_function<'fut>(client: &'fut mut GooseClient) { + /// async fn delete_function(client: &mut GooseClient) { /// let request_builder = client.goose_delete("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -1049,12 +1122,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let mut task = GooseTask::new(get_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// + /// let mut task = GooseTask::new(task!(get_function)); /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. - /// fn get_function<'fut>(client: &'fut mut GooseClient) { + /// async fn get_function(client: &mut GooseClient) { /// let request_builder = client.goose_get("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -1161,12 +1238,16 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(get_function); + /// let mut task = GooseTask::new(task!(get_function)); /// /// /// A simple task that makes a GET request. - /// fn get_function<'fut>(client: &'fut mut GooseClient) { - /// let response = client.get("/404"); + /// async fn get_function(client: &mut GooseClient) { + /// let response = client.get("/404").await; /// match &response { /// Ok(r) => { /// // We expect a 404 here. @@ -1198,17 +1279,21 @@ impl GooseClient { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let mut task = GooseTask::new(loadtest_index_page); + /// let mut task = GooseTask::new(task!(loadtest_index_page)); /// - /// fn loadtest_index_page<'fut>(client: &'fut mut GooseClient) { - /// let response = client.set_request_name("index").get("/"); + /// async fn loadtest_index_page(client: &mut GooseClient) { + /// let response = client.set_request_name("index").get("/").await; /// // Extract the response Result. /// match response { /// Ok(r) => { /// // We only need to check pages that returned a success status code. /// if r.status().is_success() { - /// match r.text() { + /// match r.text().await { /// Ok(text) => { /// // If the expected string doesn't exist, this page load /// // was a failure. @@ -1281,10 +1366,14 @@ impl GooseTask { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// GooseTask::new(my_task_function).set_name("foo"); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// fn my_task_function<'fut>(client: &'fut mut GooseClient) { + /// GooseTask::new(task!(my_task_function)).set_name("foo"); + /// + /// async fn my_task_function(client: &mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1307,10 +1396,14 @@ impl GooseTask { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// GooseTask::new(my_on_start_function).set_on_start(); + /// GooseTask::new(task!(my_on_start_function)).set_on_start(); /// - /// fn my_on_start_function<'fut>(client: &'fut mut GooseClient) { + /// async fn my_on_start_function(client: &mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1333,10 +1426,14 @@ impl GooseTask { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// GooseTask::new(my_on_stop_function).set_on_stop(); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// fn my_on_stop_function<'fut>(client: &'fut mut GooseClient) { + /// GooseTask::new(task!(my_on_stop_function)).set_on_stop(); + /// + /// async fn my_on_stop_function(client: &mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1353,10 +1450,14 @@ impl GooseTask { /// # Example /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// GooseTask::new(task_function).set_weight(3); + /// GooseTask::new(task!(task_function)).set_weight(3); /// - /// fn task_function<'fut>(client: &'fut mut GooseClient) { + /// async fn task_function(client: &mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1384,20 +1485,24 @@ impl GooseTask { /// In this first example, the variable names indicate the order the tasks will be run in: /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; /// - /// let runs_first = GooseTask::new(first_task_function).set_sequence(3); - /// let runs_second = GooseTask::new(second_task_function).set_sequence(5835); - /// let runs_last = GooseTask::new(third_task_function); + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// fn first_task_function<'fut>(client: &'fut mut GooseClient) { + /// let runs_first = GooseTask::new(task!(first_task_function)).set_sequence(3); + /// let runs_second = GooseTask::new(task!(second_task_function)).set_sequence(5835); + /// let runs_last = GooseTask::new(task!(third_task_function)); + /// + /// async fn first_task_function(client: &mut GooseClient) { /// let _response = client.get("/1"); /// } /// - /// fn second_task_function<'fut>(client: &'fut mut GooseClient) { + /// async fn second_task_function(client: &mut GooseClient) { /// let _response = client.get("/2"); /// } /// - /// fn third_task_function<'fut>(client: &'fut mut GooseClient) { + /// async fn third_task_function(client: &mut GooseClient) { /// let _response = client.get("/3"); /// } /// ``` @@ -1408,20 +1513,24 @@ impl GooseTask { /// run in a random and weighted order: /// ```rust /// use goose::goose::{GooseTask, GooseClient}; + /// use goose::task; + /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; /// - /// let runs_first = GooseTask::new(first_task_function).set_sequence(1).set_weight(2); - /// let runs_second = GooseTask::new(second_task_function_a).set_sequence(2); - /// let also_runs_second = GooseTask::new(second_task_function_b).set_sequence(2).set_weight(2); + /// let runs_first = GooseTask::new(task!(first_task_function)).set_sequence(1).set_weight(2); + /// let runs_second = GooseTask::new(task!(second_task_function_a)).set_sequence(2); + /// let also_runs_second = GooseTask::new(task!(second_task_function_b)).set_sequence(2).set_weight(2); /// - /// fn first_task_function<'fut>(client: &'fut mut GooseClient) { + /// async fn first_task_function(client: &mut GooseClient) { /// let _response = client.get("/1"); /// } /// - /// fn second_task_function_a<'fut>(client: &'fut mut GooseClient) { + /// async fn second_task_function_a(client: &mut GooseClient) { /// let _response = client.get("/2a"); /// } /// - /// fn second_task_function_b<'fut>(client: &'fut mut GooseClient) { + /// async fn second_task_function_b(client: &mut GooseClient) { /// let _response = client.get("/2b"); /// } /// ``` @@ -1452,11 +1561,11 @@ mod tests { #[test] fn goose_task_set() { // Simplistic test task functions. - async fn test_function_a<'fut>(client: &'fut mut GooseClient) -> () { + async fn test_function_a(client: &mut GooseClient) -> () { let _response = client.get("/a/").await; } - async fn test_function_b<'fut>(client: &'fut mut GooseClient) -> () { + async fn test_function_b(client: &mut GooseClient) -> () { let _response = client.get("/b/").await; } @@ -1549,7 +1658,7 @@ mod tests { #[test] fn goose_task() { // Simplistic test task functions. - async fn test_function_a <'fut>(client: &'fut mut GooseClient) -> () { + async fn test_function_a(client: &mut GooseClient) -> () { let _response = client.get("/a/"); } diff --git a/src/lib.rs b/src/lib.rs index c0803e4a..e1f5d9b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -524,9 +524,12 @@ impl GooseAttack { /// /// # Example /// ```rust,no_run - /// use goose::GooseAttack, task; + /// use goose::{GooseAttack, task}; /// use goose::goose::{GooseTaskSet, GooseTask, GooseClient}; /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// /// GooseAttack::initialize() /// .register_taskset(GooseTaskSet::new("ExampleTasks") /// .register_task(GooseTask::new(task!(example_task))) @@ -535,11 +538,11 @@ impl GooseAttack { /// .register_task(GooseTask::new(task!(other_task))) /// ); /// - /// fn example_task(client: &mut GooseClient) { + /// async fn example_task(client: &mut GooseClient) { /// let _response = client.get("/foo"); /// } /// - /// fn other_task(client: &mut GooseClient) { + /// async fn other_task(client: &mut GooseClient) { /// let _response = client.get("/bar"); /// } /// ``` @@ -635,21 +638,24 @@ impl GooseAttack { /// /// # Example /// ```rust,no_run - /// use goose::GooseAttack; + /// use goose::{GooseAttack, task}; /// use goose::goose::{GooseTaskSet, GooseTask, GooseClient}; /// + /// // Needed to wrap and store async functions. + /// use std::boxed::Box; + /// /// GooseAttack::initialize() /// .register_taskset(GooseTaskSet::new("ExampleTasks") - /// .register_task(GooseTask::new(example_task).set_weight(2)) - /// .register_task(GooseTask::new(another_example_task).set_weight(3)) + /// .register_task(GooseTask::new(task!(example_task)).set_weight(2)) + /// .register_task(GooseTask::new(task!(another_example_task)).set_weight(3)) /// ) /// .execute(); /// - /// fn example_task(client: &mut GooseClient) { + /// async fn example_task(client: &mut GooseClient) { /// let _response = client.get("/foo"); /// } /// - /// fn another_example_task(client: &mut GooseClient) { + /// async fn another_example_task(client: &mut GooseClient) { /// let _response = client.get("/bar"); /// } /// ``` From 659665678e08d09c23833881543d5347fbc6b2b5 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Mon, 25 May 2020 08:11:11 +0200 Subject: [PATCH 18/18] replace thread::sleep with tokio::time::delay_for --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e1f5d9b2..05824474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -296,7 +296,7 @@ use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::sync::{Arc, mpsc, Mutex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::{thread, time}; +use std::time; use lazy_static::lazy_static; #[cfg(feature = "gaggle")] @@ -916,7 +916,7 @@ impl GooseAttack { clients.push(client); self.active_clients += 1; debug!("sleeping {:?} milliseconds...", sleep_duration); - thread::sleep(sleep_duration); + tokio::time::delay_for(sleep_duration).await; } else { warn!("no tasks for thread {} to run", self.task_sets[thread_client.task_sets_index].name); @@ -976,7 +976,7 @@ impl GooseAttack { display_running_statistics = true; // Give client threads time to send statstics. let pause = time::Duration::from_millis(100); - thread::sleep(pause); + tokio::time::delay_for(pause).await; } } @@ -1103,7 +1103,7 @@ impl GooseAttack { } let one_second = time::Duration::from_secs(1); - thread::sleep(one_second); + tokio::time::delay_for(one_second).await; } self }