use nix::sys::resource::{getrusage, Usage, UsageWho};
use nix::sys::time::TimeVal;
use std::fmt::Write as _;
use std::io::Write as _;
use std::time::{Duration, Instant};
enum Unit {
Minutes,
Seconds,
Millis,
Micros,
}
struct TimerSnapshot {
wall_time: Instant,
cpu_fish: Usage,
cpu_children: Usage,
}
pub fn push_timer() -> PrintElapsedOnDrop {
PrintElapsedOnDrop {
start: TimerSnapshot::take(),
}
}
impl TimerSnapshot {
pub fn take() -> TimerSnapshot {
TimerSnapshot {
cpu_fish: getrusage(UsageWho::RUSAGE_SELF).unwrap(),
cpu_children: getrusage(UsageWho::RUSAGE_CHILDREN).unwrap(),
wall_time: Instant::now(),
}
}
pub fn get_delta(t1: &TimerSnapshot, t2: &TimerSnapshot, verbose: bool) -> String {
#[allow(clippy::unnecessary_cast)]
const fn from(val: TimeVal) -> Duration {
let micros = val.tv_sec() as i64 * 1_000_000 + val.tv_usec() as i64;
Duration::from_micros(micros as u64)
}
let mut fish_sys = from(t2.cpu_fish.system_time()) - from(t1.cpu_fish.system_time());
let mut fish_usr = from(t2.cpu_fish.user_time()) - from(t1.cpu_fish.user_time());
let mut child_sys =
from(t2.cpu_children.system_time()) - from(t1.cpu_children.system_time());
let mut child_usr = from(t2.cpu_children.user_time()) - from(t1.cpu_children.user_time());
fish_sys = fish_sys.max(Duration::ZERO);
fish_usr = fish_usr.max(Duration::ZERO);
child_sys = child_sys.max(Duration::ZERO);
child_usr = child_usr.max(Duration::ZERO);
let net_wall_micros = (t2.wall_time - t1.wall_time).as_micros() as i64;
let net_sys_micros = (fish_sys + child_sys).as_micros() as i64;
let net_usr_micros = (fish_usr + child_usr).as_micros() as i64;
let wall_unit = Unit::for_micros(net_wall_micros);
let cpu_unit = Unit::for_micros(net_sys_micros.max(net_usr_micros));
let wall_time = wall_unit.convert_micros(net_wall_micros);
let sys_time = cpu_unit.convert_micros(net_sys_micros);
let usr_time = cpu_unit.convert_micros(net_usr_micros);
let mut output = String::new();
#[rustfmt::skip]
if !verbose {
write!(output, "\n_______________________________").unwrap();
write!(output, "\nExecuted in {:6.2} {}", wall_time, wall_unit.long_name()).unwrap();
write!(output, "\n usr time {:6.2} {}", usr_time, cpu_unit.long_name()).unwrap();
write!(output, "\n sys time {:6.2} {}", sys_time, cpu_unit.long_name()).unwrap();
} else {
let fish_unit = Unit::for_micros(fish_sys.max(fish_usr).as_micros() as i64);
let child_unit = Unit::for_micros(child_sys.max(child_usr).as_micros() as i64);
let fish_usr_time = fish_unit.convert_micros(fish_usr.as_micros() as i64);
let fish_sys_time = fish_unit.convert_micros(fish_sys.as_micros() as i64);
let child_usr_time = child_unit.convert_micros(child_usr.as_micros() as i64);
let child_sys_time = child_unit.convert_micros(child_sys.as_micros() as i64);
let column2_unit_len = wall_unit
.short_name()
.len()
.max(cpu_unit.short_name().len());
let wall_unit = wall_unit.short_name();
let cpu_unit = cpu_unit.short_name();
let fish_unit = fish_unit.short_name();
let child_unit = child_unit.short_name();
output += "\n________________________________________________________";
write!(
output,
"\nExecuted in {wall_time:6.2} {wall_unit:<width1$} {fish:<width2$} external",
width1 = column2_unit_len,
fish = "fish",
width2 = fish_unit.len() + 7
)
.unwrap();
write!(output, "\n usr time {usr_time:6.2} {cpu_unit:<column2_unit_len$} {fish_usr_time:6.2} {fish_unit} {child_usr_time:6.2} {child_unit}").unwrap();
write!(output, "\n sys time {sys_time:6.2} {cpu_unit:<column2_unit_len$} {fish_sys_time:6.2} {fish_unit} {child_sys_time:6.2} {child_unit}").unwrap();
};
output += "\n";
output
}
}
pub struct PrintElapsedOnDrop {
start: TimerSnapshot,
}
impl Drop for PrintElapsedOnDrop {
fn drop(&mut self) {
let end = TimerSnapshot::take();
let output = TimerSnapshot::get_delta(&self.start, &end, true);
let mut stderr = std::io::stderr().lock();
let _ = stderr.write_all(output.as_bytes());
let _ = stderr.write_all(b"\n");
}
}
impl Unit {
const fn for_micros(micros: i64) -> Unit {
match micros {
900_000_001.. => Unit::Minutes,
999_995.. => Unit::Seconds,
1000.. => Unit::Millis,
_ => Unit::Micros,
}
}
const fn short_name(&self) -> &'static str {
match *self {
Unit::Minutes => "mins",
Unit::Seconds => "secs",
Unit::Millis => "millis",
Unit::Micros => "micros",
}
}
const fn long_name(&self) -> &'static str {
match *self {
Unit::Minutes => "minutes",
Unit::Seconds => "seconds",
Unit::Millis => "milliseconds",
Unit::Micros => "microseconds",
}
}
fn convert_micros(&self, micros: i64) -> f64 {
match *self {
Unit::Minutes => micros as f64 / 1.0E6 / 60.0,
Unit::Seconds => micros as f64 / 1.0E6,
Unit::Millis => micros as f64 / 1.0E3,
Unit::Micros => micros as f64 / 1.0,
}
}
}
#[cfg(test)]
mod tests {
use super::TimerSnapshot;
use std::time::Duration;
#[test]
fn timer_format_and_alignment() {
let mut t1 = TimerSnapshot::take();
{
let t1_fish = t1.cpu_fish.as_mut();
t1_fish.ru_utime.tv_usec = 0;
t1_fish.ru_stime.tv_usec = 0;
}
{
let t1_children = t1.cpu_children.as_mut();
t1_children.ru_utime.tv_usec = 0;
t1_children.ru_stime.tv_usec = 0;
}
let mut t2 = TimerSnapshot::take();
{
let t2_fish = t2.cpu_fish.as_mut();
t2_fish.ru_utime.tv_usec = 999995;
t2_fish.ru_stime.tv_usec = 999994;
}
{
let t2_children = t2.cpu_children.as_mut();
t2_children.ru_utime.tv_usec = 1000;
t2_children.ru_stime.tv_usec = 500;
}
t2.wall_time = t1.wall_time + Duration::from_micros(500);
let expected = r#"
________________________________________________________
Executed in 500.00 micros fish external
usr time 1.00 secs 1.00 secs 1.00 millis
sys time 1.00 secs 1.00 secs 0.50 millis
"#;
let actual = TimerSnapshot::get_delta(&t1, &t2, true);
assert_eq!(actual, expected);
}
}