use rand::RngExt;
use std::sync::Arc;
use std::time::Duration;
use tropel_core::config::{ExecutionConfig, ThinkTimeConfig};
use tropel_sdk::Result;
pub(crate) fn extract_think_time(exec_cfg: &ExecutionConfig) -> ThinkTimeConfig {
match exec_cfg {
ExecutionConfig::ConstantVus { think_time, .. } => think_time.clone(),
ExecutionConfig::RampingVus { think_time, .. } => think_time.clone(),
ExecutionConfig::ConstantArrivalRate { think_time, .. } => think_time.clone(),
ExecutionConfig::SharedIterations { think_time, .. } => think_time.clone(),
ExecutionConfig::PerVUIterations { think_time, .. } => think_time.clone(),
ExecutionConfig::RampingArrivalRate { think_time, .. } => think_time.clone(),
ExecutionConfig::ExternallyControlled { think_time, .. } => think_time.clone(),
}
}
pub(crate) fn parse_duration_str(s: &str) -> Result<Duration> {
let s = s.trim();
if s.is_empty() || s == "0" || s == "0s" {
return Ok(Duration::ZERO);
}
tropel_sdk::parse_duration(s)
}
pub(crate) async fn apply_think_time(
config: &ThinkTimeConfig,
iter_duration: Option<Duration>,
stop: Option<&Arc<tokio::sync::Notify>>,
) {
async fn interruptible_sleep(dur: Duration, stop: Option<&Arc<tokio::sync::Notify>>) {
match stop {
Some(s) => {
let notified = s.notified();
tokio::pin!(notified);
tokio::select! {
_ = tokio::time::sleep(dur) => {}
_ = notified => {}
}
}
None => {
tokio::time::sleep(dur).await;
}
}
}
if let Some(pacing_str) = &config.iteration_pacing {
match parse_duration_str(pacing_str) {
Ok(pacing) => {
if let Some(actual_dur) = iter_duration {
if actual_dur < pacing {
let remaining = pacing - actual_dur;
if remaining > Duration::from_millis(1) {
interruptible_sleep(remaining, stop).await;
}
}
}
return;
}
Err(e) => {
tracing::warn!(
"Malformed iterationPacing '{}' ({}): ignoring",
pacing_str,
e
);
}
}
}
if let Some(delay_str) = &config.delay {
match parse_duration_str(delay_str) {
Ok(delay) => {
if delay > Duration::from_millis(1) {
interruptible_sleep(delay, stop).await;
return;
}
}
Err(e) => {
tracing::warn!(
"Malformed thinkTime.delay '{}' ({}): using zero think time",
delay_str,
e
);
}
}
}
if let (Some(min_str), Some(max_str)) = (&config.min_delay, &config.max_delay) {
match (parse_duration_str(min_str), parse_duration_str(max_str)) {
(Ok(min), Ok(max)) if max > Duration::ZERO && max > min => {
let range_ms = (max - min).as_millis() as u64;
let rand_ms = rand::rng().random_range(0..=range_ms);
interruptible_sleep(min + Duration::from_millis(rand_ms), stop).await;
}
(Err(e), _) => {
tracing::warn!(
"Malformed thinkTime.minDelay '{}' ({}): ignoring",
min_str,
e
);
}
(_, Err(e)) => {
tracing::warn!(
"Malformed thinkTime.maxDelay '{}' ({}): ignoring",
max_str,
e
);
}
_ => {}
}
}
}