1#[cfg(feature = "pdo-model")]
25use crate::model::{HeavyPdoTraceConfig, RepeatedHeavyPdoPatternConfig, TraceHeavyPdoConfig};
26use crate::{
27 model::{BwTraceConfig, RepeatedBwPatternConfig, StaticBwConfig},
28 Bandwidth, BwTrace, Duration,
29};
30
31const MTU_IN_BYTES: u64 = 1500;
32const MTU_IN_BITS: u64 = MTU_IN_BYTES * 8;
33const MTU_PER_MILLIS: Bandwidth = Bandwidth::from_kbps(MTU_IN_BITS);
34const MAHIMAHI_TS_BIN: Duration = Duration::from_millis(1);
35
36macro_rules! saturating_duration_as_millis_u64 {
37 ($duration:expr) => {
38 $duration
39 .as_secs()
40 .saturating_mul(1_000)
41 .saturating_add($duration.subsec_millis() as u64)
42 };
43}
44
45pub trait Mahimahi: BwTrace {
55 fn mahimahi(&mut self, total_dur: &Duration) -> Vec<u64> {
62 let mut timestamp = MAHIMAHI_TS_BIN;
63 let mut v = Vec::new();
64 let mut transfer = Bandwidth::from_bps(0);
65 let mut bin_rem = MAHIMAHI_TS_BIN;
66 while let Some((bw, mut dur)) = self.next_bw() {
67 if timestamp > *total_dur {
68 break;
69 }
70 while (timestamp <= *total_dur) && !dur.is_zero() {
71 let bin = bin_rem.min(dur);
72 bin_rem -= bin;
73 dur -= bin;
74 let bin_factor = bin.as_secs_f64() / MAHIMAHI_TS_BIN.as_secs_f64();
75 transfer += bw.mul_f64(bin_factor);
76 while transfer >= MTU_PER_MILLIS {
77 v.push(saturating_duration_as_millis_u64!(timestamp));
78 transfer -= MTU_PER_MILLIS;
79 }
80 if bin_rem.is_zero() {
81 bin_rem = MAHIMAHI_TS_BIN;
82 timestamp += MAHIMAHI_TS_BIN;
83 }
84 }
85 }
86 v
87 }
88}
89
90impl<T: BwTrace + ?Sized> Mahimahi for T {}
91
92pub trait MahimahiExt: Mahimahi {
94 fn mahimahi_to_string(&mut self, total_dur: &Duration) -> String {
96 let ts = self.mahimahi(total_dur);
97 itertools::join(ts, "\n")
98 }
99
100 fn mahimahi_to_file<P: AsRef<std::path::Path>>(&mut self, total_dur: &Duration, path: P) {
102 let content = self.mahimahi_to_string(total_dur);
103 std::fs::write(path, content).unwrap();
104 }
105}
106
107impl<T: Mahimahi + ?Sized> MahimahiExt for T {}
108
109pub fn load_mahimahi_trace(
125 trace: Vec<u64>,
126 count: Option<usize>,
127) -> Result<RepeatedBwPatternConfig, &'static str> {
128 let mut pattern: Vec<StaticBwConfig> = vec![];
129 let mut insert_into_pattern = |config| {
131 if pattern.is_empty() {
132 pattern.push(config);
133 } else {
134 let last_config = pattern.last_mut().unwrap();
135 if last_config.bw.unwrap() == config.bw.unwrap() {
136 last_config.duration =
137 Some(last_config.duration.unwrap() + config.duration.unwrap());
138 } else {
139 pattern.push(config);
140 }
141 }
142 };
143
144 let mut zeor_ts_cnt = 0; let mut last_ts = 0; let mut last_cnt = 0; for ts in trace {
148 if ts == 0 {
150 zeor_ts_cnt += 1;
151 continue;
152 }
153 match ts.cmp(&last_ts) {
155 std::cmp::Ordering::Less => {
156 return Err("timestamps must be monotonically nondecreasing");
157 }
158 std::cmp::Ordering::Equal => {
159 last_cnt += 1;
160 }
161 std::cmp::Ordering::Greater => {
162 if last_ts > 0 {
163 insert_into_pattern(
165 StaticBwConfig::new()
166 .bw(MTU_PER_MILLIS * last_cnt)
167 .duration(MAHIMAHI_TS_BIN),
168 );
169 }
170 if ts - last_ts > 1 {
171 insert_into_pattern(
173 StaticBwConfig::new()
174 .bw(Bandwidth::ZERO)
175 .duration(MAHIMAHI_TS_BIN * ((ts - last_ts - 1) as u32)),
176 );
177 }
178 last_cnt = 1;
179 last_ts = ts;
180 }
181 }
182 }
183 if last_cnt == 0 {
184 return Err("trace must last for a nonzero amount of time");
186 } else {
187 insert_into_pattern(
189 StaticBwConfig::new()
190 .bw(MTU_PER_MILLIS * (last_cnt + zeor_ts_cnt))
191 .duration(MAHIMAHI_TS_BIN),
192 );
193 }
194 Ok(RepeatedBwPatternConfig::new()
195 .count(count.unwrap_or(0))
196 .pattern(
197 pattern
198 .drain(..)
199 .map(|config| Box::new(config) as Box<dyn BwTraceConfig>)
200 .collect(),
201 ))
202}
203
204pub const MAHIMAHI_PDO_BYTES: u64 = 1504;
206
207#[cfg(feature = "pdo-model")]
224pub fn load_mahimahi_heavy_pdo_trace(
225 trace: Vec<u64>,
226 count: Option<usize>,
227 pdo_bytes: u64,
228) -> Result<RepeatedHeavyPdoPatternConfig, &'static str> {
229 let mut pattern: Vec<(Duration, u64)> = vec![];
230 let mut last_ts = 0;
231 for ts in trace {
232 match ts.cmp(&last_ts) {
233 std::cmp::Ordering::Less => {
234 return Err("timestamps must be monotonically nondecreasing");
235 }
236 std::cmp::Ordering::Equal if !pattern.is_empty() => {
237 pattern.last_mut().unwrap().1 += pdo_bytes;
238 }
239 _ => {
240 pattern.push((Duration::from_millis(ts - last_ts), pdo_bytes));
241 last_ts = ts;
242 }
243 }
244 }
245 if last_ts == 0 {
246 return Err("trace must last for a nonzero amount of time");
247 }
248 Ok(RepeatedHeavyPdoPatternConfig::new()
249 .count(count.unwrap_or(0))
250 .pattern(vec![
251 Box::new(TraceHeavyPdoConfig::new().pattern(pattern)) as Box<dyn HeavyPdoTraceConfig>
252 ]))
253}
254
255#[cfg(test)]
256mod test {
257 use super::*;
258 use crate::model::StaticBwConfig;
259 use crate::Bandwidth;
260
261 #[test]
262 #[cfg(feature = "pdo-model")]
263 fn test_load_heavy_pdo() {
264 use crate::HeavyPdoTrace;
265
266 assert!(matches!(
267 load_mahimahi_heavy_pdo_trace(vec![0, 2, 4, 3], None, MAHIMAHI_PDO_BYTES),
268 Err("timestamps must be monotonically nondecreasing")
269 ));
270 assert!(matches!(
271 load_mahimahi_heavy_pdo_trace(vec![0, 0, 0], None, MAHIMAHI_PDO_BYTES),
272 Err("trace must last for a nonzero amount of time")
273 ));
274 assert!(matches!(
275 load_mahimahi_heavy_pdo_trace(vec![], None, MAHIMAHI_PDO_BYTES),
276 Err("trace must last for a nonzero amount of time")
277 ));
278
279 let mut pdo = load_mahimahi_heavy_pdo_trace(vec![1, 1, 5, 6], Some(2), 1504)
280 .unwrap()
281 .build();
282 for _ in 0..2 {
283 assert_eq!(pdo.next_heavy_pdo(), Some((Duration::from_millis(1), 3008)));
284 assert_eq!(pdo.next_heavy_pdo(), Some((Duration::from_millis(4), 1504)));
285 assert_eq!(pdo.next_heavy_pdo(), Some((Duration::from_millis(1), 1504)));
286 }
287 assert_eq!(pdo.next_heavy_pdo(), None);
288
289 let mut pdo = load_mahimahi_heavy_pdo_trace(vec![0, 0, 2, 2, 3], None, 1400)
291 .unwrap()
292 .build();
293 for _ in 0..3 {
294 assert_eq!(pdo.next_heavy_pdo(), Some((Duration::ZERO, 2800)));
295 assert_eq!(pdo.next_heavy_pdo(), Some((Duration::from_millis(2), 2800)));
296 assert_eq!(pdo.next_heavy_pdo(), Some((Duration::from_millis(1), 1400)));
297 }
298 }
299
300 #[test]
301 #[cfg(feature = "pdo-model")]
302 fn test_heavy_pdo_matches_timestamps() {
303 use crate::HeavyPdoTrace;
304
305 let trace = vec![2, 2, 3, 3, 4, 4, 5, 5, 8, 9];
307 let mut pdo = load_mahimahi_heavy_pdo_trace(trace.clone(), Some(2), 1)
308 .unwrap()
309 .build();
310 let mut now = 0;
311 let mut replayed = vec![];
312 while let Some((interval, bytes)) = pdo.next_heavy_pdo() {
313 now += interval.as_millis() as u64;
314 replayed.extend(std::iter::repeat_n(now, bytes as usize));
315 }
316 let expected: Vec<u64> = trace
317 .iter()
318 .copied()
319 .chain(trace.iter().map(|ts| ts + 9))
320 .collect();
321 assert_eq!(replayed, expected);
322 }
323
324 #[test]
325 fn test_trait() {
326 let mut static_bw = StaticBwConfig::new()
327 .bw(Bandwidth::from_mbps(24))
328 .duration(Duration::from_secs(1))
329 .build();
330 assert_eq!(
331 static_bw.mahimahi(&Duration::from_millis(5)),
332 [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
333 );
334 let mut static_bw = StaticBwConfig::new()
335 .bw(Bandwidth::from_mbps(12))
336 .duration(Duration::from_secs(1))
337 .build();
338 assert_eq!(
339 static_bw.mahimahi_to_string(&Duration::from_millis(5)),
340 "1\n2\n3\n4\n5"
341 );
342 let a = vec![
343 Box::new(
344 StaticBwConfig::new()
345 .bw(Bandwidth::from_mbps(12))
346 .duration(Duration::from_millis(2)),
347 ) as Box<dyn BwTraceConfig>,
348 Box::new(
349 StaticBwConfig::new()
350 .bw(Bandwidth::from_mbps(24))
351 .duration(Duration::from_millis(2)),
352 ) as Box<dyn BwTraceConfig>,
353 ];
354 let mut c = Box::new(RepeatedBwPatternConfig::new().pattern(a).count(2)).into_model();
355 assert_eq!(
356 c.mahimahi(&Duration::MAX),
357 [1, 2, 3, 3, 4, 4, 5, 6, 7, 7, 8, 8]
358 );
359 }
360
361 #[test]
362 fn test_load() {
363 assert!(matches!(
364 load_mahimahi_trace(vec![0, 2, 4, 3], None),
365 Err("timestamps must be monotonically nondecreasing")
366 ));
367 assert!(matches!(
368 load_mahimahi_trace(vec![0, 0, 0], None),
369 Err("trace must last for a nonzero amount of time")
370 ));
371
372 let trace = vec![1, 1, 5, 6];
373 let mut bw = load_mahimahi_trace(trace, None).unwrap().build();
374 assert_eq!(
376 bw.next_bw(),
377 Some((Bandwidth::from_mbps(24), Duration::from_millis(1)))
378 );
379 assert_eq!(
380 bw.next_bw(),
381 Some((Bandwidth::from_mbps(0), Duration::from_millis(3)))
382 );
383 assert_eq!(
384 bw.next_bw(),
385 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
386 );
387 assert_eq!(
389 bw.next_bw(),
390 Some((Bandwidth::from_mbps(24), Duration::from_millis(1)))
391 );
392
393 let trace = vec![0, 0, 2, 2, 3, 3, 6, 6];
394 let mut bw = load_mahimahi_trace(trace, Some(0)).unwrap().build();
395 assert_eq!(
397 bw.next_bw(),
398 Some((Bandwidth::from_mbps(0), Duration::from_millis(1)))
399 );
400 assert_eq!(
401 bw.next_bw(),
402 Some((Bandwidth::from_mbps(24), Duration::from_millis(2)))
403 );
404 assert_eq!(
405 bw.next_bw(),
406 Some((Bandwidth::from_mbps(0), Duration::from_millis(2)))
407 );
408 assert_eq!(
409 bw.next_bw(),
410 Some((Bandwidth::from_mbps(48), Duration::from_millis(1)))
411 );
412 assert_eq!(
414 bw.next_bw(),
415 Some((Bandwidth::from_mbps(0), Duration::from_millis(1)))
416 );
417 assert_eq!(
418 bw.next_bw(),
419 Some((Bandwidth::from_mbps(24), Duration::from_millis(2)))
420 );
421
422 let mut bw = RepeatedBwPatternConfig::new()
423 .count(2)
424 .pattern(vec![
425 Box::new(load_mahimahi_trace(vec![1, 1, 2, 2, 3, 3], Some(1)).unwrap())
426 as Box<dyn BwTraceConfig>,
427 Box::new(load_mahimahi_trace(vec![1, 2], Some(2)).unwrap())
428 as Box<dyn BwTraceConfig>,
429 ])
430 .build();
431 assert_eq!(
432 bw.next_bw(),
433 Some((Bandwidth::from_mbps(24), Duration::from_millis(3)))
434 );
435 assert_eq!(
436 bw.next_bw(),
437 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
438 );
439 assert_eq!(
440 bw.next_bw(),
441 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
442 );
443 assert_eq!(
444 bw.next_bw(),
445 Some((Bandwidth::from_mbps(24), Duration::from_millis(3)))
446 );
447 assert_eq!(
448 bw.next_bw(),
449 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
450 );
451 assert_eq!(
452 bw.next_bw(),
453 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
454 );
455 assert_eq!(bw.next_bw(), None);
456 }
457
458 #[test]
459 fn test_interoperability() {
460 let check = |trace: Vec<u64>| {
462 let mut bw = load_mahimahi_trace(trace.clone(), None).unwrap().build();
463 assert_eq!(
464 bw.mahimahi(&Duration::from_millis(*trace.last().unwrap())),
465 trace
466 );
467 };
468 check(vec![1, 1, 5, 6]);
469 check(vec![2, 2, 3, 3, 4, 4, 5, 5, 8, 9]);
470
471 let mut bw = load_mahimahi_trace(vec![0, 0, 2, 2, 3, 3, 6, 6], None)
472 .unwrap()
473 .build();
474 assert_eq!(
475 bw.mahimahi(&Duration::from_millis(12)),
476 vec![2, 2, 3, 3, 6, 6, 6, 6, 8, 8, 9, 9, 12, 12, 12, 12]
477 );
478
479 let mut bw = RepeatedBwPatternConfig::new()
480 .count(2)
481 .pattern(vec![
482 Box::new(load_mahimahi_trace(vec![1, 1, 2, 2, 3, 3], Some(1)).unwrap())
483 as Box<dyn BwTraceConfig>,
484 Box::new(load_mahimahi_trace(vec![1, 2], Some(2)).unwrap())
485 as Box<dyn BwTraceConfig>,
486 ])
487 .build();
488 assert_eq!(
489 bw.mahimahi(&Duration::MAX),
490 vec![1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 12, 13, 14]
491 );
492 }
493}