1use crate::{
25 model::{BwTraceConfig, RepeatedBwPatternConfig, StaticBwConfig},
26 Bandwidth, BwTrace, Duration,
27};
28
29const MTU_IN_BYTES: u64 = 1500;
30const MTU_IN_BITS: u64 = MTU_IN_BYTES * 8;
31const MTU_PER_MILLIS: Bandwidth = Bandwidth::from_kbps(MTU_IN_BITS);
32const MAHIMAHI_TS_BIN: Duration = Duration::from_millis(1);
33
34macro_rules! saturating_duration_as_millis_u64 {
35 ($duration:expr) => {
36 $duration
37 .as_secs()
38 .saturating_mul(1_000)
39 .saturating_add($duration.subsec_millis() as u64)
40 };
41}
42
43pub trait Mahimahi: BwTrace {
53 fn mahimahi(&mut self, total_dur: &Duration) -> Vec<u64> {
60 let mut timestamp = MAHIMAHI_TS_BIN;
61 let mut v = Vec::new();
62 let mut transfer = Bandwidth::from_bps(0);
63 let mut bin_rem = MAHIMAHI_TS_BIN;
64 while let Some((bw, mut dur)) = self.next_bw() {
65 if timestamp > *total_dur {
66 break;
67 }
68 while (timestamp <= *total_dur) && !dur.is_zero() {
69 let bin = bin_rem.min(dur);
70 bin_rem -= bin;
71 dur -= bin;
72 let bin_factor = bin.as_secs_f64() / MAHIMAHI_TS_BIN.as_secs_f64();
73 transfer += bw.mul_f64(bin_factor);
74 while transfer >= MTU_PER_MILLIS {
75 v.push(saturating_duration_as_millis_u64!(timestamp));
76 transfer -= MTU_PER_MILLIS;
77 }
78 if bin_rem.is_zero() {
79 bin_rem = MAHIMAHI_TS_BIN;
80 timestamp += MAHIMAHI_TS_BIN;
81 }
82 }
83 }
84 v
85 }
86}
87
88impl<T: BwTrace + ?Sized> Mahimahi for T {}
89
90pub trait MahimahiExt: Mahimahi {
92 fn mahimahi_to_string(&mut self, total_dur: &Duration) -> String {
94 let ts = self.mahimahi(total_dur);
95 itertools::join(ts, "\n")
96 }
97
98 fn mahimahi_to_file<P: AsRef<std::path::Path>>(&mut self, total_dur: &Duration, path: P) {
100 let content = self.mahimahi_to_string(total_dur);
101 std::fs::write(path, content).unwrap();
102 }
103}
104
105impl<T: Mahimahi + ?Sized> MahimahiExt for T {}
106
107pub fn load_mahimahi_trace(
123 trace: Vec<u64>,
124 count: Option<usize>,
125) -> Result<RepeatedBwPatternConfig, &'static str> {
126 let mut pattern: Vec<StaticBwConfig> = vec![];
127 let mut insert_into_pattern = |config| {
129 if pattern.is_empty() {
130 pattern.push(config);
131 } else {
132 let last_config = pattern.last_mut().unwrap();
133 if last_config.bw.unwrap() == config.bw.unwrap() {
134 last_config.duration =
135 Some(last_config.duration.unwrap() + config.duration.unwrap());
136 } else {
137 pattern.push(config);
138 }
139 }
140 };
141
142 let mut zeor_ts_cnt = 0; let mut last_ts = 0; let mut last_cnt = 0; for ts in trace {
146 if ts == 0 {
148 zeor_ts_cnt += 1;
149 continue;
150 }
151 match ts.cmp(&last_ts) {
153 std::cmp::Ordering::Less => {
154 return Err("timestamps must be monotonically nondecreasing");
155 }
156 std::cmp::Ordering::Equal => {
157 last_cnt += 1;
158 }
159 std::cmp::Ordering::Greater => {
160 if last_ts > 0 {
161 insert_into_pattern(
163 StaticBwConfig::new()
164 .bw(MTU_PER_MILLIS * last_cnt)
165 .duration(MAHIMAHI_TS_BIN),
166 );
167 }
168 if ts - last_ts > 1 {
169 insert_into_pattern(
171 StaticBwConfig::new()
172 .bw(Bandwidth::ZERO)
173 .duration(MAHIMAHI_TS_BIN * ((ts - last_ts - 1) as u32)),
174 );
175 }
176 last_cnt = 1;
177 last_ts = ts;
178 }
179 }
180 }
181 if last_cnt == 0 {
182 return Err("trace must last for a nonzero amount of time");
184 } else {
185 insert_into_pattern(
187 StaticBwConfig::new()
188 .bw(MTU_PER_MILLIS * (last_cnt + zeor_ts_cnt))
189 .duration(MAHIMAHI_TS_BIN),
190 );
191 }
192 Ok(RepeatedBwPatternConfig::new()
193 .count(count.unwrap_or(0))
194 .pattern(
195 pattern
196 .drain(..)
197 .map(|config| Box::new(config) as Box<dyn BwTraceConfig>)
198 .collect(),
199 ))
200}
201
202#[cfg(test)]
203mod test {
204 use super::*;
205 use crate::model::StaticBwConfig;
206 use crate::Bandwidth;
207
208 #[test]
209 fn test_trait() {
210 let mut static_bw = StaticBwConfig::new()
211 .bw(Bandwidth::from_mbps(24))
212 .duration(Duration::from_secs(1))
213 .build();
214 assert_eq!(
215 static_bw.mahimahi(&Duration::from_millis(5)),
216 [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
217 );
218 let mut static_bw = StaticBwConfig::new()
219 .bw(Bandwidth::from_mbps(12))
220 .duration(Duration::from_secs(1))
221 .build();
222 assert_eq!(
223 static_bw.mahimahi_to_string(&Duration::from_millis(5)),
224 "1\n2\n3\n4\n5"
225 );
226 let a = vec![
227 Box::new(
228 StaticBwConfig::new()
229 .bw(Bandwidth::from_mbps(12))
230 .duration(Duration::from_millis(2)),
231 ) as Box<dyn BwTraceConfig>,
232 Box::new(
233 StaticBwConfig::new()
234 .bw(Bandwidth::from_mbps(24))
235 .duration(Duration::from_millis(2)),
236 ) as Box<dyn BwTraceConfig>,
237 ];
238 let mut c = Box::new(RepeatedBwPatternConfig::new().pattern(a).count(2)).into_model();
239 assert_eq!(
240 c.mahimahi(&Duration::MAX),
241 [1, 2, 3, 3, 4, 4, 5, 6, 7, 7, 8, 8]
242 );
243 }
244
245 #[test]
246 fn test_load() {
247 assert!(matches!(
248 load_mahimahi_trace(vec![0, 2, 4, 3], None),
249 Err("timestamps must be monotonically nondecreasing")
250 ));
251 assert!(matches!(
252 load_mahimahi_trace(vec![0, 0, 0], None),
253 Err("trace must last for a nonzero amount of time")
254 ));
255
256 let trace = vec![1, 1, 5, 6];
257 let mut bw = load_mahimahi_trace(trace, None).unwrap().build();
258 assert_eq!(
260 bw.next_bw(),
261 Some((Bandwidth::from_mbps(24), Duration::from_millis(1)))
262 );
263 assert_eq!(
264 bw.next_bw(),
265 Some((Bandwidth::from_mbps(0), Duration::from_millis(3)))
266 );
267 assert_eq!(
268 bw.next_bw(),
269 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
270 );
271 assert_eq!(
273 bw.next_bw(),
274 Some((Bandwidth::from_mbps(24), Duration::from_millis(1)))
275 );
276
277 let trace = vec![0, 0, 2, 2, 3, 3, 6, 6];
278 let mut bw = load_mahimahi_trace(trace, Some(0)).unwrap().build();
279 assert_eq!(
281 bw.next_bw(),
282 Some((Bandwidth::from_mbps(0), Duration::from_millis(1)))
283 );
284 assert_eq!(
285 bw.next_bw(),
286 Some((Bandwidth::from_mbps(24), Duration::from_millis(2)))
287 );
288 assert_eq!(
289 bw.next_bw(),
290 Some((Bandwidth::from_mbps(0), Duration::from_millis(2)))
291 );
292 assert_eq!(
293 bw.next_bw(),
294 Some((Bandwidth::from_mbps(48), Duration::from_millis(1)))
295 );
296 assert_eq!(
298 bw.next_bw(),
299 Some((Bandwidth::from_mbps(0), Duration::from_millis(1)))
300 );
301 assert_eq!(
302 bw.next_bw(),
303 Some((Bandwidth::from_mbps(24), Duration::from_millis(2)))
304 );
305
306 let mut bw = RepeatedBwPatternConfig::new()
307 .count(2)
308 .pattern(vec![
309 Box::new(load_mahimahi_trace(vec![1, 1, 2, 2, 3, 3], Some(1)).unwrap())
310 as Box<dyn BwTraceConfig>,
311 Box::new(load_mahimahi_trace(vec![1, 2], Some(2)).unwrap())
312 as Box<dyn BwTraceConfig>,
313 ])
314 .build();
315 assert_eq!(
316 bw.next_bw(),
317 Some((Bandwidth::from_mbps(24), Duration::from_millis(3)))
318 );
319 assert_eq!(
320 bw.next_bw(),
321 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
322 );
323 assert_eq!(
324 bw.next_bw(),
325 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
326 );
327 assert_eq!(
328 bw.next_bw(),
329 Some((Bandwidth::from_mbps(24), Duration::from_millis(3)))
330 );
331 assert_eq!(
332 bw.next_bw(),
333 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
334 );
335 assert_eq!(
336 bw.next_bw(),
337 Some((Bandwidth::from_mbps(12), Duration::from_millis(2)))
338 );
339 assert_eq!(bw.next_bw(), None);
340 }
341
342 #[test]
343 fn test_interoperability() {
344 let check = |trace: Vec<u64>| {
346 let mut bw = load_mahimahi_trace(trace.clone(), None).unwrap().build();
347 assert_eq!(
348 bw.mahimahi(&Duration::from_millis(*trace.last().unwrap())),
349 trace
350 );
351 };
352 check(vec![1, 1, 5, 6]);
353 check(vec![2, 2, 3, 3, 4, 4, 5, 5, 8, 9]);
354
355 let mut bw = load_mahimahi_trace(vec![0, 0, 2, 2, 3, 3, 6, 6], None)
356 .unwrap()
357 .build();
358 assert_eq!(
359 bw.mahimahi(&Duration::from_millis(12)),
360 vec![2, 2, 3, 3, 6, 6, 6, 6, 8, 8, 9, 9, 12, 12, 12, 12]
361 );
362
363 let mut bw = RepeatedBwPatternConfig::new()
364 .count(2)
365 .pattern(vec![
366 Box::new(load_mahimahi_trace(vec![1, 1, 2, 2, 3, 3], Some(1)).unwrap())
367 as Box<dyn BwTraceConfig>,
368 Box::new(load_mahimahi_trace(vec![1, 2], Some(2)).unwrap())
369 as Box<dyn BwTraceConfig>,
370 ])
371 .build();
372 assert_eq!(
373 bw.mahimahi(&Duration::MAX),
374 vec![1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 12, 13, 14]
375 );
376 }
377}