use std::{
cmp::max,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
use human_repr::HumanDuration;
pub(crate) trait SystemTimeExt {
fn from_unix(t: u64) -> Self;
fn to_unix(self) -> u64;
}
impl SystemTimeExt for SystemTime {
fn from_unix(t: u64) -> Self {
UNIX_EPOCH + Duration::from_secs(t)
}
fn to_unix(self) -> u64 {
self.duration_since(UNIX_EPOCH)
.map_or_else(|_| 0, |d| d.as_secs())
}
}
#[derive(Debug, Default, Clone)]
pub(crate) struct Stopwatch {
pub name: String,
start_: Option<Instant>,
stop_: Option<Instant>,
}
impl Stopwatch {
#[must_use]
pub(crate) fn new(name: &str) -> Self {
Self {
name: name.to_string(),
start_: Some(Instant::now()),
stop_: None,
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
pub(crate) fn start(&mut self) {
assert!(self.start_.is_none(), "Stopwatch already started");
self.start_ = Some(Instant::now());
}
#[must_use]
pub(crate) fn stop(&mut self) -> Option<Duration> {
assert!(self.stop_.is_none(), "Stopwatch already stopped");
self.stop_ = Some(Instant::now());
self.elapsed()
}
#[must_use]
pub(crate) fn elapsed(&self) -> Option<Duration> {
if let Some(start) = self.start_
&& let Some(stop) = self.stop_
{
return Some(stop - start);
}
None
}
#[must_use]
pub(crate) fn chain(&mut self, new_name: &str) -> Self {
let _ = self.stop();
Self {
name: new_name.to_string(),
start_: self.stop_,
stop_: None,
}
}
fn fmt_ln(&self, f: &mut std::fmt::Formatter<'_>, width: usize) -> std::fmt::Result {
let t = self.elapsed();
if let Some(t) = t {
writeln!(f, " {:width$}: {}", self.name, t.human_duration())
} else {
writeln!(f, " {:width$}: None", self.name)
}
}
}
#[derive(Debug, Default, Clone)]
pub(crate) struct StopwatchChain {
watches: Vec<Stopwatch>,
}
impl StopwatchChain {
pub(crate) fn next(&mut self, name: &str) {
let new1 = match self.watches.last_mut() {
None => Stopwatch::new(name),
Some(latest) => latest.chain(name),
};
self.watches.push(new1);
}
pub(crate) fn stop(&mut self) {
let _ = self.watches.last_mut().map(Stopwatch::stop);
}
#[must_use]
pub(crate) fn find(&self, name: &str) -> Option<&Stopwatch> {
self.watches.iter().find(|&sw| sw.name == name)
}
}
impl std::fmt::Display for StopwatchChain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut largest = 0usize;
for sw in &self.watches {
largest = max(largest, sw.name.len());
}
for sw in &self.watches {
sw.fmt_ln(f, largest)?;
}
Ok(())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::time::SystemTime;
use crate::util::time::SystemTimeExt as _;
use super::{Stopwatch, StopwatchChain};
#[test]
fn new_stopwatch_is_running() {
let mut a = Stopwatch::new("");
assert!(a.stop().is_some());
}
#[test]
fn default_stopwatch_is_not_running() {
let mut a = Stopwatch::default();
assert!(a.stop().is_none());
}
#[test]
#[should_panic(expected = "Stopwatch already started")]
fn cannot_start_twice() {
let mut a = Stopwatch::new("a");
a.start();
}
#[test]
#[should_panic(expected = "Stopwatch already stopped")]
fn cannot_stop() {
let mut a = Stopwatch::new("a");
let _ = a.stop();
let _ = a.stop();
}
#[test]
fn empty_chain() {
let c = StopwatchChain::default();
println!("{c}");
}
#[test]
fn running_chain() {
let mut c = StopwatchChain::default();
c.next("a");
c.next("b");
c.next("c");
println!("{c}");
}
#[test]
fn finished_chain() {
let mut c = StopwatchChain::default();
c.next("a");
c.next("b");
c.next("c");
c.stop();
println!("{c}");
}
#[test]
#[should_panic(expected = "Stopwatch already stopped")]
fn cannot_restart_stopped_chain() {
let mut c = StopwatchChain::default();
c.next("a");
c.stop();
c.next("b");
}
#[test]
fn time_conversions() {
let st = SystemTime::from_unix(0);
assert_eq!(st, SystemTime::UNIX_EPOCH);
assert_eq!(st.to_unix(), 0);
}
}