utils_box_stopwatch/
stopwatch.rs

1//! # Stopwatch utilities
2//! A toolbox of small time keeping utilities.
3//! Useful for measuring and comparing execution times.
4use std::{
5    cmp::Ordering,
6    collections::HashMap,
7    time::{Duration, Instant},
8};
9use utils_box_logger::*;
10
11pub struct StopWatch {
12    timer: Instant,
13    last_lap: Duration,
14}
15
16impl StopWatch {
17    pub fn start() -> Self {
18        StopWatch {
19            timer: std::time::Instant::now(),
20            last_lap: Duration::new(0, 0),
21        }
22    }
23
24    pub fn lap_time(&mut self, lap: &str) -> Duration {
25        let elapsed = self.timer.elapsed() - self.last_lap;
26
27        let (time, unit) = format_duration(&elapsed);
28
29        log_info!("[StopWatch] Lap [{}]: {} {}", lap, time, unit);
30        self.last_lap = self.timer.elapsed();
31
32        elapsed
33    }
34
35    pub fn check_time(&self, lap: &str) -> Duration {
36        let elapsed = self.timer.elapsed();
37
38        let (time, unit) = format_duration(&elapsed);
39
40        log_info!("[StopWatch] Up to [{}]: {} {}", lap, time, unit);
41        elapsed
42    }
43}
44
45fn format_duration(duration: &Duration) -> (f64, &str) {
46    let micros = duration.as_micros();
47    if micros < 1000 {
48        (micros as f64, "us")
49    } else if micros < 1000000 {
50        ((micros / 1000) as f64, "ms")
51    } else {
52        (micros as f64 / 1.0e6, "s")
53    }
54}
55
56pub struct StopWatchStats {
57    lap_totals: HashMap<String, (u8, Duration)>,
58    max_id: u8,
59}
60
61impl StopWatchStats {
62    pub fn init() -> Self {
63        StopWatchStats {
64            lap_totals: HashMap::new(),
65            max_id: 0,
66        }
67    }
68
69    pub fn store_lap(&mut self, lap: &str, time: Duration) -> Duration {
70        match self.lap_totals.get(lap) {
71            Some(&(id, total)) => {
72                self.lap_totals.insert(lap.to_string(), (id, time + total));
73                time + total
74            }
75            None => {
76                self.max_id += 1;
77                self.lap_totals.insert(lap.to_string(), (self.max_id, time));
78                time
79            }
80        }
81    }
82
83    pub fn report(&self) {
84        if self.lap_totals.is_empty() {
85            let report_width = 20;
86            log_info!("+{:->report_width$}+", "");
87            log_info!("|{:^report_width$}|", "StopWatch Report");
88            log_info!("+{:->report_width$}+", "");
89            log_info!("| No laps recorded!  |");
90            log_info!("+{:->report_width$}+", "");
91            return;
92        }
93
94        let max = self
95            .lap_totals
96            .iter()
97            .max_by(|a, b| {
98                if a.0.len() > b.0.len() {
99                    Ordering::Greater
100                } else {
101                    Ordering::Less
102                }
103            })
104            .unwrap()
105            .0
106            .len();
107
108        let mut laps: Vec<(&String, &(u8, Duration))> = self.lap_totals.iter().collect();
109        laps.sort_by(|a, b| {
110            if a.1.0 > b.1.0 {
111                Ordering::Greater
112            } else {
113                Ordering::Less
114            }
115        });
116
117        let report_width = max + 14;
118        log_info!("+{:->report_width$}+", "");
119        log_info!("|{:^report_width$}|", "StopWatch Report");
120        log_info!("+{:->report_width$}+", "");
121
122        laps.iter().for_each(|(lap, (_id, time))| {
123            let (time, unit) = format_duration(time);
124            log_info!("|{:>max$} | {:7.3} {:3}|", lap, time, unit);
125        });
126
127        log_info!("+{:->report_width$}+", "");
128    }
129}
130
131pub struct TimeKeeper {
132    stop_watch: StopWatch,
133    stats: StopWatchStats,
134}
135
136impl TimeKeeper {
137    pub fn init() -> Self {
138        TimeKeeper {
139            stop_watch: StopWatch::start(),
140            stats: StopWatchStats::init(),
141        }
142    }
143
144    pub fn lap(&mut self, lap: &str) -> Duration {
145        self.stats.store_lap(lap, self.stop_watch.lap_time(lap))
146    }
147
148    pub fn lap_totals(&self, lap: &str) -> Duration {
149        let total = *self.stats.lap_totals.get(lap).unwrap();
150        let (time, unit) = format_duration(&total.1);
151        log_info!("[TimeKeeper] Lap [{}] Total Time: {} {}", lap, time, unit);
152
153        total.1
154    }
155
156    pub fn totals(&self) {
157        self.stats.report()
158    }
159
160    pub fn merge(&mut self, time_keeper: Self) {
161        time_keeper
162            .stats
163            .lap_totals
164            .iter()
165            .for_each(|(lap, (id, time))| {
166                let new_time = match self.stats.lap_totals.get(lap) {
167                    Some((d, t)) => (*d.min(id), *t + *time),
168                    None => (*id, *time),
169                };
170                self.stats.lap_totals.insert(lap.to_string(), new_time);
171            });
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use crate::stopwatch::*;
178
179    #[test]
180    fn stopwatch_test() {
181        let mut s = StopWatch::start();
182        let mut stats = StopWatchStats::init();
183
184        stats.store_lap("aaaaaaaaaaaa", s.lap_time("a"));
185
186        for _ in 0..5 {
187            std::thread::sleep(Duration::from_millis(5));
188            stats.store_lap("b", s.lap_time("b"));
189        }
190
191        stats.store_lap("aaaaaaaaaaaa", s.lap_time("a"));
192
193        stats.report();
194    }
195
196    #[test]
197    fn timekeeper_test() {
198        let mut s = TimeKeeper::init();
199        let mut t = TimeKeeper::init();
200
201        s.totals();
202
203        s.lap("arni");
204
205        for _ in 0..5 {
206            std::thread::sleep(Duration::from_millis(5));
207            s.lap("rifi");
208            t.lap("rifi");
209        }
210        s.lap_totals("rifi");
211        std::thread::sleep(Duration::from_millis(1234));
212        s.lap("arni");
213
214        s.totals();
215        t.totals();
216
217        s.merge(t);
218
219        s.totals();
220    }
221}