1#![allow(dead_code)]
2use std::collections::HashMap;
3use std::time::Duration;
4
5#[macro_export]
7#[doc(hidden)]
8macro_rules! elapsed {
9 ($code:expr) => {{
10 let t = std::time::Instant::now();
11 let ret = $code;
12 let duration = t.elapsed();
13 (duration, ret)
14 }};
15 ($label:expr, $ts:expr, $code:expr) => {{
16 let t = std::time::Instant::now();
17 let ret = $code;
18 let duration = t.elapsed();
19 $ts.push($label, duration);
20 ret
21 }};
22}
23
24#[derive(aksr::Builder, Debug, Default, Clone, PartialEq)]
26pub struct Ts {
27 map: HashMap<String, Vec<Duration>>,
29 names: Vec<String>,
30}
31
32impl std::ops::Index<&str> for Ts {
33 type Output = Vec<Duration>;
34
35 fn index(&self, index: &str) -> &Self::Output {
36 self.map.get(index).unwrap_or_else(|| {
37 let available_keys: Vec<&str> = self.map.keys().map(|s| s.as_str()).collect();
38 panic!(
39 "Key '{}' was not found in Ts. Available keys: {:?}",
40 index, available_keys
41 )
42 })
43 }
44}
45
46impl std::ops::Index<usize> for Ts {
47 type Output = Vec<Duration>;
48
49 fn index(&self, index: usize) -> &Self::Output {
50 self.names
51 .get(index)
52 .and_then(|key| self.map.get(key))
53 .unwrap_or_else(|| {
54 panic!(
55 "Index {} was not found in Ts. Available indices: 0..{}",
56 index,
57 self.names.len()
58 )
59 })
60 }
61}
62
63impl Ts {
64 pub fn summary(&self) {
65 let decimal_places = 4;
66 let place_holder = '-';
67 let width_count = 10;
68 let width_time = 15;
69 let width_task = self
70 .names
71 .iter()
72 .map(|s| s.len())
73 .max()
74 .map(|x| x + 8)
75 .unwrap_or(60);
76
77 let sep = "-".repeat(width_task + 66);
78
79 println!(
81 "\n\n{:<width_task$}{:<width_count$}{:<width_time$}{:<width_time$}{:<width_time$}{:<width_time$}",
82 "Task", "Count", "Mean", "Min", "Max", "Total",
83 );
84 println!("{}", sep);
85
86 if self.is_empty() {
87 println!("No data available");
88 } else {
89 let total_name = "(Total)".to_string();
91 let mut iter = self
92 .names
93 .iter()
94 .chain(std::iter::once(&total_name))
95 .peekable();
96 while let Some(task) = iter.next() {
97 if iter.peek().is_none() {
98 let avg = self
99 .avg()
100 .map_or(place_holder.into(), |x| format!("{:.decimal_places$?}", x));
101 let total = format!("{:.decimal_places$?}", self.sum());
102 println!(
103 "{:<width_task$}{:<width_count$}{:<width_time$}{:<width_time$}{:<width_time$}{:<width_time$}",
104 task, place_holder, avg, place_holder, place_holder, total
105 );
106 } else {
107 let durations = &self.map[task];
108 let count = durations.len();
109 let total = format!("{:.decimal_places$?}", self.sum_by_key(task));
110 let avg = self
111 .avg_by_key(task)
112 .map_or(place_holder.into(), |x| format!("{:.decimal_places$?}", x));
113 let min = durations
114 .iter()
115 .min()
116 .map_or(place_holder.into(), |x| format!("{:.decimal_places$?}", x));
117 let max = durations
118 .iter()
119 .max()
120 .map_or(place_holder.into(), |x| format!("{:.decimal_places$?}", x));
121
122 println!(
123 "{:<width_task$}{:<width_count$}{:<width_time$}{:<width_time$}{:<width_time$}{:<width_time$}",
124 task, count, avg, min, max, total
125 );
126 }
127 }
128 }
129 }
130
131 pub fn merge(xs: &[&Ts]) -> Self {
132 let mut names = Vec::new();
133 let mut map: HashMap<String, Vec<Duration>> = HashMap::new();
134 for x in xs.iter() {
135 names.extend_from_slice(x.get_names());
136 map.extend(x.get_map().to_owned());
137 }
138
139 Self { names, map }
140 }
141
142 pub fn push(&mut self, k: &str, v: Duration) {
143 if !self.names.contains(&k.to_string()) {
144 self.names.push(k.to_string());
145 }
146 self.map
147 .entry(k.to_string())
148 .and_modify(|x| x.push(v))
149 .or_insert(vec![v]);
150 }
151
152 pub fn numit(&self) -> anyhow::Result<usize> {
153 if self.names.is_empty() {
155 anyhow::bail!("Empty Ts");
156 }
157
158 let len = self[0].len();
159 for v in self.map.values() {
160 if v.len() != len {
161 anyhow::bail!(
162 "Invalid Ts: The number of elements in each values entry is inconsistent"
163 );
164 }
165 }
166
167 Ok(len)
168 }
169
170 pub fn is_valid(&self) -> bool {
171 let mut iter = self.map.values();
172 if let Some(first) = iter.next() {
173 let len = first.len();
174 iter.all(|v| v.len() == len)
175 } else {
176 true
177 }
178 }
179
180 pub fn sum_by_index(&self, i: usize) -> Duration {
181 self[i].iter().sum::<Duration>()
182 }
183
184 pub fn sum_by_key(&self, i: &str) -> Duration {
185 self[i].iter().sum::<Duration>()
186 }
187
188 pub fn avg_by_index(&self, i: usize) -> anyhow::Result<Duration> {
189 let len = self[i].len();
190 if len == 0 {
191 anyhow::bail!("Cannot compute average for an empty duration vector.")
192 } else {
193 Ok(self.sum_by_index(i) / len as u32)
194 }
195 }
196
197 pub fn avg_by_key(&self, i: &str) -> anyhow::Result<Duration> {
198 let len = self[i].len();
199 if len == 0 {
200 anyhow::bail!("Cannot compute average for an empty duration vector.")
201 } else {
202 Ok(self.sum_by_key(i) / len as u32)
203 }
204 }
205
206 pub fn sum_column(&self, i: usize) -> Duration {
207 self.map
208 .values()
209 .filter_map(|vec| vec.get(i))
210 .copied()
211 .sum()
212 }
213
214 pub fn sum(&self) -> Duration {
215 self.map.values().flat_map(|vec| vec.iter()).copied().sum()
216 }
217
218 pub fn avg(&self) -> anyhow::Result<Duration> {
219 self.names.iter().map(|x| self.avg_by_key(x)).sum()
220 }
221
222 pub fn skip(mut self, n: usize) -> Self {
223 self.map.iter_mut().for_each(|(_, vec)| {
224 *vec = vec.iter().skip(n).copied().collect();
225 });
226 self
227 }
228
229 pub fn clear(&mut self) {
230 self.names.clear();
231 self.map.clear();
232 }
233
234 pub fn is_empty(&self) -> bool {
235 self.names.is_empty() && self.map.is_empty()
236 }
237
238 pub fn get_names(&self) -> &Vec<String> {
240 &self.names
241 }
242
243 pub fn get_map(&self) -> &HashMap<String, Vec<Duration>> {
245 &self.map
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use std::time::Duration;
253
254 #[test]
255 fn test_push_and_indexing() {
256 let mut ts = Ts::default();
257
258 ts.push("task1", Duration::new(1, 0));
259 ts.push("task1", Duration::new(2, 0));
260 ts.push("task2", Duration::new(3, 0));
261
262 assert_eq!(ts["task1"], vec![Duration::new(1, 0), Duration::new(2, 0)]);
263 assert_eq!(ts["task2"], vec![Duration::new(3, 0)]);
264 }
265
266 #[test]
267 fn test_numit() {
268 let mut ts = Ts::default();
269
270 ts.push("task1", Duration::new(1, 0));
271 ts.push("task1", Duration::new(2, 0));
272 ts.push("task2", Duration::new(3, 0));
273 ts.push("task2", Duration::new(4, 0));
274
275 assert_eq!(ts.numit().unwrap(), 2);
276 }
277
278 #[test]
279 fn test_is_valid() {
280 let mut ts = Ts::default();
281
282 ts.push("task1", Duration::new(1, 0));
283 ts.push("task1", Duration::new(2, 0));
284 ts.push("task2", Duration::new(3, 0));
285
286 assert!(!ts.is_valid());
287
288 ts.push("task2", Duration::new(4, 0));
289 ts.push("task3", Duration::new(5, 0));
290
291 assert!(!ts.is_valid());
292
293 ts.push("task3", Duration::new(6, 0));
294 assert!(ts.is_valid());
295 }
296
297 #[test]
298 fn test_sum_by_index() {
299 let mut ts = Ts::default();
300
301 ts.push("task1", Duration::new(1, 0));
302 ts.push("task1", Duration::new(2, 0));
303 ts.push("task2", Duration::new(3, 0));
304 ts.push("task2", Duration::new(3, 0));
305 ts.push("task2", Duration::new(3, 0));
306
307 assert_eq!(ts.sum_by_index(0), Duration::new(3, 0)); assert_eq!(ts.sum_by_index(1), Duration::new(9, 0)); }
310
311 #[test]
312 fn test_sum_by_key() {
313 let mut ts = Ts::default();
314
315 ts.push("task1", Duration::new(1, 0));
316 ts.push("task1", Duration::new(2, 0));
317 ts.push("task2", Duration::new(3, 0));
318 ts.push("task2", Duration::new(3, 0));
319 ts.push("task2", Duration::new(3, 0));
320
321 assert_eq!(ts.sum_by_key("task1"), Duration::new(3, 0)); assert_eq!(ts.sum_by_key("task2"), Duration::new(9, 0)); }
324
325 #[test]
326 fn test_avg_by_index() {
327 let mut ts = Ts::default();
328
329 ts.push("task1", Duration::new(1, 0));
330 ts.push("task1", Duration::new(2, 0));
331 ts.push("task2", Duration::new(2, 0));
332 ts.push("task2", Duration::new(2, 0));
333 ts.push("task3", Duration::new(2, 0));
334
335 assert_eq!(ts.avg_by_index(0).unwrap(), Duration::new(1, 500_000_000));
336 assert_eq!(ts.avg_by_index(1).unwrap(), Duration::new(2, 0));
337 assert_eq!(ts.avg_by_index(2).unwrap(), Duration::new(2, 0));
338 }
339
340 #[test]
341 fn test_avg_by_key() {
342 let mut ts = Ts::default();
343
344 ts.push("task1", Duration::new(1, 0));
345 ts.push("task1", Duration::new(2, 0));
346
347 let avg = ts.avg_by_key("task1").unwrap();
348 assert_eq!(avg, Duration::new(1, 500_000_000));
349 }
350
351 #[test]
352 fn test_sum_column() {
353 let mut ts = Ts::default();
354
355 ts.push("task1", Duration::new(1, 0));
356 ts.push("task1", Duration::new(2, 0));
357 ts.push("task2", Duration::new(3, 0));
358
359 assert_eq!(ts.sum_column(0), Duration::new(4, 0)); }
361
362 #[test]
363 fn test_sum() {
364 let mut ts = Ts::default();
365
366 ts.push("task1", Duration::new(1, 0));
367 ts.push("task1", Duration::new(2, 0));
368 ts.push("task2", Duration::new(3, 0));
369
370 assert_eq!(ts.sum(), Duration::new(6, 0));
371 }
372
373 #[test]
374 fn test_avg() {
375 let mut ts = Ts::default();
376
377 ts.push("task1", Duration::new(1, 0));
378 ts.push("task1", Duration::new(2, 0));
379 ts.push("task2", Duration::new(3, 0));
380 ts.push("task2", Duration::new(4, 0));
381
382 assert_eq!(ts.avg().unwrap(), Duration::new(5, 0));
383 }
384
385 #[test]
386 fn test_skip() {
387 let mut ts = Ts::default();
388
389 ts.push("task1", Duration::new(1, 0));
390 ts.push("task1", Duration::new(2, 0));
391 ts.push("task2", Duration::new(3, 0));
392 ts.push("task2", Duration::new(4, 0));
393 ts.push("task2", Duration::new(4, 0));
394
395 let ts_skipped = ts.skip(1);
396
397 assert_eq!(ts_skipped["task1"], vec![Duration::new(2, 0)]);
398 assert_eq!(
399 ts_skipped["task2"],
400 vec![Duration::new(4, 0), Duration::new(4, 0)]
401 );
402
403 let ts_skipped = ts_skipped.skip(1);
404
405 assert!(ts_skipped["task1"].is_empty());
406 assert_eq!(ts_skipped["task2"], vec![Duration::new(4, 0)]);
407 }
408
409 #[test]
410 fn test_clear() {
411 let mut ts = Ts::default();
412
413 ts.push("task1", Duration::new(1, 0));
414 ts.push("task2", Duration::new(2, 0));
415
416 ts.clear();
417 assert!(ts.names.is_empty());
418 assert!(ts.map.is_empty());
419 }
420}