use std::time::{Duration, Instant};
pub fn measure_duration<T, F: FnOnce() -> T>(f: F) -> (T, Duration) {
let start = Instant::now();
let result = f();
let duration = start.elapsed();
(result, duration)
}
#[derive(Debug, Clone)]
pub struct Stopwatch {
start: Instant,
last_lap: Instant,
laps: Vec<(String, Duration)>,
}
impl Default for Stopwatch {
fn default() -> Self {
Self::new()
}
}
impl Stopwatch {
#[must_use]
pub fn new() -> Self {
let now = Instant::now();
Self {
start: now,
last_lap: now,
laps: Vec::new(),
}
}
#[must_use]
pub fn stopped() -> Self {
let now = Instant::now();
Self {
start: now,
last_lap: now,
laps: Vec::new(),
}
}
pub fn restart(&mut self) {
let now = Instant::now();
self.start = now;
self.last_lap = now;
self.laps.clear();
}
pub fn lap(&mut self, name: impl Into<String>) {
let now = Instant::now();
let duration = now - self.last_lap;
self.laps.push((name.into(), duration));
self.last_lap = now;
}
#[must_use]
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
#[must_use]
pub fn since_last_lap(&self) -> Duration {
self.last_lap.elapsed()
}
#[must_use]
pub fn laps(&self) -> &[(String, Duration)] {
&self.laps
}
#[must_use]
pub fn lap_count(&self) -> usize {
self.laps.len()
}
#[must_use]
pub fn get_lap(&self, name: &str) -> Option<Duration> {
self.laps.iter().find(|(n, _)| n == name).map(|(_, d)| *d)
}
#[must_use]
pub fn total_lap_time(&self) -> Duration {
self.laps.iter().map(|(_, d)| *d).sum()
}
#[must_use]
pub fn stats(&self) -> TimingStats {
if self.laps.is_empty() {
return TimingStats {
count: 0,
total: Duration::ZERO,
min: None,
max: None,
mean: None,
};
}
let durations: Vec<_> = self.laps.iter().map(|(_, d)| *d).collect();
let total: Duration = durations.iter().sum();
let min = durations.iter().min().copied();
let max = durations.iter().max().copied();
let mean = Some(total / durations.len() as u32);
TimingStats {
count: durations.len(),
total,
min,
max,
mean,
}
}
#[must_use]
pub fn report(&self) -> String {
let mut lines = Vec::new();
lines.push(format!("Total elapsed: {:?}", self.elapsed()));
lines.push(format!("Laps: {}", self.lap_count()));
if !self.laps.is_empty() {
lines.push(String::new());
for (name, duration) in &self.laps {
lines.push(format!(" {name}: {duration:?}"));
}
let stats = self.stats();
lines.push(String::new());
lines.push("Statistics:".to_string());
if let Some(min) = stats.min {
lines.push(format!(" Min: {min:?}"));
}
if let Some(max) = stats.max {
lines.push(format!(" Max: {max:?}"));
}
if let Some(mean) = stats.mean {
lines.push(format!(" Mean: {mean:?}"));
}
}
lines.join("\n")
}
}
#[derive(Debug, Clone)]
pub struct TimingStats {
pub count: usize,
pub total: Duration,
pub min: Option<Duration>,
pub max: Option<Duration>,
pub mean: Option<Duration>,
}
impl TimingStats {
#[must_use]
pub fn is_empty(&self) -> bool {
self.count == 0
}
}
#[derive(Debug, Clone)]
pub struct Timer {
start: Instant,
duration: Duration,
}
impl Timer {
#[must_use]
pub fn new(duration: Duration) -> Self {
Self {
start: Instant::now(),
duration,
}
}
#[must_use]
pub fn from_secs(secs: u64) -> Self {
Self::new(Duration::from_secs(secs))
}
#[must_use]
pub fn from_millis(ms: u64) -> Self {
Self::new(Duration::from_millis(ms))
}
#[must_use]
pub fn is_expired(&self) -> bool {
self.start.elapsed() >= self.duration
}
#[must_use]
pub fn remaining(&self) -> Duration {
let elapsed = self.start.elapsed();
if elapsed >= self.duration {
Duration::ZERO
} else {
self.duration - elapsed
}
}
pub fn reset(&mut self) {
self.start = Instant::now();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
#[test]
fn test_measure_duration() {
let (result, duration) = measure_duration(|| {
sleep(Duration::from_millis(10));
42
});
assert_eq!(result, 42);
assert!(duration >= Duration::from_millis(10));
}
#[test]
fn test_stopwatch_basic() {
let mut sw = Stopwatch::new();
sleep(Duration::from_millis(10));
sw.lap("first");
sleep(Duration::from_millis(10));
sw.lap("second");
assert_eq!(sw.lap_count(), 2);
assert!(sw.elapsed() >= Duration::from_millis(20));
}
#[test]
fn test_stopwatch_get_lap() {
let mut sw = Stopwatch::new();
sleep(Duration::from_millis(5));
sw.lap("test_lap");
let lap = sw.get_lap("test_lap");
assert!(lap.is_some());
assert!(lap.unwrap() >= Duration::from_millis(5));
assert!(sw.get_lap("nonexistent").is_none());
}
#[test]
fn test_stopwatch_stats() {
let mut sw = Stopwatch::new();
sw.lap("a");
sw.lap("b");
sw.lap("c");
let stats = sw.stats();
assert_eq!(stats.count, 3);
assert!(stats.min.is_some());
assert!(stats.max.is_some());
assert!(stats.mean.is_some());
}
#[test]
fn test_stopwatch_empty_stats() {
let sw = Stopwatch::new();
let stats = sw.stats();
assert_eq!(stats.count, 0);
assert!(stats.is_empty());
}
#[test]
fn test_stopwatch_restart() {
let mut sw = Stopwatch::new();
sw.lap("first");
sw.restart();
assert_eq!(sw.lap_count(), 0);
}
#[test]
fn test_timer_basic() {
let timer = Timer::from_millis(50);
assert!(!timer.is_expired());
assert!(timer.remaining() > Duration::ZERO);
sleep(Duration::from_millis(60));
assert!(timer.is_expired());
assert_eq!(timer.remaining(), Duration::ZERO);
}
#[test]
fn test_timer_reset() {
let mut timer = Timer::from_millis(100);
sleep(Duration::from_millis(50));
timer.reset();
assert!(timer.remaining() > Duration::from_millis(80));
}
#[test]
fn test_stopwatch_report() {
let mut sw = Stopwatch::new();
sw.lap("setup");
sw.lap("execute");
sw.lap("teardown");
let report = sw.report();
assert!(report.contains("Total elapsed"));
assert!(report.contains("Laps: 3"));
assert!(report.contains("setup"));
assert!(report.contains("execute"));
assert!(report.contains("teardown"));
}
#[test]
fn stopwatch_stopped_constructor() {
let sw = Stopwatch::stopped();
assert_eq!(sw.lap_count(), 0);
assert!(sw.laps().is_empty());
}
#[test]
fn stopwatch_default_matches_new() {
let def = Stopwatch::default();
let new = Stopwatch::new();
assert_eq!(def.lap_count(), new.lap_count());
}
#[test]
fn stopwatch_since_last_lap() {
let sw = Stopwatch::new();
let since = sw.since_last_lap();
assert!(since < Duration::from_secs(1));
}
#[test]
fn stopwatch_total_lap_time() {
let mut sw = Stopwatch::new();
sw.lap("a");
sw.lap("b");
let total = sw.total_lap_time();
assert!(total > Duration::ZERO);
}
#[test]
fn stopwatch_debug_and_clone() {
let mut sw = Stopwatch::new();
sw.lap("x");
let debug = format!("{sw:?}");
assert!(debug.contains("Stopwatch"));
let cloned = sw.clone();
assert_eq!(cloned.lap_count(), 1);
}
#[test]
fn timing_stats_debug_clone_and_is_empty() {
let mut sw = Stopwatch::new();
sw.lap("a");
let stats = sw.stats();
let debug = format!("{stats:?}");
assert!(debug.contains("TimingStats"));
let cloned = stats.clone();
assert_eq!(cloned.count, 1);
assert!(!cloned.is_empty());
}
#[test]
fn timer_from_secs() {
let timer = Timer::from_secs(60);
assert!(!timer.is_expired());
assert!(timer.remaining() > Duration::from_secs(59));
}
#[test]
fn timer_debug_and_clone() {
let timer = Timer::from_millis(100);
let debug = format!("{timer:?}");
assert!(debug.contains("Timer"));
let cloned = timer.clone();
assert!(!cloned.is_expired());
}
#[test]
fn report_empty_laps() {
let sw = Stopwatch::new();
let report = sw.report();
assert!(report.contains("Laps: 0"));
assert!(!report.contains("Statistics"));
}
}