use crate::config::LatencyConfig;
use rand::Rng;
use std::time::Duration;
use tokio::time::sleep;
use tracing::debug;
#[derive(Clone)]
pub struct LatencyInjector {
config: LatencyConfig,
}
impl LatencyInjector {
pub fn new(config: LatencyConfig) -> Self {
Self { config }
}
pub fn is_enabled(&self) -> bool {
self.config.enabled
}
pub async fn inject(&self) -> u64 {
if !self.config.enabled {
return 0;
}
let mut rng = rand::rng();
if rng.random::<f64>() > self.config.probability {
return 0;
}
let delay_ms = self.calculate_delay();
if delay_ms > 0 {
debug!("Injecting latency: {}ms", delay_ms);
sleep(Duration::from_millis(delay_ms)).await;
}
delay_ms
}
fn calculate_delay(&self) -> u64 {
let mut rng = rand::rng();
let base_delay = if let Some(fixed) = self.config.fixed_delay_ms {
fixed
} else if let Some((min, max)) = self.config.random_delay_range_ms {
rng.random_range(min..=max)
} else {
0
};
if self.config.jitter_percent > 0.0 {
let jitter = (base_delay as f64 * self.config.jitter_percent / 100.0) as u64;
let jitter_offset = rng.random_range(0..=jitter);
if rng.random_bool(0.5) {
base_delay + jitter_offset
} else {
base_delay.saturating_sub(jitter_offset)
}
} else {
base_delay
}
}
pub fn config(&self) -> &LatencyConfig {
&self.config
}
pub fn update_config(&mut self, config: LatencyConfig) {
self.config = config;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_fixed_delay() {
let config = LatencyConfig {
enabled: true,
fixed_delay_ms: Some(100),
random_delay_range_ms: None,
jitter_percent: 0.0,
probability: 1.0,
};
let injector = LatencyInjector::new(config);
let delay = injector.calculate_delay();
assert_eq!(delay, 100);
}
#[test]
fn test_calculate_random_delay() {
let config = LatencyConfig {
enabled: true,
fixed_delay_ms: None,
random_delay_range_ms: Some((50, 150)),
jitter_percent: 0.0,
probability: 1.0,
};
let injector = LatencyInjector::new(config);
for _ in 0..100 {
let delay = injector.calculate_delay();
assert!((50..=150).contains(&delay));
}
}
#[test]
fn test_jitter() {
let config = LatencyConfig {
enabled: true,
fixed_delay_ms: Some(100),
random_delay_range_ms: None,
jitter_percent: 10.0, probability: 1.0,
};
let injector = LatencyInjector::new(config);
for _ in 0..100 {
let delay = injector.calculate_delay();
assert!((90..=110).contains(&delay));
}
}
#[tokio::test]
async fn test_inject_latency() {
let config = LatencyConfig {
enabled: true,
fixed_delay_ms: Some(10),
random_delay_range_ms: None,
jitter_percent: 0.0,
probability: 1.0,
};
let injector = LatencyInjector::new(config);
let start = std::time::Instant::now();
let delay_ms = injector.inject().await;
let elapsed = start.elapsed();
assert!(elapsed >= Duration::from_millis(10));
assert_eq!(delay_ms, 10);
}
#[tokio::test]
async fn test_probability() {
let config = LatencyConfig {
enabled: true,
fixed_delay_ms: Some(10),
random_delay_range_ms: None,
jitter_percent: 0.0,
probability: 0.0, };
let injector = LatencyInjector::new(config);
let start = std::time::Instant::now();
let delay_ms = injector.inject().await;
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(5));
assert_eq!(delay_ms, 0);
}
}