khora_core/utils/
timer.rs1use std::time::{Duration, Instant};
18
19#[derive(Debug, Clone)]
37pub struct Stopwatch {
38 start_time: Option<Instant>,
39}
40
41impl Stopwatch {
42 #[inline]
44 pub fn new() -> Self {
45 Self {
46 start_time: Some(Instant::now()),
47 }
48 }
49
50 #[inline]
54 pub fn elapsed(&self) -> Option<Duration> {
55 self.start_time.map(|start| start.elapsed())
56 }
57
58 #[inline]
60 pub fn elapsed_ms(&self) -> Option<u64> {
61 self.elapsed().map(|d| d.as_millis() as u64)
62 }
63
64 #[inline]
66 pub fn elapsed_us(&self) -> Option<u64> {
67 self.elapsed().map(|d| d.as_micros() as u64)
68 }
69
70 #[inline]
72 pub fn elapsed_secs_f64(&self) -> Option<f64> {
73 self.elapsed().map(|d| d.as_secs_f64())
74 }
75}
76
77impl Default for Stopwatch {
78 fn default() -> Self {
80 Self::new()
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use std::thread;
88
89 const SMALL_DURATION_MS: u64 = 15;
90 const SLEEP_DURATION_MS: u64 = 100;
91 const SLEEP_MARGIN_MS: u64 = 200;
92
93 #[test]
96 fn stopwatch_creation_starts_timer() {
97 let watch = Stopwatch::new();
98 assert!(
100 watch.elapsed().is_some(),
101 "Elapsed should return Some after creation"
102 );
103 assert!(
104 watch.elapsed_ms().is_some(),
105 "Elapsed_ms should return Some after creation"
106 );
107 assert!(
108 watch.elapsed_us().is_some(),
109 "Elapsed_us should return Some after creation"
110 );
111 assert!(
112 watch.elapsed_secs_f64().is_some(),
113 "Elapsed_secs_f64 should return Some after creation"
114 );
115 }
116
117 #[test]
120 fn stopwatch_elapsed_time_near_zero_initially() {
121 let watch = Stopwatch::new();
122
123 let elapsed_duration = watch.elapsed().expect("Should have elapsed duration");
125 assert!(
126 elapsed_duration < Duration::from_millis(SMALL_DURATION_MS),
127 "Initial elapsed duration ({elapsed_duration:?}) should be very small"
128 );
129
130 let elapsed_ms = watch.elapsed_ms().expect("Should have elapsed ms");
132 assert!(
133 elapsed_ms < SMALL_DURATION_MS,
134 "Initial elapsed ms ({elapsed_ms}) should be very small"
135 );
136
137 let elapsed_us = watch.elapsed_us().expect("Should have elapsed us");
139 let small_duration_us = SMALL_DURATION_MS * 1000;
140 assert!(
141 elapsed_us < small_duration_us,
142 "Initial elapsed us ({elapsed_us}) should be very small"
143 );
144
145 let elapsed_secs_f64 = watch
146 .elapsed_secs_f64()
147 .expect("Should have elapsed seconds as f64");
148 assert!(
149 elapsed_secs_f64 < SMALL_DURATION_MS as f64 / 1000.0,
150 "Initial elapsed seconds ({elapsed_secs_f64}) should be very small"
151 );
152 }
153
154 #[test]
157 fn stopwatch_elapsed_time_after_delay() {
158 let watch = Stopwatch::new();
159 let sleep_duration = Duration::from_millis(SLEEP_DURATION_MS);
160 let margin_duration = Duration::from_millis(SLEEP_MARGIN_MS);
161 let min_expected_duration = sleep_duration;
162 let max_expected_duration = sleep_duration + margin_duration;
163
164 thread::sleep(sleep_duration);
165
166 let elapsed_duration = watch
168 .elapsed()
169 .expect("Should have elapsed duration after sleep");
170 assert!(
171 elapsed_duration >= min_expected_duration,
172 "Elapsed duration ({elapsed_duration:?}) should be >= sleep duration ({min_expected_duration:?})"
173 );
174 assert!(
175 elapsed_duration < max_expected_duration,
176 "Elapsed duration ({elapsed_duration:?}) should be < sleep duration + margin ({max_expected_duration:?})"
177 );
178
179 let elapsed_ms = watch
181 .elapsed_ms()
182 .expect("Should have elapsed ms after sleep");
183 let min_expected_ms = SLEEP_DURATION_MS;
184 let max_expected_ms = SLEEP_DURATION_MS + SLEEP_MARGIN_MS;
185 assert!(
186 elapsed_ms >= min_expected_ms,
187 "Elapsed ms ({elapsed_ms}) should be >= sleep duration ms ({min_expected_ms})"
188 );
189 assert!(
190 elapsed_ms < max_expected_ms,
191 "Elapsed ms ({elapsed_ms}) should be < sleep duration ms + margin ({max_expected_ms})"
192 );
193
194 let elapsed_us = watch
196 .elapsed_us()
197 .expect("Should have elapsed us after sleep");
198 let min_expected_us = SLEEP_DURATION_MS * 1000;
199 let max_expected_us = (SLEEP_DURATION_MS + SLEEP_MARGIN_MS) * 1000;
200 assert!(
201 elapsed_us >= min_expected_us,
202 "Elapsed us ({elapsed_us}) should be >= sleep duration us ({min_expected_us})"
203 );
204 assert!(
205 elapsed_us < max_expected_us,
206 "Elapsed us ({elapsed_us}) should be < sleep duration us + margin ({max_expected_us})"
207 );
208
209 let elapsed_secs_f64 = watch
211 .elapsed_secs_f64()
212 .expect("Should have elapsed seconds as f64 after sleep");
213 let min_expected_secs_f64 = SLEEP_DURATION_MS as f64 / 1000.0;
214 let max_expected_secs_f64 = (SLEEP_DURATION_MS + SLEEP_MARGIN_MS) as f64 / 1000.0;
215 assert!(
216 elapsed_secs_f64 >= min_expected_secs_f64,
217 "Elapsed seconds ({elapsed_secs_f64}) should be >= sleep duration seconds ({min_expected_secs_f64})"
218 );
219 assert!(
220 elapsed_secs_f64 < max_expected_secs_f64,
221 "Elapsed seconds ({elapsed_secs_f64}) should be < sleep duration seconds + margin ({max_expected_secs_f64})"
222 );
223 }
224
225 #[test]
228 fn stopwatch_implements_default() {
229 let watch = Stopwatch::default();
230 assert!(watch.elapsed().is_some());
231 }
232
233 #[test]
236 fn stopwatch_clone() {
237 let watch1 = Stopwatch::new();
238 thread::sleep(Duration::from_millis(10));
239 let watch2 = watch1.clone(); let elapsed1 = watch1.elapsed_us().unwrap();
244 let elapsed2 = watch2.elapsed_us().unwrap();
245
246 let difference = if elapsed1 > elapsed2 {
248 elapsed1.abs_diff(elapsed2)
249 } else {
250 elapsed2.abs_diff(elapsed1)
251 };
252 assert!(
253 difference < 1000,
254 "Elapsed time of clones should be very close (diff: {difference} us)"
255 ); }
257}