use std::collections::HashMap;
use crate::event::Event;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Snapshot {
pub metrics: HashMap<Event, HashMap<usize, u64>>,
}
#[derive(Debug, Clone, Default)]
pub struct Phase {
pub begin: Snapshot,
pub end: Snapshot,
}
impl Phase {
pub fn diff(&self) -> Snapshot {
let metrics = self
.end
.metrics
.iter()
.map(|(event, end_per_cpu)| {
let begin_per_cpu = self.begin.metrics.get(event);
let deltas = end_per_cpu
.iter()
.map(|(cpu, ¤t_value)| {
let delta = begin_per_cpu
.and_then(|m| m.get(cpu))
.map_or(current_value, |&prev| current_value.wrapping_sub(prev));
(*cpu, delta)
})
.collect();
(*event, deltas)
})
.collect();
Snapshot { metrics }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::Event;
fn snapshot(metrics: Vec<(Event, u64)>) -> Snapshot {
Snapshot {
metrics: metrics
.into_iter()
.map(|(event, value)| (event, HashMap::from([(0, value)])))
.collect(),
}
}
fn total(snapshot: &Snapshot, event: Event) -> u64 {
snapshot.metrics[&event].values().sum()
}
#[test]
fn diff_basic_delta() {
let phase = Phase {
begin: snapshot(vec![(Event::CpuCycles, 100)]),
end: snapshot(vec![(Event::CpuCycles, 350)]),
};
assert_eq!(total(&phase.diff(), Event::CpuCycles), 250);
}
#[test]
fn diff_multiple_events() {
let phase = Phase {
begin: snapshot(vec![(Event::CpuCycles, 100), (Event::Instructions, 200)]),
end: snapshot(vec![(Event::CpuCycles, 400), (Event::Instructions, 500)]),
};
let diff = phase.diff();
assert_eq!(total(&diff, Event::CpuCycles), 300);
assert_eq!(total(&diff, Event::Instructions), 300);
}
#[test]
fn diff_equal_values_returns_zero() {
let phase = Phase {
begin: snapshot(vec![(Event::CpuCycles, 42)]),
end: snapshot(vec![(Event::CpuCycles, 42)]),
};
assert_eq!(total(&phase.diff(), Event::CpuCycles), 0);
}
#[test]
fn diff_wraps_on_counter_overflow() {
let phase = Phase {
begin: snapshot(vec![(Event::CpuCycles, u64::MAX - 5)]),
end: snapshot(vec![(Event::CpuCycles, 10)]),
};
assert_eq!(total(&phase.diff(), Event::CpuCycles), 16);
}
#[test]
fn diff_event_missing_in_begin_uses_end_value() {
let phase = Phase {
begin: snapshot(vec![]),
end: snapshot(vec![(Event::CacheMisses, 77)]),
};
assert_eq!(total(&phase.diff(), Event::CacheMisses), 77);
}
#[test]
fn diff_event_missing_in_end_is_absent_from_result() {
let phase = Phase {
begin: snapshot(vec![(Event::CpuCycles, 100), (Event::Instructions, 200)]),
end: snapshot(vec![(Event::CpuCycles, 150)]),
};
let diff = phase.diff();
assert_eq!(diff.metrics.len(), 1);
assert_eq!(total(&diff, Event::CpuCycles), 50);
assert!(!diff.metrics.contains_key(&Event::Instructions));
}
#[test]
fn diff_empty_snapshots_returns_empty() {
let phase = Phase::default();
assert_eq!(phase.diff(), Snapshot::default());
}
#[test]
fn diff_sums_independent_per_cpu_deltas() {
let phase = Phase {
begin: Snapshot {
metrics: HashMap::from([(Event::CpuCycles, HashMap::from([(0, 100), (1, 500)]))]),
},
end: Snapshot {
metrics: HashMap::from([(Event::CpuCycles, HashMap::from([(0, 300), (1, 550)]))]),
},
};
assert_eq!(total(&phase.diff(), Event::CpuCycles), 250);
}
#[test]
fn diff_per_cpu_wraps_independently_on_overflow() {
let phase = Phase {
begin: Snapshot {
metrics: HashMap::from([(
Event::CpuCycles,
HashMap::from([(0, u64::MAX - 5), (1, 500)]),
)]),
},
end: Snapshot {
metrics: HashMap::from([(Event::CpuCycles, HashMap::from([(0, 10), (1, 600)]))]),
},
};
assert_eq!(total(&phase.diff(), Event::CpuCycles), 116);
}
}