use std::default::Default;
use std::fmt;
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct TimeSpan {
pub start: Instant,
pub stop: Option<Instant>,
}
impl Into<Duration> for TimeSpan {
fn into(self) -> Duration {
if let Some(stop) = self.stop {
stop - self.start
} else {
self.start.elapsed()
}
}
}
#[derive(Clone, Default, Debug)]
pub struct Stopwatch {
pub spans: Vec<TimeSpan>,
}
impl fmt::Display for Stopwatch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
return write!(f, "{}s", self.elapsed().as_secs_f64());
}
}
impl Stopwatch {
pub fn start(&mut self) -> Option<TimeSpan> {
let ret = self.stop();
self.spans.push(TimeSpan {
start: Instant::now(),
stop: None,
});
return ret;
}
pub fn stop(&mut self) -> Option<TimeSpan> {
let mut ret = None;
if self.is_running() {
self.spans.last_mut().unwrap().stop = Some(Instant::now());
ret = Some(self.spans.last().unwrap().clone());
}
return ret;
}
pub fn is_running(&self) -> bool {
!self.spans.is_empty() && self.spans.last().unwrap().stop.is_none()
}
pub fn elapsed(&self) -> Duration {
self.spans.iter().map(|s| {let d: Duration = s.clone().into(); d}).sum()
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::*;
static SLEEP_MS: u64 = 50;
static TOLERANCE_PERCENTAGE: f64 = 0.3;
#[test]
fn repeated_stops() {
let mut sw = Stopwatch::default();
for _ in 0..1000 {
sw.start();
}
sw.stop();
assert_eq!(sw.spans.len(), 1000);
assert!(sw.spans.last().unwrap().stop.is_some());
}
#[test]
fn elapsed_none() {
let mut sw = Stopwatch::default();
sw.stop();
sw.stop();
assert_eq!(sw.elapsed().as_secs_f32(), 0.0);
}
#[test]
fn elapsed_ms() {
let mut sw = Stopwatch::default();
sw.start();
sleep_ms(SLEEP_MS);
assert_duration_near(sw.elapsed(), SLEEP_MS);
}
#[test]
fn stop() {
let mut sw = Stopwatch::default();
sw.start();
sleep_ms(SLEEP_MS);
sw.stop();
assert_duration_near(sw.elapsed(), SLEEP_MS);
sleep_ms(SLEEP_MS);
assert_duration_near(sw.elapsed(), SLEEP_MS);
}
#[test]
fn resume_once() {
let mut sw = Stopwatch::default();
assert_eq!(sw.spans.len(), 0);
sw.start();
assert_eq!(sw.spans.len(), 1);
sleep_ms(SLEEP_MS);
sw.stop();
assert_eq!(sw.spans.len(), 1);
assert_duration_near(sw.elapsed(), SLEEP_MS);
sw.start();
assert_eq!(sw.spans.len(), 2);
sleep_ms(SLEEP_MS);
assert_duration_near(sw.elapsed(), 2 * SLEEP_MS);
}
#[test]
fn resume_twice() {
let mut sw = Stopwatch::default();
assert_eq!(sw.spans.len(), 0);
sw.start();
sleep_ms(SLEEP_MS);
sw.stop();
assert_eq!(sw.spans.len(), 1);
assert_duration_near(sw.elapsed(), SLEEP_MS);
sw.start();
assert_eq!(sw.spans.len(), 2);
sleep_ms(SLEEP_MS);
sw.start();
assert_eq!(sw.spans.len(), 3);
assert_duration_near(sw.elapsed(), 2 * SLEEP_MS);
sw.start();
assert_eq!(sw.spans.len(), 4);
sleep_ms(SLEEP_MS);
assert_duration_near(sw.elapsed(), 3 * SLEEP_MS);
}
#[test]
fn is_running() {
let mut sw = Stopwatch::default();
assert!(!sw.is_running());
sw.start();
assert!(sw.is_running());
sw.stop();
assert!(!sw.is_running());
}
#[test]
fn reset() {
let mut sw = Stopwatch::default();
sw.start();
sleep_ms(SLEEP_MS);
sw.spans.clear();
assert!(!sw.is_running());
sw.start();
sleep_ms(SLEEP_MS);
assert_duration_near(sw.elapsed(), SLEEP_MS);
}
fn sleep_ms(ms: u64) {
std::thread::sleep(Duration::from_millis(ms))
}
fn assert_near(x: i64, y: i64, tolerance: u64) {
let diff = (x - y).abs() as u64;
if diff > tolerance {
panic!("Expected {:?}, got {:?}", x, y);
}
}
fn assert_duration_near(duration: Duration, elapsed: u64) {
let tolerance_value = (TOLERANCE_PERCENTAGE * elapsed as f64) as u64;
assert_near(elapsed as i64, duration.as_millis() as i64, tolerance_value);
}
}