1use crate::{
36 Bandwidth, BwTrace, Delay, DelayPerPacketTrace, DelayTrace, DuplicatePattern, DuplicateTrace,
37 Duration, LossPattern, LossTrace,
38};
39use std::io::Write;
40
41#[cfg(feature = "serde")]
42use serde::{Deserialize, Serialize};
43
44#[derive(Debug, Clone, PartialEq)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub struct BwSeriesPoint {
48 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
50 pub start_time: Duration,
51 pub value: Bandwidth,
53 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
55 pub duration: Duration,
56}
57
58#[derive(Debug, Clone, PartialEq)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct DelaySeriesPoint {
62 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
64 pub start_time: Duration,
65 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
67 pub value: Delay,
68 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
70 pub duration: Duration,
71}
72
73#[derive(Debug, Clone, PartialEq)]
75#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
76pub struct DelayPerPacketSeriesPoint {
77 pub packet_index: usize,
79 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
81 pub value: Delay,
82}
83
84#[derive(Debug, Clone, PartialEq)]
86#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87pub struct LossSeriesPoint {
88 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
90 pub start_time: Duration,
91 pub value: LossPattern,
93 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
95 pub duration: Duration,
96}
97
98#[derive(Debug, Clone, PartialEq)]
100#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
101pub struct DuplicateSeriesPoint {
102 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
104 pub start_time: Duration,
105 pub value: DuplicatePattern,
107 #[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
109 pub duration: Duration,
110}
111
112#[cfg(feature = "serde")]
113mod duration_serde {
114 use serde::{Deserialize, Deserializer, Serializer};
115 use std::time::Duration;
116
117 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
118 where
119 S: Serializer,
120 {
121 let secs = duration.as_secs_f64();
122 serializer.serialize_f64(secs)
123 }
124
125 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
126 where
127 D: Deserializer<'de>,
128 {
129 let secs = f64::deserialize(deserializer)?;
130 Ok(Duration::from_secs_f64(secs))
131 }
132}
133
134pub fn expand_bw_trace(
171 trace: &mut dyn BwTrace,
172 start_time: Duration,
173 end_time: Duration,
174) -> Vec<BwSeriesPoint> {
175 let mut series = Vec::new();
176 let mut current_time = Duration::ZERO;
177
178 while let Some((value, duration)) = trace.next_bw() {
179 let segment_end = current_time + duration;
180
181 if segment_end <= start_time {
183 current_time = segment_end;
184 continue;
185 }
186
187 if current_time >= end_time {
189 break;
190 }
191
192 let actual_start = current_time.max(start_time);
194 let actual_end = segment_end.min(end_time);
195 let actual_duration = actual_end.saturating_sub(actual_start);
196
197 if !actual_duration.is_zero() {
198 series.push(BwSeriesPoint {
199 start_time: actual_start - start_time, value,
201 duration: actual_duration,
202 });
203 }
204
205 current_time = segment_end;
206
207 if current_time >= end_time {
209 break;
210 }
211 }
212
213 series
214}
215
216pub fn expand_delay_trace(
221 trace: &mut dyn DelayTrace,
222 start_time: Duration,
223 end_time: Duration,
224) -> Vec<DelaySeriesPoint> {
225 let mut series = Vec::new();
226 let mut current_time = Duration::ZERO;
227
228 while let Some((value, duration)) = trace.next_delay() {
229 let segment_end = current_time + duration;
230
231 if segment_end <= start_time {
232 current_time = segment_end;
233 continue;
234 }
235
236 if current_time >= end_time {
237 break;
238 }
239
240 let actual_start = current_time.max(start_time);
241 let actual_end = segment_end.min(end_time);
242 let actual_duration = actual_end.saturating_sub(actual_start);
243
244 if !actual_duration.is_zero() {
245 series.push(DelaySeriesPoint {
246 start_time: actual_start - start_time,
247 value,
248 duration: actual_duration,
249 });
250 }
251
252 current_time = segment_end;
253
254 if current_time >= end_time {
255 break;
256 }
257 }
258
259 series
260}
261
262pub fn expand_delay_per_packet_trace(
276 trace: &mut dyn DelayPerPacketTrace,
277 max_packets: Option<usize>,
278) -> Vec<DelayPerPacketSeriesPoint> {
279 let mut series = Vec::new();
280 let mut packet_index = 0;
281
282 while let Some(value) = trace.next_delay() {
283 series.push(DelayPerPacketSeriesPoint {
284 packet_index,
285 value,
286 });
287
288 packet_index += 1;
289
290 if let Some(max) = max_packets {
291 if packet_index >= max {
292 break;
293 }
294 }
295 }
296
297 series
298}
299
300pub fn expand_loss_trace(
302 trace: &mut dyn LossTrace,
303 start_time: Duration,
304 end_time: Duration,
305) -> Vec<LossSeriesPoint> {
306 let mut series = Vec::new();
307 let mut current_time = Duration::ZERO;
308
309 while let Some((value, duration)) = trace.next_loss() {
310 let segment_end = current_time + duration;
311
312 if segment_end <= start_time {
313 current_time = segment_end;
314 continue;
315 }
316
317 if current_time >= end_time {
318 break;
319 }
320
321 let actual_start = current_time.max(start_time);
322 let actual_end = segment_end.min(end_time);
323 let actual_duration = actual_end.saturating_sub(actual_start);
324
325 if !actual_duration.is_zero() {
326 series.push(LossSeriesPoint {
327 start_time: actual_start - start_time,
328 value,
329 duration: actual_duration,
330 });
331 }
332
333 current_time = segment_end;
334
335 if current_time >= end_time {
336 break;
337 }
338 }
339
340 series
341}
342
343pub fn expand_duplicate_trace(
345 trace: &mut dyn DuplicateTrace,
346 start_time: Duration,
347 end_time: Duration,
348) -> Vec<DuplicateSeriesPoint> {
349 let mut series = Vec::new();
350 let mut current_time = Duration::ZERO;
351
352 while let Some((value, duration)) = trace.next_duplicate() {
353 let segment_end = current_time + duration;
354
355 if segment_end <= start_time {
356 current_time = segment_end;
357 continue;
358 }
359
360 if current_time >= end_time {
361 break;
362 }
363
364 let actual_start = current_time.max(start_time);
365 let actual_end = segment_end.min(end_time);
366 let actual_duration = actual_end.saturating_sub(actual_start);
367
368 if !actual_duration.is_zero() {
369 series.push(DuplicateSeriesPoint {
370 start_time: actual_start - start_time,
371 value,
372 duration: actual_duration,
373 });
374 }
375
376 current_time = segment_end;
377
378 if current_time >= end_time {
379 break;
380 }
381 }
382
383 series
384}
385
386#[cfg(feature = "serde")]
398pub fn write_bw_series_json<P: AsRef<std::path::Path>>(
399 series: &[BwSeriesPoint],
400 path: P,
401) -> std::io::Result<()> {
402 let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
403 std::fs::write(path, json)
404}
405
406pub fn write_bw_series_csv<P: AsRef<std::path::Path>>(
415 series: &[BwSeriesPoint],
416 path: P,
417) -> std::io::Result<()> {
418 let mut file = std::fs::File::create(path)?;
419 writeln!(file, "start_time_secs,bandwidth_bps,duration_secs")?;
420
421 for point in series {
422 writeln!(
423 file,
424 "{},{},{}",
425 point.start_time.as_secs_f64(),
426 point.value.as_bps(),
427 point.duration.as_secs_f64()
428 )?;
429 }
430
431 Ok(())
432}
433
434#[cfg(feature = "serde")]
436pub fn write_delay_series_json<P: AsRef<std::path::Path>>(
437 series: &[DelaySeriesPoint],
438 path: P,
439) -> std::io::Result<()> {
440 let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
441 std::fs::write(path, json)
442}
443
444pub fn write_delay_series_csv<P: AsRef<std::path::Path>>(
448 series: &[DelaySeriesPoint],
449 path: P,
450) -> std::io::Result<()> {
451 let mut file = std::fs::File::create(path)?;
452 writeln!(file, "start_time_secs,delay_secs,duration_secs")?;
453
454 for point in series {
455 writeln!(
456 file,
457 "{},{},{}",
458 point.start_time.as_secs_f64(),
459 point.value.as_secs_f64(),
460 point.duration.as_secs_f64()
461 )?;
462 }
463
464 Ok(())
465}
466
467#[cfg(feature = "serde")]
469pub fn write_delay_per_packet_series_json<P: AsRef<std::path::Path>>(
470 series: &[DelayPerPacketSeriesPoint],
471 path: P,
472) -> std::io::Result<()> {
473 let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
474 std::fs::write(path, json)
475}
476
477pub fn write_delay_per_packet_series_csv<P: AsRef<std::path::Path>>(
481 series: &[DelayPerPacketSeriesPoint],
482 path: P,
483) -> std::io::Result<()> {
484 let mut file = std::fs::File::create(path)?;
485 writeln!(file, "packet_index,delay_secs")?;
486
487 for point in series {
488 writeln!(file, "{},{}", point.packet_index, point.value.as_secs_f64())?;
489 }
490
491 Ok(())
492}
493
494#[cfg(feature = "serde")]
496pub fn write_loss_series_json<P: AsRef<std::path::Path>>(
497 series: &[LossSeriesPoint],
498 path: P,
499) -> std::io::Result<()> {
500 let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
501 std::fs::write(path, json)
502}
503
504pub fn write_loss_series_csv<P: AsRef<std::path::Path>>(
509 series: &[LossSeriesPoint],
510 path: P,
511) -> std::io::Result<()> {
512 let mut file = std::fs::File::create(path)?;
513 writeln!(file, "start_time_secs,loss_pattern,duration_secs")?;
514
515 for point in series {
516 let pattern_str = point
517 .value
518 .iter()
519 .map(|p| p.to_string())
520 .collect::<Vec<_>>()
521 .join(";");
522
523 writeln!(
524 file,
525 "{},{},{}",
526 point.start_time.as_secs_f64(),
527 pattern_str,
528 point.duration.as_secs_f64()
529 )?;
530 }
531
532 Ok(())
533}
534
535#[cfg(feature = "serde")]
537pub fn write_duplicate_series_json<P: AsRef<std::path::Path>>(
538 series: &[DuplicateSeriesPoint],
539 path: P,
540) -> std::io::Result<()> {
541 let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
542 std::fs::write(path, json)
543}
544
545pub fn write_duplicate_series_csv<P: AsRef<std::path::Path>>(
550 series: &[DuplicateSeriesPoint],
551 path: P,
552) -> std::io::Result<()> {
553 let mut file = std::fs::File::create(path)?;
554 writeln!(file, "start_time_secs,duplicate_pattern,duration_secs")?;
555
556 for point in series {
557 let pattern_str = point
558 .value
559 .iter()
560 .map(|p| p.to_string())
561 .collect::<Vec<_>>()
562 .join(";");
563
564 writeln!(
565 file,
566 "{},{},{}",
567 point.start_time.as_secs_f64(),
568 pattern_str,
569 point.duration.as_secs_f64()
570 )?;
571 }
572
573 Ok(())
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use crate::model::StaticBwConfig;
580
581 #[test]
582 fn test_expand_bw_trace_basic() {
583 let mut trace = StaticBwConfig::new()
584 .bw(Bandwidth::from_mbps(10))
585 .duration(Duration::from_secs(5))
586 .build();
587
588 let series = expand_bw_trace(&mut trace, Duration::from_secs(0), Duration::from_secs(5));
589
590 assert_eq!(series.len(), 1);
591 assert_eq!(series[0].start_time, Duration::from_secs(0));
592 assert_eq!(series[0].value, Bandwidth::from_mbps(10));
593 assert_eq!(series[0].duration, Duration::from_secs(5));
594 }
595
596 #[test]
597 fn test_expand_bw_trace_with_cutting() {
598 let mut trace = StaticBwConfig::new()
599 .bw(Bandwidth::from_mbps(10))
600 .duration(Duration::from_secs(10))
601 .build();
602
603 let series = expand_bw_trace(&mut trace, Duration::from_secs(2), Duration::from_secs(7));
604
605 assert_eq!(series.len(), 1);
606 assert_eq!(series[0].start_time, Duration::from_secs(0)); assert_eq!(series[0].value, Bandwidth::from_mbps(10));
608 assert_eq!(series[0].duration, Duration::from_secs(5)); }
610}