mockforge_chaos/
latency.rs1use crate::config::LatencyConfig;
4use rand::Rng;
5use std::time::Duration;
6use tokio::time::sleep;
7use tracing::debug;
8
9#[derive(Clone)]
11pub struct LatencyInjector {
12 config: LatencyConfig,
13}
14
15impl LatencyInjector {
16 pub fn new(config: LatencyConfig) -> Self {
18 Self { config }
19 }
20
21 pub fn is_enabled(&self) -> bool {
23 self.config.enabled
24 }
25
26 pub async fn inject(&self) -> u64 {
29 if !self.config.enabled {
30 return 0;
31 }
32
33 let mut rng = rand::rng();
35 if rng.random::<f64>() > self.config.probability {
36 return 0;
37 }
38
39 let delay_ms = self.calculate_delay();
40 if delay_ms > 0 {
41 debug!("Injecting latency: {}ms", delay_ms);
42 sleep(Duration::from_millis(delay_ms)).await;
43 }
44
45 delay_ms
46 }
47
48 fn calculate_delay(&self) -> u64 {
50 let mut rng = rand::rng();
51
52 let base_delay = if let Some(fixed) = self.config.fixed_delay_ms {
54 fixed
55 } else if let Some((min, max)) = self.config.random_delay_range_ms {
56 rng.random_range(min..=max)
57 } else {
58 0
59 };
60
61 if self.config.jitter_percent > 0.0 {
63 let jitter = (base_delay as f64 * self.config.jitter_percent / 100.0) as u64;
64 let jitter_offset = rng.random_range(0..=jitter);
65 if rng.random_bool(0.5) {
66 base_delay + jitter_offset
67 } else {
68 base_delay.saturating_sub(jitter_offset)
69 }
70 } else {
71 base_delay
72 }
73 }
74
75 pub fn config(&self) -> &LatencyConfig {
77 &self.config
78 }
79
80 pub fn update_config(&mut self, config: LatencyConfig) {
82 self.config = config;
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_calculate_fixed_delay() {
92 let config = LatencyConfig {
93 enabled: true,
94 fixed_delay_ms: Some(100),
95 random_delay_range_ms: None,
96 jitter_percent: 0.0,
97 probability: 1.0,
98 };
99
100 let injector = LatencyInjector::new(config);
101 let delay = injector.calculate_delay();
102 assert_eq!(delay, 100);
103 }
104
105 #[test]
106 fn test_calculate_random_delay() {
107 let config = LatencyConfig {
108 enabled: true,
109 fixed_delay_ms: None,
110 random_delay_range_ms: Some((50, 150)),
111 jitter_percent: 0.0,
112 probability: 1.0,
113 };
114
115 let injector = LatencyInjector::new(config);
116 for _ in 0..100 {
117 let delay = injector.calculate_delay();
118 assert!((50..=150).contains(&delay));
119 }
120 }
121
122 #[test]
123 fn test_jitter() {
124 let config = LatencyConfig {
125 enabled: true,
126 fixed_delay_ms: Some(100),
127 random_delay_range_ms: None,
128 jitter_percent: 10.0, probability: 1.0,
130 };
131
132 let injector = LatencyInjector::new(config);
133 for _ in 0..100 {
134 let delay = injector.calculate_delay();
135 assert!((90..=110).contains(&delay));
137 }
138 }
139
140 #[tokio::test]
141 async fn test_inject_latency() {
142 let config = LatencyConfig {
143 enabled: true,
144 fixed_delay_ms: Some(10),
145 random_delay_range_ms: None,
146 jitter_percent: 0.0,
147 probability: 1.0,
148 };
149
150 let injector = LatencyInjector::new(config);
151 let start = std::time::Instant::now();
152 let delay_ms = injector.inject().await;
153 let elapsed = start.elapsed();
154
155 assert!(elapsed >= Duration::from_millis(10));
157 assert_eq!(delay_ms, 10);
159 }
160
161 #[tokio::test]
162 async fn test_probability() {
163 let config = LatencyConfig {
164 enabled: true,
165 fixed_delay_ms: Some(10),
166 random_delay_range_ms: None,
167 jitter_percent: 0.0,
168 probability: 0.0, };
170
171 let injector = LatencyInjector::new(config);
172 let start = std::time::Instant::now();
173 let delay_ms = injector.inject().await;
174 let elapsed = start.elapsed();
175
176 assert!(elapsed < Duration::from_millis(5));
178 assert_eq!(delay_ms, 0);
180 }
181}