1
use std::cell::RefCell;
2
use std::collections::HashMap;
3
use std::io;
4
use std::io::Write;
5
use std::time::Instant;
6

            
7
use log::info;
8

            
9
/// A timing object to measure the time of different parts of the program. This
10
/// is useful for debugging and profiling.
11
#[derive(Default)]
12
pub struct Timing {
13
    /// Stores the results of finished timers.
14
    results: RefCell<Vec<(String, f32)>>,
15
}
16

            
17
/// Aggregated timing summary for a named timer.
18
struct Aggregate {
19
    name: String,
20
    min: f32,
21
    max: f32,
22
    total: f32,
23
    avg: f32,
24
    count: usize,
25
}
26

            
27
impl Timing {
28
    /// Creates a new timing object to track timers.
29
15200
    pub fn new() -> Self {
30
15200
        Self {
31
15200
            results: RefCell::new(Vec::new()),
32
15200
        }
33
15200
    }
34

            
35
    /// Starts a new timer with the given name.
36
5762
    pub fn measure<F, O>(&self, name: &str, function: F) -> O
37
5762
    where
38
5762
        F: FnOnce() -> O,
39
    {
40
5762
        let start = Instant::now();
41
5762
        let result = function();
42

            
43
5762
        let time = start.elapsed().as_secs_f64();
44
5762
        info!("Time {}: {:.3}s", name, time);
45

            
46
        // Register the result.
47
5762
        self.results.borrow_mut().push((name.to_string(), time as f32));
48
5762
        result
49
5762
    }
50

            
51
    /// Aggregate results by name and compute (min, max, avg, count, total) for each.
52
    fn aggregate_results(&self) -> Vec<Aggregate> {
53
        let mut map: HashMap<String, Aggregate> = HashMap::new();
54
        for (name, time) in self.results.borrow().iter() {
55
            map.entry(name.clone())
56
                .and_modify(|ag| {
57
                    ag.count += 1;
58
                    ag.total += *time;
59
                    ag.min = ag.min.min(*time);
60
                    ag.max = ag.max.max(*time);
61
                })
62
                .or_insert(Aggregate {
63
                    name: name.clone(),
64
                    min: *time,
65
                    max: *time,
66
                    total: *time,
67
                    avg: 0.0,
68
                    count: 1,
69
                });
70
        }
71

            
72
        // Compute the averages and sort by name.
73
        let mut out: Vec<Aggregate> = map
74
            .into_values()
75
            .map(|mut ag| {
76
                ag.avg = if ag.count > 0 {
77
                    ag.total / (ag.count as f32)
78
                } else {
79
                    0.0
80
                };
81
                ag
82
            })
83
            .collect();
84

            
85
        out.sort_by(|a, b| a.name.cmp(&b.name));
86
        out
87
    }
88

            
89
    /// Prints all the finished timers aggregated by name (total first; omit metrics when n == 1).
90
    pub fn print(&self) {
91
        for ag in self.aggregate_results() {
92
            if ag.count == 1 {
93
                eprintln!("Time {}: {:.3}s", ag.name, ag.total);
94
            } else {
95
                eprintln!(
96
                    "Time {}: {:.3}s, min: {:.3}s, max: {:.3}s, avg: {:.3}s, n: {}",
97
                    ag.name, ag.total, ag.min, ag.max, ag.avg, ag.count
98
                );
99
            }
100
        }
101
    }
102

            
103
    /// Writes a YAML report of the finished timers to the given writer.
104
    pub fn print_yaml<W: Write>(&self, tool_name: &str, writer: &mut W) -> io::Result<()> {
105
        writeln!(writer, "- tool: {tool_name}")?;
106
        writeln!(writer, "  timing:")?;
107

            
108
        for ag in self.aggregate_results() {
109
            writeln!(writer, "    {}:", ag.name)?;
110
            writeln!(writer, "      total: {total:.3}s", total = ag.total)?;
111
            if ag.count > 1 {
112
                writeln!(writer, "      count: {}", ag.count)?;
113
                writeln!(writer, "      min: {min:.3}s", min = ag.min)?;
114
                writeln!(writer, "      max: {max:.3}s", max = ag.max)?;
115
                writeln!(writer, "      avg: {avg:.3}s", avg = ag.avg)?;
116
            }
117
        }
118
        Ok(())
119
    }
120
}