1use std::time::{Duration, Instant};
2
3#[derive(Debug, Clone)]
5pub struct Phase {
6 pub name: String,
7 pub duration: Duration,
8}
9
10#[derive(Debug, Clone)]
12pub struct CommandExistsCall {
13 pub command: String,
14 pub found: bool,
15 pub duration: Duration,
16 pub cached: bool,
18}
19
20#[derive(Debug, Default)]
22pub struct Timings {
23 phases: Vec<Phase>,
24 command_exists_calls: Vec<CommandExistsCall>,
25}
26
27pub struct PhaseTimer {
29 start: Instant,
30}
31
32impl PhaseTimer {
33 pub fn start() -> Self {
34 Self { start: Instant::now() }
35 }
36
37 pub fn elapsed(&self) -> Duration {
38 self.start.elapsed()
39 }
40}
41
42impl Timings {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn record_phase(&mut self, name: &str, duration: Duration) {
48 self.phases.push(Phase {
49 name: name.to_string(),
50 duration,
51 });
52 }
53
54 pub fn record_command_exists(&mut self, command: &str, found: bool, duration: Duration, cached: bool) {
55 self.command_exists_calls.push(CommandExistsCall {
56 command: command.to_string(),
57 found,
58 duration,
59 cached,
60 });
61 }
62
63 pub fn phases(&self) -> &[Phase] {
64 &self.phases
65 }
66
67 pub fn command_exists_calls(&self) -> &[CommandExistsCall] {
68 &self.command_exists_calls
69 }
70
71 pub fn total_duration(&self) -> Duration {
72 self.phases.iter().map(|p| p.duration).sum()
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn timings_new_is_empty() {
82 let t = Timings::new();
83 assert!(t.phases().is_empty());
84 assert!(t.command_exists_calls().is_empty());
85 assert_eq!(t.total_duration(), Duration::ZERO);
86 }
87
88 #[test]
89 fn timings_record_phase() {
90 let mut t = Timings::new();
91 t.record_phase("config_load", Duration::from_micros(1230));
92 assert_eq!(t.phases().len(), 1);
93 assert_eq!(t.phases()[0].name, "config_load");
94 assert_eq!(t.phases()[0].duration, Duration::from_micros(1230));
95 }
96
97 #[test]
98 fn timings_record_command_exists_call() {
99 let mut t = Timings::new();
100 t.record_command_exists("git", true, Duration::from_micros(2340), false);
101 t.record_command_exists("lsd", false, Duration::from_micros(3120), true);
102 assert_eq!(t.command_exists_calls().len(), 2);
103 assert_eq!(t.command_exists_calls()[0].command, "git");
104 assert!(t.command_exists_calls()[0].found);
105 assert!(!t.command_exists_calls()[0].cached);
106 assert_eq!(t.command_exists_calls()[1].command, "lsd");
107 assert!(!t.command_exists_calls()[1].found);
108 assert!(t.command_exists_calls()[1].cached);
109 }
110
111 #[test]
112 fn timings_total_duration() {
113 let mut t = Timings::new();
114 t.record_phase("a", Duration::from_micros(100));
115 t.record_phase("b", Duration::from_micros(200));
116 assert_eq!(t.total_duration(), Duration::from_micros(300));
117 }
118
119 #[test]
120 fn phase_timer_elapsed_is_positive() {
121 let timer = PhaseTimer::start();
122 std::thread::sleep(Duration::from_millis(1));
123 assert!(timer.elapsed() >= Duration::from_millis(1));
124 }
125}