1use crate::rng::SimRng;
4use crate::time::Duration;
5
6pub trait LatencyModel {
8 fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration;
10}
11
12#[derive(Debug, Clone, Copy, Default)]
16pub struct FixedLatency;
17
18impl LatencyModel for FixedLatency {
19 fn compute(&mut self, base_latency: Duration, _rng: &mut SimRng) -> Duration {
20 base_latency
21 }
22}
23
24#[derive(Debug, Clone, Copy)]
29pub struct UniformJitter {
30 pub max_jitter: Duration,
32}
33
34impl UniformJitter {
35 pub fn new(max_jitter: Duration) -> Self {
37 UniformJitter { max_jitter }
38 }
39}
40
41impl LatencyModel for UniformJitter {
42 fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
43 rng.duration_with_jitter(base_latency, self.max_jitter)
44 }
45}
46
47#[derive(Debug, Clone, Copy)]
52pub struct PercentageJitter {
53 pub fraction: f64,
55}
56
57impl PercentageJitter {
58 pub fn new(percent: f64) -> Self {
63 PercentageJitter {
64 fraction: percent / 100.0,
65 }
66 }
67
68 pub fn from_fraction(fraction: f64) -> Self {
70 PercentageJitter { fraction }
71 }
72}
73
74impl LatencyModel for PercentageJitter {
75 fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
76 let max_jitter_nanos = (base_latency.as_nanos() as f64 * self.fraction) as u64;
77 let max_jitter = Duration::from_nanos(max_jitter_nanos);
78 rng.duration_with_jitter(base_latency, max_jitter)
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
84pub struct OverheadPlusJitter {
85 pub overhead: Duration,
87 pub max_jitter: Duration,
89}
90
91impl OverheadPlusJitter {
92 pub fn new(overhead: Duration, max_jitter: Duration) -> Self {
94 OverheadPlusJitter {
95 overhead,
96 max_jitter,
97 }
98 }
99}
100
101impl LatencyModel for OverheadPlusJitter {
102 fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
103 let base_with_overhead = base_latency.saturating_add(self.overhead);
104 rng.duration_with_jitter(base_with_overhead, self.max_jitter)
105 }
106}
107
108#[derive(Debug, Clone, Copy)]
115pub struct SpikyLatency<L: LatencyModel> {
116 pub inner: L,
118 pub spike_probability: f64,
120 pub spike_max: Duration,
122}
123
124impl<L: LatencyModel> SpikyLatency<L> {
125 pub fn new(inner: L, spike_probability: f64, spike_max: Duration) -> Self {
127 SpikyLatency {
128 inner,
129 spike_probability,
130 spike_max,
131 }
132 }
133}
134
135impl<L: LatencyModel> LatencyModel for SpikyLatency<L> {
136 fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
137 let base = self.inner.compute(base_latency, rng);
138 if self.spike_probability > 0.0
139 && self.spike_max.as_nanos() > 0
140 && rng.bool(self.spike_probability)
141 {
142 let spike_nanos = rng.u64(self.spike_max.as_nanos() + 1);
143 base.saturating_add(Duration::from_nanos(spike_nanos))
144 } else {
145 base
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn fixed_latency() {
156 let mut model = FixedLatency;
157 let mut rng = SimRng::new(42);
158 let base = Duration::from_millis(100);
159
160 for _ in 0..100 {
161 assert_eq!(model.compute(base, &mut rng), base);
162 }
163 }
164
165 #[test]
166 fn uniform_jitter_bounds() {
167 let mut model = UniformJitter::new(Duration::from_millis(10));
168 let mut rng = SimRng::new(42);
169 let base = Duration::from_millis(100);
170
171 for _ in 0..1000 {
172 let latency = model.compute(base, &mut rng);
173 assert!(latency.as_millis() >= 90);
174 assert!(latency.as_millis() <= 110);
175 }
176 }
177
178 #[test]
179 fn uniform_jitter_clamps_negative() {
180 let mut model = UniformJitter::new(Duration::from_millis(100));
181 let mut rng = SimRng::new(42);
182 let base = Duration::from_millis(10); for _ in 0..1000 {
185 let latency = model.compute(base, &mut rng);
186 let _ = latency.as_nanos();
189 }
190 }
191
192 #[test]
193 fn percentage_jitter() {
194 let mut model = PercentageJitter::new(10.0); let mut rng = SimRng::new(42);
196 let base = Duration::from_millis(100);
197
198 for _ in 0..1000 {
199 let latency = model.compute(base, &mut rng);
200 assert!(latency.as_millis() >= 90);
201 assert!(latency.as_millis() <= 110);
202 }
203 }
204
205 #[test]
206 fn overhead_plus_jitter() {
207 let mut model = OverheadPlusJitter::new(Duration::from_millis(5), Duration::from_millis(2));
208 let mut rng = SimRng::new(42);
209 let base = Duration::from_millis(100);
210
211 for _ in 0..1000 {
212 let latency = model.compute(base, &mut rng);
213 assert!(latency.as_millis() >= 103);
215 assert!(latency.as_millis() <= 107);
216 }
217 }
218
219 #[test]
220 fn deterministic_jitter() {
221 let mut model1 = UniformJitter::new(Duration::from_millis(10));
222 let mut model2 = UniformJitter::new(Duration::from_millis(10));
223 let mut rng1 = SimRng::new(42);
224 let mut rng2 = SimRng::new(42);
225 let base = Duration::from_millis(100);
226
227 for _ in 0..100 {
228 assert_eq!(
229 model1.compute(base, &mut rng1),
230 model2.compute(base, &mut rng2)
231 );
232 }
233 }
234}