stopwatch2/
lib.rs

1use std::default::Default;
2use std::fmt;
3use std::time::{Duration, Instant};
4
5/// A span of time that is started but might not have an end yet.
6#[derive(Clone, Debug)]
7pub struct TimeSpan {
8    /// The instant at which the span started.
9    pub start: Instant,
10    /// The instant at which the span stopped, if any.
11    pub stop: Option<Instant>,
12}
13
14/// Converts a TimeSpan into a Duration.
15impl 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/// A stopwatch used to calculate time differences.
26/// # Example
27/// ```rust
28/// use stopwatch2::*;
29///
30/// let mut s = Stopwatch::default();
31/// s.start(); // Starts the stopwatch.
32/// s.start(); // Creates a new time span, which are commonly called "splits".
33/// s.stop(); // Stops the stopwatch.
34/// println!("{}", s); // Prints the total time.
35/// println!("{:?}", s); // Prints the different time spans as debug information.
36/// let total_time = s.elapsed(); // returns the total time as a Duration.
37/// for span in &s.spans {
38///     println!("{:?} -> {:?}", span.start, span.stop);
39/// }
40/// s.spans.clear(); // Reset the stopwatch.
41/// println!("{}", s); // Prints the total time.
42/// println!("{:?}", s); // Prints the different time spans as debug information.
43/// ```
44#[derive(Clone, Default, Debug)]
45pub struct Stopwatch {
46    /// All the time spans that this stopwatch has been or is still running.
47    /// Only the last timespan is allowed to have no stop value, which means it
48    /// is still active.
49    pub spans: Vec<TimeSpan>,
50}
51
52/// Prints the total time this Stopwatch has run.
53impl 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    /// Starts the stopwatch.
61    ///
62    /// If it is already started, it will create a new split.
63    /// This means it will stop and start the stopwatch, creating a new TimeSpan
64    /// in the process.
65    pub fn start(&mut self) -> Option<TimeSpan> {
66        // if no split or last split is stopped, create new one.
67        let ret = self.stop();
68        self.spans.push(TimeSpan {
69            start: Instant::now(),
70            stop: None,
71        });
72        return ret;
73    }
74
75    /// Stops the stopwatch without resetting it.
76    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    /// Returns whether the stopwatch is running.
86    pub fn is_running(&self) -> bool {
87        // if no spans or last span has an end, we are not running.
88        // equiv: if we have splits and the last one has no stop
89        !self.spans.is_empty() && self.spans.last().unwrap().stop.is_none()
90    }
91
92    /// Returns the total elapsed time accumulated inside of this stopwatch.
93    pub fn elapsed(&self) -> Duration {
94        // better way to do the conversion here?
95        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    // helpers
205    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