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::debug;
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
    #[must_use]
30
77088
    pub fn new() -> Self {
31
77088
        Self {
32
77088
            results: RefCell::new(Vec::new()),
33
77088
        }
34
77088
    }
35

            
36
    /// Measures the time taken for the given function and registers it under the given name.
37
32188
    pub fn measure<F, O>(&self, name: &str, function: F) -> O
38
32188
    where
39
32188
        F: FnOnce() -> O,
40
    {
41
32188
        let start = Instant::now();
42
32188
        let result = function();
43

            
44
32188
        let time = start.elapsed().as_secs_f64();
45
32188
        debug!("Time {}: {:.3}s", name, time);
46

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

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

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

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

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

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

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