Skip to content

Commit 3db41cf

Browse files
Merge pull request tag1consulting#368 from jeremyandrews/revert-test
optimize fast path with Duration-based wait time
2 parents dd81446 + a4ba013 commit 3db41cf

9 files changed

+115
-91
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 0.14.1-dev
44
- [#364](https://github.com/tag1consulting/goose/pull/364) add link from the [Developer Documentation](https://docs.rs/goose) to [The Git Book](https://book.goose.rs)
5+
- [#368](https://github.com/tag1consulting/goose/pull/368) optimize fastpath if no delay between tasks
56

67
## 0.14.0 September 15, 2021
78
- [#361](https://github.com/tag1consulting/goose/pull/361) convert `README.md` (and enhance) into [`The Goose Book`](https://book.goose.rs/)

examples/simple.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
//! See the License for the specific language governing permissions and
1818
//! limitations under the License.
1919
20-
use std::time::Duration;
21-
2220
use goose::prelude::*;
21+
use std::time::Duration;
2322

2423
#[tokio::main]
2524
async fn main() -> Result<(), GooseError> {

examples/simple_closure.rs

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
//! limitations under the License.
1818
1919
use goose::prelude::*;
20-
2120
use std::boxed::Box;
2221
use std::sync::Arc;
2322
use std::time::Duration;

examples/simple_with_session.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
//! See the License for the specific language governing permissions and
1818
//! limitations under the License.
1919
20-
use std::time::Duration;
21-
2220
use goose::prelude::*;
2321
use serde::Deserialize;
22+
use std::time::Duration;
2423

2524
struct Session {
2625
jwt_token: String,

examples/umami/main.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ mod common;
33
mod english;
44
mod spanish;
55

6-
use std::time::Duration;
7-
86
use goose::prelude::*;
7+
use std::time::Duration;
98

109
use crate::admin::*;
1110
use crate::english::*;
@@ -73,7 +72,7 @@ async fn main() -> Result<(), GooseError> {
7372
.register_taskset(
7473
taskset!("Admin user")
7574
.set_weight(1)?
76-
.set_wait_time(Duration::from_secs(0), Duration::from_secs(3))?
75+
.set_wait_time(Duration::from_secs(3), Duration::from_secs(10))?
7776
.register_task(task!(log_in).set_on_start().set_name("auth /en/user/login"))
7877
.register_task(task!(front_page_en).set_name("auth /").set_weight(2)?)
7978
.register_task(task!(article_listing_en).set_name("auth /en/articles/"))

src/goose.rs

+14-13
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,17 @@
5555
//!
5656
//! ### Task Set Wait Time
5757
//!
58-
//! Wait time is specified as a low-high duration range. Each time a task completes in
59-
//! the task set, the user will pause for a random number of milliseconds inclusively between
58+
//! Wait time is specified as a low-high Duration range. Each time a task completes in the
59+
//! task set, the user will pause for a random number of milliseconds inclusively between
6060
//! the low and high wait times. In the following example, users loading `foo` tasks will
61-
//! sleep 0 to 3 seconds after each task completes, and users loading `bar` tasks will
61+
//! sleep 0 to 2.5 seconds after each task completes, and users loading `bar` tasks will
6262
//! sleep 5 to 10 seconds after each task completes.
6363
//!
6464
//! ```rust
6565
//! use goose::prelude::*;
6666
//! use std::time::Duration;
6767
//!
68-
//! let mut foo_tasks = taskset!("FooTasks").set_wait_time(Duration::from_secs(0), Duration::from_secs(3)).unwrap();
68+
//! let mut foo_tasks = taskset!("FooTasks").set_wait_time(Duration::from_secs(0), Duration::from_millis(2500)).unwrap();
6969
//! let mut bar_tasks = taskset!("BarTasks").set_wait_time(Duration::from_secs(5), Duration::from_secs(10)).unwrap();
7070
//! ```
7171
//! ## Creating Tasks
@@ -472,7 +472,8 @@ pub struct GooseTaskSet {
472472
pub task_sets_index: usize,
473473
/// An integer value that controls the frequency that this task set will be assigned to a user.
474474
pub weight: usize,
475-
/// An range of duration indicating the interval a user will sleep after running a task.
475+
/// A [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) range defining the
476+
/// minimum and maximum time a [`GooseUser`] should sleep after running a task.
476477
pub task_wait: Option<(Duration, Duration)>,
477478
/// A vector containing one copy of each [`GooseTask`](./struct.GooseTask.html) that will
478479
/// run by users running this task set.
@@ -591,9 +592,8 @@ impl GooseTaskSet {
591592
self
592593
}
593594

594-
/// Configure a duration per task_set to pause after running each task. The length of the pause will be randomly
595-
/// selected from `min_wait` to `max_wait` inclusively. For example, if `min_wait` is `Duration::from_secs(0)` and
596-
/// `max_wait` is `Duration::from_secs(2)`, the user will randomly sleep between 0 and 2_000 milliseconds after each task completes.
595+
/// Configure a task_set to to pause after running each task. The length of the pause will be randomly
596+
/// selected from `min_wait` to `max_wait` inclusively.
597597
///
598598
/// # Example
599599
/// ```rust
@@ -613,12 +613,12 @@ impl GooseTaskSet {
613613
max_wait: Duration,
614614
) -> Result<Self, GooseError> {
615615
trace!(
616-
"{} set_wait time: min: {}ms max: {}ms",
616+
"{} set_wait time: min: {:?} max: {:?}",
617617
self.name,
618-
min_wait.as_millis(),
619-
max_wait.as_millis()
618+
min_wait,
619+
max_wait
620620
);
621-
if min_wait > max_wait {
621+
if min_wait.as_millis() > max_wait.as_millis() {
622622
return Err(GooseError::InvalidWaitTime {
623623
min_wait,
624624
max_wait,
@@ -627,7 +627,7 @@ impl GooseTaskSet {
627627
.to_string(),
628628
});
629629
}
630-
self.task_wait.replace((min_wait, max_wait));
630+
self.task_wait = Some((min_wait, max_wait));
631631

632632
Ok(self)
633633
}
@@ -2714,6 +2714,7 @@ mod tests {
27142714
assert_eq!(task_set.tasks.len(), 3);
27152715
assert_eq!(task_set.weighted_tasks.len(), 0);
27162716
assert_eq!(task_set.task_sets_index, usize::max_value());
2717+
assert_eq!(task_set.task_wait, None);
27172718

27182719
// Host field can be changed.
27192720
task_set = task_set.set_host("https://bar.example.com/");

src/lib.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@
8484
//! helper, for example to set a timeout on this specific request:
8585
//!
8686
//! ```rust
87-
//! use std::time;
87+
//! use std::time::Duration;
8888
//!
8989
//! use goose::prelude::*;
9090
//!
9191
//! async fn loadtest_bar(user: &mut GooseUser) -> GooseTaskResult {
9292
//! let request_builder = user.goose_get("/path/to/bar")?;
93-
//! let _goose = user.goose_send(request_builder.timeout(time::Duration::from_secs(3)), None).await?;
93+
//! let _goose = user.goose_send(request_builder.timeout(Duration::from_secs(3)), None).await?;
9494
//!
9595
//! Ok(())
9696
//! }
@@ -468,8 +468,8 @@ use std::sync::{
468468
atomic::{AtomicBool, AtomicUsize, Ordering},
469469
Arc,
470470
};
471-
use std::time::Duration;
472-
use std::{fmt, io, time};
471+
use std::time::{self, Duration};
472+
use std::{fmt, io};
473473
use tokio::fs::File;
474474

475475
use crate::config::{GooseConfiguration, GooseDefaults};
@@ -1805,9 +1805,9 @@ impl GooseAttack {
18051805
{
18061806
let sleep_delay = self.configuration.running_metrics.unwrap() * 1_000;
18071807
goose_attack_run_state.spawn_user_in_ms -= sleep_delay;
1808-
tokio::time::Duration::from_millis(sleep_delay as u64)
1808+
Duration::from_millis(sleep_delay as u64)
18091809
} else {
1810-
tokio::time::Duration::from_millis(goose_attack_run_state.spawn_user_in_ms as u64)
1810+
Duration::from_millis(goose_attack_run_state.spawn_user_in_ms as u64)
18111811
};
18121812
debug!("sleeping {:?}...", sleep_duration);
18131813
goose_attack_run_state.drift_timer =
@@ -1817,7 +1817,7 @@ impl GooseAttack {
18171817
// If enough users have been spawned, move onto the next attack phase.
18181818
if self.weighted_users.is_empty() {
18191819
// Pause a tenth of a second waiting for the final user to fully start up.
1820-
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1820+
tokio::time::sleep(Duration::from_millis(100)).await;
18211821

18221822
if self.attack_mode == AttackMode::Worker {
18231823
info!(
@@ -2050,7 +2050,7 @@ impl GooseAttack {
20502050
if self.configuration.no_autostart {
20512051
// Sleep then check for further instructions.
20522052
if goose_attack_run_state.idle_status_displayed {
2053-
let sleep_duration = tokio::time::Duration::from_millis(250);
2053+
let sleep_duration = Duration::from_millis(250);
20542054
debug!("sleeping {:?}...", sleep_duration);
20552055
goose_attack_run_state.drift_timer = util::sleep_minus_drift(
20562056
sleep_duration,

src/user.rs

+85-59
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
use futures::future::Fuse;
2-
use futures::{pin_mut, select, FutureExt};
31
use rand::Rng;
4-
use std::time::{self, Duration, Instant};
2+
use std::time::{self, Duration};
53

64
use crate::get_worker_id;
75
use crate::goose::{GooseTaskFunction, GooseTaskSet, GooseUser, GooseUserCommand};
@@ -10,7 +8,7 @@ use crate::metrics::{GooseMetric, GooseTaskMetric};
108

119
pub(crate) async fn user_main(
1210
thread_number: usize,
13-
mut thread_task_set: GooseTaskSet,
11+
thread_task_set: GooseTaskSet,
1412
mut thread_user: GooseUser,
1513
thread_receiver: flume::Receiver<GooseUserCommand>,
1614
worker: bool,
@@ -52,64 +50,73 @@ pub(crate) async fn user_main(
5250

5351
// If normal tasks are defined, loop launching tasks until parent tells us to stop.
5452
if !thread_task_set.weighted_tasks.is_empty() {
55-
let mut task_iter = thread_task_set.weighted_tasks.iter().cycle();
56-
let next_task_delay = Fuse::terminated();
57-
pin_mut!(next_task_delay);
58-
59-
let task_wait = match thread_task_set.task_wait.take() {
60-
Some((min, max)) if min == max => min,
61-
Some((min, max)) => Duration::from_millis(
62-
rand::thread_rng().gen_range(min.as_millis()..max.as_millis()) as u64,
63-
),
64-
None => Duration::from_millis(0),
65-
};
66-
67-
next_task_delay.set(tokio::time::sleep(Duration::from_secs(0)).fuse());
68-
loop {
69-
select! {
70-
_ = next_task_delay => {
71-
let (thread_task_index, thread_task_name) = task_iter.next().unwrap();
72-
if *thread_task_index == 0 {
73-
// Tracks the time it takes to loop through all GooseTasks when Coordinated Omission
74-
// Mitigation is enabled.
75-
thread_user.update_request_cadence(thread_number).await;
76-
}
53+
// When there is a delay between tasks, wake every second to check for messages.
54+
let one_second = Duration::from_secs(1);
55+
56+
'launch_tasks: loop {
57+
// Tracks the time it takes to loop through all GooseTasks when Coordinated Omission
58+
// Mitigation is enabled.
59+
thread_user.update_request_cadence(thread_number).await;
60+
61+
for (thread_task_index, thread_task_name) in &thread_task_set.weighted_tasks {
62+
// Determine which task we're going to run next.
63+
let function = &thread_task_set.tasks[*thread_task_index].function;
64+
debug!(
65+
"launching on_start {} task from {}",
66+
thread_task_name, thread_task_set.name
67+
);
68+
// Invoke the task function.
69+
let _todo = invoke_task_function(
70+
function,
71+
&mut thread_user,
72+
*thread_task_index,
73+
thread_task_name,
74+
)
75+
.await;
76+
77+
if received_exit(&thread_receiver) {
78+
break 'launch_tasks;
79+
}
7780

78-
// Get a reference to the task function we're going to invoke next.
79-
let function = &thread_task_set.tasks[*thread_task_index].function;
80-
debug!(
81-
"launching on_start {} task from {}",
82-
thread_task_name, thread_task_set.name
83-
);
84-
85-
let now = Instant::now();
86-
// Invoke the task function.
87-
let _ = invoke_task_function(
88-
function,
89-
&mut thread_user,
90-
*thread_task_index,
91-
thread_task_name,
92-
)
93-
.await;
94-
95-
let elapsed = now.elapsed();
96-
97-
if elapsed < task_wait {
98-
next_task_delay.set(tokio::time::sleep(task_wait - elapsed).fuse());
99-
} else {
100-
next_task_delay.set(tokio::time::sleep(Duration::from_millis(0)).fuse());
101-
}
102-
},
103-
message = thread_receiver.recv_async().fuse() => {
104-
match message {
105-
// Time to exit, break out of launch_tasks loop.
106-
Err(_) | Ok(GooseUserCommand::Exit) => {
107-
break ;
108-
}
109-
Ok(command) => {
110-
debug!("ignoring unexpected GooseUserCommand: {:?}", command);
81+
// If the task_wait is defined, wait for a random time between tasks.
82+
if let Some((min, max)) = thread_task_set.task_wait {
83+
let wait_time = rand::thread_rng().gen_range(min..max).as_millis();
84+
// Counter to track how long we've slept, waking regularly to check for messages.
85+
let mut slept: u128 = 0;
86+
// Wake every second to check if the parent thread has told us to exit.
87+
let mut in_sleep_loop = true;
88+
// Track the time slept for Coordinated Omission Mitigation.
89+
let sleep_timer = time::Instant::now();
90+
91+
while in_sleep_loop {
92+
if received_exit(&thread_receiver) {
93+
break 'launch_tasks;
11194
}
95+
96+
let sleep_duration = if wait_time - slept >= 1000 {
97+
slept += 1000;
98+
if slept >= wait_time {
99+
// Break out of sleep loop after next sleep.
100+
in_sleep_loop = false;
101+
}
102+
one_second
103+
} else {
104+
slept += wait_time;
105+
// Break out of sleep loop after next sleep.
106+
in_sleep_loop = false;
107+
Duration::from_millis((wait_time - slept) as u64)
108+
};
109+
110+
debug!(
111+
"user {} from {} sleeping {:?} ...",
112+
thread_number, thread_task_set.name, sleep_duration
113+
);
114+
115+
tokio::time::sleep(sleep_duration).await;
112116
}
117+
// Track how much time the GooseUser sleeps during this loop through all GooseTasks,
118+
// used by Coordinated Omission Mitigation.
119+
thread_user.slept += (time::Instant::now() - sleep_timer).as_millis() as u64;
113120
}
114121
}
115122
}
@@ -152,6 +159,25 @@ pub(crate) async fn user_main(
152159
}
153160
}
154161

162+
// Determine if the parent has sent a GooseUserCommand::Exit message.
163+
fn received_exit(thread_receiver: &flume::Receiver<GooseUserCommand>) -> bool {
164+
let mut message = thread_receiver.try_recv();
165+
while message.is_ok() {
166+
match message.unwrap() {
167+
// GooseUserCommand::Exit received.
168+
GooseUserCommand::Exit => {
169+
return true;
170+
}
171+
command => {
172+
debug!("ignoring unexpected GooseUserCommand: {:?}", command);
173+
}
174+
}
175+
message = thread_receiver.try_recv();
176+
}
177+
// GooseUserCommand::Exit not received.
178+
false
179+
}
180+
155181
// Invoke the task function, collecting task metrics.
156182
async fn invoke_task_function(
157183
function: &GooseTaskFunction,

src/util.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ pub fn parse_timespan(time_str: &str) -> usize {
8282
///
8383
/// // Do other stuff, in this case sleep 250 milliseconds. This is
8484
/// // the "drift" that will be subtracted from the sleep time later.
85-
/// tokio::time::sleep(tokio::time::Duration::from_millis(250));
85+
/// tokio::time::sleep(std::time::Duration::from_millis(250));
8686
///
8787
/// // Sleep for 1 second minus the time spent doing other stuff.
8888
/// drift_timer = util::sleep_minus_drift(
89-
/// tokio::time::Duration::from_secs(1),
89+
/// std::time::Duration::from_secs(1),
9090
/// drift_timer,
9191
/// ).await;
9292
///
@@ -98,7 +98,7 @@ pub fn parse_timespan(time_str: &str) -> usize {
9898
/// }
9999
/// ```
100100
pub async fn sleep_minus_drift(
101-
duration: tokio::time::Duration,
101+
duration: std::time::Duration,
102102
drift: tokio::time::Instant,
103103
) -> tokio::time::Instant {
104104
match duration.checked_sub(drift.elapsed()) {

0 commit comments

Comments
 (0)