1use parking_lot::Mutex;
7use rand::SeedableRng;
8use rand::rngs::StdRng;
9use rusty_common::collections::FxHashMap;
10use std::sync::Arc;
11
12pub trait LatencyModel: Send + Sync {
14 fn get_latency_ns(&self) -> u64;
16
17 fn avg_latency_ns(&self) -> u64;
19
20 fn clone_box(&self) -> Box<dyn LatencyModel>;
22}
23
24#[derive(Debug, Clone)]
26pub struct FixedLatency {
27 latency_ns: u64,
28}
29
30impl FixedLatency {
31 #[must_use]
33 pub const fn new(latency_ns: u64) -> Self {
34 Self { latency_ns }
35 }
36
37 #[must_use]
39 pub const fn from_micros(latency_us: u64) -> Self {
40 Self {
41 latency_ns: latency_us * 1000,
42 }
43 }
44
45 #[must_use]
47 pub const fn from_millis(latency_ms: u64) -> Self {
48 Self {
49 latency_ns: latency_ms * 1_000_000,
50 }
51 }
52}
53
54impl LatencyModel for FixedLatency {
55 fn get_latency_ns(&self) -> u64 {
56 self.latency_ns
57 }
58
59 fn avg_latency_ns(&self) -> u64 {
60 self.latency_ns
61 }
62
63 fn clone_box(&self) -> Box<dyn LatencyModel> {
64 Box::new(self.clone())
65 }
66}
67
68pub struct GaussianLatency {
70 mean_ns: u64,
71 std_dev_ns: u64,
72 rng: Arc<Mutex<StdRng>>,
73}
74
75impl GaussianLatency {
76 #[must_use]
78 pub fn new(mean_ns: u64, std_dev_ns: u64) -> Self {
79 Self {
80 mean_ns,
81 std_dev_ns,
82 rng: Arc::new(Mutex::new(StdRng::from_seed([0; 32]))),
83 }
84 }
85
86 #[must_use]
88 pub fn from_micros(mean_us: u64, std_dev_us: u64) -> Self {
89 Self::new(mean_us * 1000, std_dev_us * 1000)
90 }
91
92 #[must_use]
94 pub fn with_seed(mean_ns: u64, std_dev_ns: u64, seed: u64) -> Self {
95 Self {
96 mean_ns,
97 std_dev_ns,
98 rng: Arc::new(Mutex::new(StdRng::seed_from_u64(seed))),
99 }
100 }
101
102 fn sample_normal(&self, rng: &mut StdRng) -> f64 {
104 use rand::Rng;
105 use std::f64::consts::PI;
106
107 let u1: f64 = rng.random();
108 let u2: f64 = rng.random();
109
110 let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos();
111 self.mean_ns as f64 + z0 * self.std_dev_ns as f64
112 }
113}
114
115impl LatencyModel for GaussianLatency {
116 fn get_latency_ns(&self) -> u64 {
117 let mut rng = self.rng.lock();
118 let sample = self.sample_normal(&mut rng);
119 sample.max(0.0) as u64
121 }
122
123 fn avg_latency_ns(&self) -> u64 {
124 self.mean_ns
125 }
126
127 fn clone_box(&self) -> Box<dyn LatencyModel> {
128 Box::new(GaussianLatency {
129 mean_ns: self.mean_ns,
130 std_dev_ns: self.std_dev_ns,
131 rng: Arc::new(Mutex::new(StdRng::from_seed([0; 32]))),
132 })
133 }
134}
135
136pub struct UniformLatency {
138 min_ns: u64,
139 max_ns: u64,
140 rng: Arc<Mutex<StdRng>>,
141}
142
143impl UniformLatency {
144 #[must_use]
146 pub fn new(min_ns: u64, max_ns: u64) -> Self {
147 Self {
148 min_ns,
149 max_ns,
150 rng: Arc::new(Mutex::new(StdRng::from_seed([0; 32]))),
151 }
152 }
153
154 #[must_use]
156 pub fn from_micros(min_us: u64, max_us: u64) -> Self {
157 Self::new(min_us * 1000, max_us * 1000)
158 }
159
160 #[must_use]
162 pub fn with_seed(min_ns: u64, max_ns: u64, seed: u64) -> Self {
163 Self {
164 min_ns,
165 max_ns,
166 rng: Arc::new(Mutex::new(StdRng::seed_from_u64(seed))),
167 }
168 }
169}
170
171impl LatencyModel for UniformLatency {
172 fn get_latency_ns(&self) -> u64 {
173 use rand::Rng;
174 let mut rng = self.rng.lock();
175 rng.random_range(self.min_ns..=self.max_ns)
176 }
177
178 fn avg_latency_ns(&self) -> u64 {
179 (self.min_ns + self.max_ns) / 2
180 }
181
182 fn clone_box(&self) -> Box<dyn LatencyModel> {
183 Box::new(UniformLatency {
184 min_ns: self.min_ns,
185 max_ns: self.max_ns,
186 rng: Arc::new(Mutex::new(StdRng::from_seed([0; 32]))),
187 })
188 }
189}
190
191pub struct BimodalLatency {
193 fast_latency: Box<dyn LatencyModel>,
194 slow_latency: Box<dyn LatencyModel>,
195 fast_probability: f64,
196 rng: Arc<Mutex<StdRng>>,
197}
198
199impl BimodalLatency {
200 #[must_use]
202 pub fn new(
203 fast_latency: Box<dyn LatencyModel>,
204 slow_latency: Box<dyn LatencyModel>,
205 fast_probability: f64,
206 ) -> Self {
207 Self {
208 fast_latency,
209 slow_latency,
210 fast_probability,
211 rng: Arc::new(Mutex::new(StdRng::from_seed([0; 32]))),
212 }
213 }
214
215 pub fn with_seed(
217 fast_latency: Box<dyn LatencyModel>,
218 slow_latency: Box<dyn LatencyModel>,
219 fast_probability: f64,
220 seed: u64,
221 ) -> Self {
222 Self {
223 fast_latency,
224 slow_latency,
225 fast_probability,
226 rng: Arc::new(Mutex::new(StdRng::seed_from_u64(seed))),
227 }
228 }
229}
230
231impl LatencyModel for BimodalLatency {
232 fn get_latency_ns(&self) -> u64 {
233 use rand::Rng;
234 let mut rng = self.rng.lock();
235 if rng.random::<f64>() < self.fast_probability {
236 self.fast_latency.get_latency_ns()
237 } else {
238 self.slow_latency.get_latency_ns()
239 }
240 }
241
242 fn avg_latency_ns(&self) -> u64 {
243 let fast_avg = self.fast_latency.avg_latency_ns() as f64;
244 let slow_avg = self.slow_latency.avg_latency_ns() as f64;
245 (fast_avg * self.fast_probability + slow_avg * (1.0 - self.fast_probability)) as u64
246 }
247
248 fn clone_box(&self) -> Box<dyn LatencyModel> {
249 Box::new(BimodalLatency {
250 fast_latency: self.fast_latency.clone_box(),
251 slow_latency: self.slow_latency.clone_box(),
252 fast_probability: self.fast_probability,
253 rng: Arc::new(Mutex::new(StdRng::from_seed([0; 32]))),
254 })
255 }
256}
257
258pub struct AssetLatency {
260 default_model: Box<dyn LatencyModel>,
261 asset_models: Arc<Mutex<FxHashMap<String, Box<dyn LatencyModel>>>>,
262}
263
264impl AssetLatency {
265 #[must_use]
267 pub fn new(default_model: Box<dyn LatencyModel>) -> Self {
268 Self {
269 default_model,
270 asset_models: Arc::new(Mutex::new(FxHashMap::default())),
271 }
272 }
273
274 pub fn add_asset(&self, symbol: String, model: Box<dyn LatencyModel>) {
276 self.asset_models.lock().insert(symbol, model);
277 }
278 pub fn get_latency_for_asset(&self, symbol: &str) -> u64 {
280 let models = self.asset_models.lock();
281 if let Some(model) = models.get(symbol) {
282 model.get_latency_ns()
283 } else {
284 self.default_model.get_latency_ns()
285 }
286 }
287}
288
289impl LatencyModel for AssetLatency {
290 fn get_latency_ns(&self) -> u64 {
291 self.default_model.get_latency_ns()
292 }
293
294 fn avg_latency_ns(&self) -> u64 {
295 self.default_model.avg_latency_ns()
296 }
297
298 fn clone_box(&self) -> Box<dyn LatencyModel> {
299 let new_models = Arc::new(Mutex::new(FxHashMap::default()));
300 let models = self.asset_models.lock();
301 for (symbol, model) in models.iter() {
302 new_models.lock().insert(symbol.clone(), model.clone_box());
303 }
304
305 Box::new(AssetLatency {
306 default_model: self.default_model.clone_box(),
307 asset_models: new_models,
308 })
309 }
310}
311
312pub mod presets {
314 use super::*;
315
316 #[must_use]
318 pub fn colocation() -> Box<dyn LatencyModel> {
319 Box::new(GaussianLatency::new(50_000, 10_000)) }
321
322 #[must_use]
324 pub fn datacenter() -> Box<dyn LatencyModel> {
325 Box::new(GaussianLatency::new(250_000, 50_000)) }
327
328 #[must_use]
330 pub fn cross_region() -> Box<dyn LatencyModel> {
331 Box::new(GaussianLatency::new(5_000_000, 1_000_000)) }
333
334 #[must_use]
336 pub fn internet() -> Box<dyn LatencyModel> {
337 Box::new(BimodalLatency::new(
338 Box::new(GaussianLatency::new(20_000_000, 5_000_000)), Box::new(GaussianLatency::new(80_000_000, 20_000_000)), 0.8, ))
342 }
343
344 #[must_use]
346 pub fn exchange_realistic() -> Box<dyn LatencyModel> {
347 Box::new(BimodalLatency::new(
348 Box::new(UniformLatency::new(100_000, 500_000)), Box::new(GaussianLatency::new(2_000_000, 500_000)), 0.95, ))
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_fixed_latency() {
361 let model = FixedLatency::from_micros(100);
362 assert_eq!(model.get_latency_ns(), 100_000);
363 assert_eq!(model.avg_latency_ns(), 100_000);
364 }
365
366 #[test]
367 fn test_gaussian_latency() {
368 let model = GaussianLatency::with_seed(1_000_000, 100_000, 42);
369 let samples: Vec<u64> = (0..1000).map(|_| model.get_latency_ns()).collect();
370
371 let avg: u64 = samples.iter().sum::<u64>() / samples.len() as u64;
373 assert!(avg > 800_000 && avg < 1_200_000); }
375
376 #[test]
377 fn test_bimodal_latency() {
378 let fast = Box::new(FixedLatency::new(100_000));
379 let slow = Box::new(FixedLatency::new(1_000_000));
380 let model = BimodalLatency::with_seed(fast, slow, 0.8, 42);
381
382 let samples: Vec<u64> = (0..1000).map(|_| model.get_latency_ns()).collect();
383 let fast_count = samples.iter().filter(|&&x| x == 100_000).count();
384 let slow_count = samples.iter().filter(|&&x| x == 1_000_000).count();
385
386 assert_eq!(fast_count + slow_count, 1000);
387 assert!(fast_count > 700 && fast_count < 900);
389 }
390}