utils_box_stopwatch/
stopwatch.rs1use 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}