use std::time::Duration; use galaxyui::{Entity, SingletonEntity}; use instant::Instant; use serde::{Deserialize, Serialize}; /// This represents one interval, i.e. one stage in a multiple-stage timer. struct TimingInterval { /// Assign each interval a unique name. name: String, /// When this interval ended. instant: Instant, } impl TimingInterval { fn new(name: String, instant: Instant) -> Self { Self { name, instant } } } /// This is a collection of points in time for a multiple-stage process that we want to time, and /// we want to measure durations between each stage, or "interval". pub struct IntervalTimer { /// The timer starts when this struct is instantiated. The first interval is measured as a /// duration from this instant, with each subsequent interval being measured from the end of /// the prior interval. start_instant: Instant, intervals: Vec, } impl IntervalTimer { pub fn new() -> Self { Self { start_instant: Instant::now(), intervals: Vec::new(), } } pub fn mark_interval_end(&mut self, name: impl Into) { self.intervals .push(TimingInterval::new(name.into(), Instant::now())) } pub fn compute_duration_for_interval(&self, name: &str) -> Option { self.intervals .iter() .enumerate() .find_map(|(idx, interval)| { if interval.name == name { let since = if idx == 0 { self.start_instant } else { self.intervals[idx - 1].instant }; let marginal = interval.instant.duration_since(since); Some(marginal) } else { None } }) } /// Once you are done with all the intervals in your process, we compute a cumulative sum of the /// time at each interval, as well as an individual time between each interval. pub fn compute_stats(&self) -> Vec { let mut cumulative_duration_ms = 0; self.intervals .iter() .enumerate() .map(|(i, interval)| { let since = if i == 0 { self.start_instant } else { self.intervals[i - 1].instant }; // Converting a duration to an int in ms returns a u128 which is excessively large // for our purposes. It's also inconvenient as it isn't serializable by default. let marginal_duration_ms = interval.instant.duration_since(since).as_millis() as u64; cumulative_duration_ms += marginal_duration_ms; TimingDataPoint::new( marginal_duration_ms, cumulative_duration_ms, interval.name.clone(), ) }) .collect() } } impl Default for IntervalTimer { fn default() -> Self { Self::new() } } impl Entity for IntervalTimer { type Event = (); } impl SingletonEntity for IntervalTimer {} /// Used for reporting the timing results after timing is complete. #[derive(Clone, Deserialize, Serialize)] pub struct TimingDataPoint { name: String, marginal_duration_ms: u64, cumulative_duration_ms: u64, } impl TimingDataPoint { fn new(marginal_duration_ms: u64, cumulative_duration_ms: u64, name: String) -> Self { Self { marginal_duration_ms, cumulative_duration_ms, name, } } } #[cfg(test)] #[path = "interval_timer_tests.rs"] mod tests;