use crate::cli::RampPattern;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct RampUpConfig {
pub duration: Duration,
pub pattern: RampPattern,
pub max_concurrent: usize,
}
impl RampUpConfig {
pub fn new(duration: Duration, pattern: RampPattern, max_concurrent: usize) -> Self {
Self {
duration,
pattern,
max_concurrent,
}
}
pub fn current_concurrency(&self, start_time: Instant) -> usize {
let elapsed = start_time.elapsed();
if elapsed >= self.duration {
return self.max_concurrent;
}
let progress = elapsed.as_secs_f64() / self.duration.as_secs_f64();
let normalized_progress = progress.clamp(0.0, 1.0);
let concurrency_ratio = match self.pattern {
RampPattern::Linear => normalized_progress,
RampPattern::Exponential => normalized_progress.powf(2.0),
RampPattern::Step => {
if normalized_progress < 0.25 {
0.25
} else if normalized_progress < 0.50 {
0.50
} else if normalized_progress < 0.75 {
0.75
} else {
1.0
}
}
};
let current = (self.max_concurrent as f64 * concurrency_ratio).ceil() as usize;
current.max(1).min(self.max_concurrent)
}
pub fn is_ramping(&self, start_time: Instant) -> bool {
start_time.elapsed() < self.duration
}
pub fn description(&self) -> String {
match self.pattern {
RampPattern::Linear => format!("Linear ramp-up over {}s", self.duration.as_secs()),
RampPattern::Exponential => {
format!("Exponential ramp-up over {}s", self.duration.as_secs())
}
RampPattern::Step => format!("Step ramp-up over {}s", self.duration.as_secs()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear_ramp_up() {
let config = RampUpConfig::new(Duration::from_secs(4), RampPattern::Linear, 10);
let start = Instant::now();
assert_eq!(config.current_concurrency(start), 1);
let mid_start = Instant::now() - Duration::from_secs(2);
let mid_concurrency = config.current_concurrency(mid_start);
assert!((5..=6).contains(&mid_concurrency));
let end_start = Instant::now() - Duration::from_secs(5);
assert_eq!(config.current_concurrency(end_start), 10);
}
#[test]
fn test_step_ramp_up() {
let config = RampUpConfig::new(Duration::from_secs(4), RampPattern::Step, 12);
let start_25 = Instant::now() - Duration::from_secs(1); let conc_25 = config.current_concurrency(start_25);
assert_eq!(conc_25, 6);
let start_24 = Instant::now() - Duration::from_millis(900); let conc_24 = config.current_concurrency(start_24);
assert_eq!(conc_24, 3);
let start_50 = Instant::now() - Duration::from_secs(2); let conc_50 = config.current_concurrency(start_50);
assert_eq!(conc_50, 9);
let start_75 = Instant::now() - Duration::from_secs(3); let conc_75 = config.current_concurrency(start_75);
assert_eq!(conc_75, 12);
let start_100 = Instant::now() - Duration::from_secs(4); assert_eq!(config.current_concurrency(start_100), 12); }
#[test]
fn test_ramp_up_completion() {
let config = RampUpConfig::new(Duration::from_secs(2), RampPattern::Linear, 10);
let start = Instant::now();
assert!(config.is_ramping(start));
let completed = Instant::now() - Duration::from_secs(3);
assert!(!config.is_ramping(completed));
assert_eq!(config.current_concurrency(completed), 10);
}
}