use crate::{
Bandwidth, BwTrace, Delay, DelayPerPacketTrace, DelayTrace, DuplicatePattern, DuplicateTrace,
Duration, LossPattern, LossTrace,
};
use std::io::Write;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BwSeriesPoint {
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub start_time: Duration,
pub value: Bandwidth,
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub duration: Duration,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DelaySeriesPoint {
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub start_time: Duration,
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub value: Delay,
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub duration: Duration,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DelayPerPacketSeriesPoint {
pub packet_index: usize,
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub value: Delay,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LossSeriesPoint {
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub start_time: Duration,
pub value: LossPattern,
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub duration: Duration,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DuplicateSeriesPoint {
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub start_time: Duration,
pub value: DuplicatePattern,
#[cfg_attr(feature = "serde", serde(with = "duration_serde"))]
pub duration: Duration,
}
#[cfg(feature = "serde")]
mod duration_serde {
use serde::{Deserialize, Deserializer, Serializer};
use std::time::Duration;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let secs = duration.as_secs_f64();
serializer.serialize_f64(secs)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let secs = f64::deserialize(deserializer)?;
Ok(Duration::from_secs_f64(secs))
}
}
pub fn expand_bw_trace(
trace: &mut dyn BwTrace,
start_time: Duration,
end_time: Duration,
) -> Vec<BwSeriesPoint> {
let mut series = Vec::new();
let mut current_time = Duration::ZERO;
while let Some((value, duration)) = trace.next_bw() {
let segment_end = current_time + duration;
if segment_end <= start_time {
current_time = segment_end;
continue;
}
if current_time >= end_time {
break;
}
let actual_start = current_time.max(start_time);
let actual_end = segment_end.min(end_time);
let actual_duration = actual_end.saturating_sub(actual_start);
if !actual_duration.is_zero() {
series.push(BwSeriesPoint {
start_time: actual_start - start_time, value,
duration: actual_duration,
});
}
current_time = segment_end;
if current_time >= end_time {
break;
}
}
series
}
pub fn expand_delay_trace(
trace: &mut dyn DelayTrace,
start_time: Duration,
end_time: Duration,
) -> Vec<DelaySeriesPoint> {
let mut series = Vec::new();
let mut current_time = Duration::ZERO;
while let Some((value, duration)) = trace.next_delay() {
let segment_end = current_time + duration;
if segment_end <= start_time {
current_time = segment_end;
continue;
}
if current_time >= end_time {
break;
}
let actual_start = current_time.max(start_time);
let actual_end = segment_end.min(end_time);
let actual_duration = actual_end.saturating_sub(actual_start);
if !actual_duration.is_zero() {
series.push(DelaySeriesPoint {
start_time: actual_start - start_time,
value,
duration: actual_duration,
});
}
current_time = segment_end;
if current_time >= end_time {
break;
}
}
series
}
pub fn expand_delay_per_packet_trace(
trace: &mut dyn DelayPerPacketTrace,
max_packets: Option<usize>,
) -> Vec<DelayPerPacketSeriesPoint> {
let mut series = Vec::new();
let mut packet_index = 0;
while let Some(value) = trace.next_delay() {
series.push(DelayPerPacketSeriesPoint {
packet_index,
value,
});
packet_index += 1;
if let Some(max) = max_packets {
if packet_index >= max {
break;
}
}
}
series
}
pub fn expand_loss_trace(
trace: &mut dyn LossTrace,
start_time: Duration,
end_time: Duration,
) -> Vec<LossSeriesPoint> {
let mut series = Vec::new();
let mut current_time = Duration::ZERO;
while let Some((value, duration)) = trace.next_loss() {
let segment_end = current_time + duration;
if segment_end <= start_time {
current_time = segment_end;
continue;
}
if current_time >= end_time {
break;
}
let actual_start = current_time.max(start_time);
let actual_end = segment_end.min(end_time);
let actual_duration = actual_end.saturating_sub(actual_start);
if !actual_duration.is_zero() {
series.push(LossSeriesPoint {
start_time: actual_start - start_time,
value,
duration: actual_duration,
});
}
current_time = segment_end;
if current_time >= end_time {
break;
}
}
series
}
pub fn expand_duplicate_trace(
trace: &mut dyn DuplicateTrace,
start_time: Duration,
end_time: Duration,
) -> Vec<DuplicateSeriesPoint> {
let mut series = Vec::new();
let mut current_time = Duration::ZERO;
while let Some((value, duration)) = trace.next_duplicate() {
let segment_end = current_time + duration;
if segment_end <= start_time {
current_time = segment_end;
continue;
}
if current_time >= end_time {
break;
}
let actual_start = current_time.max(start_time);
let actual_end = segment_end.min(end_time);
let actual_duration = actual_end.saturating_sub(actual_start);
if !actual_duration.is_zero() {
series.push(DuplicateSeriesPoint {
start_time: actual_start - start_time,
value,
duration: actual_duration,
});
}
current_time = segment_end;
if current_time >= end_time {
break;
}
}
series
}
#[cfg(feature = "serde")]
pub fn write_bw_series_json<P: AsRef<std::path::Path>>(
series: &[BwSeriesPoint],
path: P,
) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
std::fs::write(path, json)
}
pub fn write_bw_series_csv<P: AsRef<std::path::Path>>(
series: &[BwSeriesPoint],
path: P,
) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
writeln!(file, "start_time_secs,bandwidth_bps,duration_secs")?;
for point in series {
writeln!(
file,
"{},{},{}",
point.start_time.as_secs_f64(),
point.value.as_bps(),
point.duration.as_secs_f64()
)?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn write_delay_series_json<P: AsRef<std::path::Path>>(
series: &[DelaySeriesPoint],
path: P,
) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
std::fs::write(path, json)
}
pub fn write_delay_series_csv<P: AsRef<std::path::Path>>(
series: &[DelaySeriesPoint],
path: P,
) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
writeln!(file, "start_time_secs,delay_secs,duration_secs")?;
for point in series {
writeln!(
file,
"{},{},{}",
point.start_time.as_secs_f64(),
point.value.as_secs_f64(),
point.duration.as_secs_f64()
)?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn write_delay_per_packet_series_json<P: AsRef<std::path::Path>>(
series: &[DelayPerPacketSeriesPoint],
path: P,
) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
std::fs::write(path, json)
}
pub fn write_delay_per_packet_series_csv<P: AsRef<std::path::Path>>(
series: &[DelayPerPacketSeriesPoint],
path: P,
) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
writeln!(file, "packet_index,delay_secs")?;
for point in series {
writeln!(file, "{},{}", point.packet_index, point.value.as_secs_f64())?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn write_loss_series_json<P: AsRef<std::path::Path>>(
series: &[LossSeriesPoint],
path: P,
) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
std::fs::write(path, json)
}
pub fn write_loss_series_csv<P: AsRef<std::path::Path>>(
series: &[LossSeriesPoint],
path: P,
) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
writeln!(file, "start_time_secs,loss_pattern,duration_secs")?;
for point in series {
let pattern_str = point
.value
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(";");
writeln!(
file,
"{},{},{}",
point.start_time.as_secs_f64(),
pattern_str,
point.duration.as_secs_f64()
)?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn write_duplicate_series_json<P: AsRef<std::path::Path>>(
series: &[DuplicateSeriesPoint],
path: P,
) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(series).map_err(std::io::Error::other)?;
std::fs::write(path, json)
}
pub fn write_duplicate_series_csv<P: AsRef<std::path::Path>>(
series: &[DuplicateSeriesPoint],
path: P,
) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
writeln!(file, "start_time_secs,duplicate_pattern,duration_secs")?;
for point in series {
let pattern_str = point
.value
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(";");
writeln!(
file,
"{},{},{}",
point.start_time.as_secs_f64(),
pattern_str,
point.duration.as_secs_f64()
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::StaticBwConfig;
#[test]
fn test_expand_bw_trace_basic() {
let mut trace = StaticBwConfig::new()
.bw(Bandwidth::from_mbps(10))
.duration(Duration::from_secs(5))
.build();
let series = expand_bw_trace(&mut trace, Duration::from_secs(0), Duration::from_secs(5));
assert_eq!(series.len(), 1);
assert_eq!(series[0].start_time, Duration::from_secs(0));
assert_eq!(series[0].value, Bandwidth::from_mbps(10));
assert_eq!(series[0].duration, Duration::from_secs(5));
}
#[test]
fn test_expand_bw_trace_with_cutting() {
let mut trace = StaticBwConfig::new()
.bw(Bandwidth::from_mbps(10))
.duration(Duration::from_secs(10))
.build();
let series = expand_bw_trace(&mut trace, Duration::from_secs(2), Duration::from_secs(7));
assert_eq!(series.len(), 1);
assert_eq!(series[0].start_time, Duration::from_secs(0)); assert_eq!(series[0].value, Bandwidth::from_mbps(10));
assert_eq!(series[0].duration, Duration::from_secs(5)); }
}