use std::{
collections::BTreeMap,
sync::atomic::{AtomicU64, Ordering},
time::Instant,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Phase {
Scan,
Hash,
Decode,
EncodeLossless,
EncodeLossy,
Score,
Preview,
Write,
Cache,
Report,
}
const PHASES: [(Phase, &str); 10] = [
(Phase::Scan, "scan"),
(Phase::Hash, "hash"),
(Phase::Decode, "decode"),
(Phase::EncodeLossless, "encode_lossless"),
(Phase::EncodeLossy, "encode_lossy"),
(Phase::Score, "score"),
(Phase::Preview, "preview"),
(Phase::Write, "write"),
(Phase::Cache, "cache"),
(Phase::Report, "report"),
];
#[derive(Default)]
pub(crate) struct Timings {
nanos: [AtomicU64; PHASES.len()],
}
impl Timings {
pub fn time<T>(&self, phase: Phase, work: impl FnOnce() -> T) -> T {
let start = Instant::now();
let value = work();
let index = PHASES.iter().position(|(p, _)| *p == phase).unwrap_or(0);
self.nanos[index].fetch_add(
u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX),
Ordering::Relaxed,
);
value
}
pub fn snapshot(&self) -> BTreeMap<String, f64> {
PHASES
.iter()
.enumerate()
.map(|(index, (_, name))| {
(
(*name).to_string(),
self.nanos[index].load(Ordering::Relaxed) as f64 / 1e9,
)
})
.filter(|(_, seconds)| *seconds > 0.0)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn phases_accumulate_independently() {
let timings = Timings::default();
assert_eq!(
timings.time(Phase::Decode, || {
std::thread::sleep(std::time::Duration::from_millis(2));
7
}),
7
);
timings.time(Phase::Decode, || {
std::thread::sleep(std::time::Duration::from_millis(2))
});
let snapshot = timings.snapshot();
assert!(snapshot["decode"] >= 0.004);
assert!(!snapshot.contains_key("score"));
}
}