1use std::default::Default;
2use std::fmt;
3use std::time::{Duration, Instant};
4
5#[derive(Clone, Debug)]
7pub struct TimeSpan {
8 pub start: Instant,
10 pub stop: Option<Instant>,
12}
13
14impl Into<Duration> for TimeSpan {
16 fn into(self) -> Duration {
17 if let Some(stop) = self.stop {
18 stop - self.start
19 } else {
20 self.start.elapsed()
21 }
22 }
23}
24
25#[derive(Clone, Default, Debug)]
45pub struct Stopwatch {
46 pub spans: Vec<TimeSpan>,
50}
51
52impl fmt::Display for Stopwatch {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 return write!(f, "{}s", self.elapsed().as_secs_f64());
56 }
57}
58
59impl Stopwatch {
60 pub fn start(&mut self) -> Option<TimeSpan> {
66 let ret = self.stop();
68 self.spans.push(TimeSpan {
69 start: Instant::now(),
70 stop: None,
71 });
72 return ret;
73 }
74
75 pub fn stop(&mut self) -> Option<TimeSpan> {
77 let mut ret = None;
78 if self.is_running() {
79 self.spans.last_mut().unwrap().stop = Some(Instant::now());
80 ret = Some(self.spans.last().unwrap().clone());
81 }
82 return ret;
83 }
84
85 pub fn is_running(&self) -> bool {
87 !self.spans.is_empty() && self.spans.last().unwrap().stop.is_none()
90 }
91
92 pub fn elapsed(&self) -> Duration {
94 self.spans.iter().map(|s| {let d: Duration = s.clone().into(); d}).sum()
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use std::time::Duration;
102 use crate::*;
103
104 static SLEEP_MS: u64 = 50;
105 static TOLERANCE_PERCENTAGE: f64 = 0.3;
106
107 #[test]
108 fn repeated_stops() {
109 let mut sw = Stopwatch::default();
110 for _ in 0..1000 {
111 sw.start();
112 }
113 sw.stop();
114 assert_eq!(sw.spans.len(), 1000);
115 assert!(sw.spans.last().unwrap().stop.is_some());
116 }
117
118 #[test]
119 fn elapsed_none() {
120 let mut sw = Stopwatch::default();
121 sw.stop();
122 sw.stop();
123 assert_eq!(sw.elapsed().as_secs_f32(), 0.0);
124 }
125
126 #[test]
127 fn elapsed_ms() {
128 let mut sw = Stopwatch::default();
129 sw.start();
130 sleep_ms(SLEEP_MS);
131 assert_duration_near(sw.elapsed(), SLEEP_MS);
132 }
133
134 #[test]
135 fn stop() {
136 let mut sw = Stopwatch::default();
137 sw.start();
138 sleep_ms(SLEEP_MS);
139 sw.stop();
140 assert_duration_near(sw.elapsed(), SLEEP_MS);
141 sleep_ms(SLEEP_MS);
142 assert_duration_near(sw.elapsed(), SLEEP_MS);
143 }
144
145 #[test]
146 fn resume_once() {
147 let mut sw = Stopwatch::default();
148 assert_eq!(sw.spans.len(), 0);
149 sw.start();
150 assert_eq!(sw.spans.len(), 1);
151 sleep_ms(SLEEP_MS);
152 sw.stop();
153 assert_eq!(sw.spans.len(), 1);
154 assert_duration_near(sw.elapsed(), SLEEP_MS);
155 sw.start();
156 assert_eq!(sw.spans.len(), 2);
157 sleep_ms(SLEEP_MS);
158 assert_duration_near(sw.elapsed(), 2 * SLEEP_MS);
159 }
160
161 #[test]
162 fn resume_twice() {
163 let mut sw = Stopwatch::default();
164 assert_eq!(sw.spans.len(), 0);
165 sw.start();
166 sleep_ms(SLEEP_MS);
167 sw.stop();
168 assert_eq!(sw.spans.len(), 1);
169 assert_duration_near(sw.elapsed(), SLEEP_MS);
170 sw.start();
171 assert_eq!(sw.spans.len(), 2);
172 sleep_ms(SLEEP_MS);
173 sw.start();
174 assert_eq!(sw.spans.len(), 3);
175 assert_duration_near(sw.elapsed(), 2 * SLEEP_MS);
176 sw.start();
177 assert_eq!(sw.spans.len(), 4);
178 sleep_ms(SLEEP_MS);
179 assert_duration_near(sw.elapsed(), 3 * SLEEP_MS);
180 }
181
182 #[test]
183 fn is_running() {
184 let mut sw = Stopwatch::default();
185 assert!(!sw.is_running());
186 sw.start();
187 assert!(sw.is_running());
188 sw.stop();
189 assert!(!sw.is_running());
190 }
191
192 #[test]
193 fn reset() {
194 let mut sw = Stopwatch::default();
195 sw.start();
196 sleep_ms(SLEEP_MS);
197 sw.spans.clear();
198 assert!(!sw.is_running());
199 sw.start();
200 sleep_ms(SLEEP_MS);
201 assert_duration_near(sw.elapsed(), SLEEP_MS);
202 }
203
204 fn sleep_ms(ms: u64) {
206 std::thread::sleep(Duration::from_millis(ms))
207 }
208
209 fn assert_near(x: i64, y: i64, tolerance: u64) {
210 let diff = (x - y).abs() as u64;
211 if diff > tolerance {
212 panic!("Expected {:?}, got {:?}", x, y);
213 }
214 }
215
216 fn assert_duration_near(duration: Duration, elapsed: u64) {
217 let tolerance_value = (TOLERANCE_PERCENTAGE * elapsed as f64) as u64;
218 assert_near(elapsed as i64, duration.as_millis() as i64, tolerance_value);
219 }
220}
221