Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to Async #22

Merged
merged 18 commits into from
May 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "goose"
version = "0.6.3-dev"
version = "0.7.0-dev"
authors = ["Jeremy Andrews <jeremy@tag1consulting.com>"]
edition = "2018"
description = "A load testing tool inspired by Locust."
Expand All @@ -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"
Expand All @@ -26,6 +27,7 @@ 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"

# optional dependencies
Expand Down
58 changes: 31 additions & 27 deletions examples/drupal_loadtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,52 @@
//! See the License for the specific language governing permissions and
//! limitations under the License.

use goose::{GooseAttack, task};
use goose::goose::{GooseTaskSet, GooseClient, GooseTask};

// Needed to wrap and store async functions.
use std::boxed::Box;

jeremyandrews marked this conversation as resolved.
Show resolved Hide resolved
use rand::Rng;
use regex::Regex;

use goose::GooseAttack;
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(task!(drupal_loadtest_front_page))
.set_weight(15)
.set_name("(Anon) front page")
)
.register_task(GooseTask::new(drupal_loadtest_node_page)
.register_task(GooseTask::new(task!(drupal_loadtest_node_page))
jeremyandrews marked this conversation as resolved.
Show resolved Hide resolved
.set_weight(10)
.set_name("(Anon) node page")
)
.register_task(GooseTask::new(drupal_loadtest_profile_page)
.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)
.register_task(GooseTask::new(task!(drupal_loadtest_login))
.set_on_start()
.set_name("(Auth) login")
)
.register_task(GooseTask::new(drupal_loadtest_front_page)
.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)
.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)
.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)
.register_task(GooseTask::new(task!(drupal_loadtest_post_comment))
.set_weight(3)
.set_name("(Auth) comment form")
)
Expand All @@ -72,13 +76,13 @@ fn main() {
}

/// View the front page.
fn drupal_loadtest_front_page(client: &mut GooseClient) {
let response = client.get("/");
async fn drupal_loadtest_front_page(client: &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) {
Expand All @@ -101,23 +105,23 @@ 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) {
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());
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) {
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());
let _response = client.get(format!("/user/{}", &uid).as_str()).await;
}

/// Log in.
fn drupal_loadtest_login(client: &mut GooseClient) {
let response = client.get("/user");
async fn drupal_loadtest_login(client: &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) {
Expand All @@ -140,7 +144,7 @@ fn drupal_loadtest_login(client: &mut GooseClient) {
("op", "Log+in"),
];
let request_builder = client.goose_post("/user");
let _response = client.goose_send(request_builder.form(&params));
let _response = client.goose_send(request_builder.form(&params)).await;
// @TODO: verify that we actually logged in.
}
Err(e) => {
Expand All @@ -155,12 +159,12 @@ 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());
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 {
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();
Expand Down Expand Up @@ -205,10 +209,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(&params));
let response = client.goose_send(request_builder.form(&params)).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);
Expand Down
24 changes: 14 additions & 10 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,44 @@
//! See the License for the specific language governing permissions and
//! limitations under the License.

use goose::GooseAttack;
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()
// In this example, we only create a single taskset, named "WebsiteUser".
.register_taskset(GooseTaskSet::new("WebsiteUser")
// After each task runs, sleep randomly from 5 to 15 seconds.
.set_wait_time(5, 15)
// This task only runs one time when the client first starts.
.register_task(GooseTask::new(website_task_login).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_task_index))
.register_task(GooseTask::new(website_task_about))
.register_task(GooseTask::new(task!(website_index)))
.register_task(GooseTask::new(task!(website_about)))
)
.execute();
}

/// 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) {
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", "")];
let _response = client.goose_send(request_builder.form(&params));
let _response = client.goose_send(request_builder.form(&params)).await;
}

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

/// A very simple task that simply loads the about page.
fn website_task_about(client: &mut GooseClient) {
let _response = client.get("/about/");
async fn website_about(client: &mut GooseClient) {
let _response = client.get("/about/").await;
}
20 changes: 10 additions & 10 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,13 +36,13 @@ pub 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());
}
// Invoke the task function.
function(&mut thread_client);
function(&mut thread_client).await;
}
}
}
Expand All @@ -66,14 +66,14 @@ pub 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 != "" {
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;
Expand Down Expand Up @@ -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;
Expand All @@ -140,13 +140,13 @@ pub 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());
}
// Invoke the task function.
function(&mut thread_client);
function(&mut thread_client).await;
}
}
}
Expand All @@ -159,4 +159,4 @@ pub fn client_main(
else {
info!("exiting client {} from {}...", thread_number, thread_task_set.name);
}
}
}
Loading