mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-01 17:00:18 +00:00
Merge pull request #965 from sharkdp/codex/wait4-resource-usage
Use `wait4` for per-command resource usage on Unix
This commit is contained in:
3 files changed
+77
-101
No files matched your search
+3
-1
@@ -3,6 +3,8 @@ use std::marker::PhantomData;
|
|||||||
pub use uom::num_traits::Zero;
|
pub use uom::num_traits::Zero;
|
||||||
pub use uom::si::f64::{Information, Ratio, Time};
|
pub use uom::si::f64::{Information, Ratio, Time};
|
||||||
pub use uom::si::information::byte;
|
pub use uom::si::information::byte;
|
||||||
|
#[cfg(any(not(windows), test))]
|
||||||
|
pub use uom::si::information::kibibyte;
|
||||||
pub use uom::si::ratio::ratio;
|
pub use uom::si::ratio::ratio;
|
||||||
#[cfg(any(not(windows), test))]
|
#[cfg(any(not(windows), test))]
|
||||||
pub use uom::si::time::microsecond;
|
pub use uom::si::time::microsecond;
|
||||||
@@ -55,7 +57,7 @@ pub fn format_duration_value(duration: f64, unit: Option<TimeUnit>) -> (String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use uom::si::{information::kibibyte, time::millisecond};
|
use uom::si::time::millisecond;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_time() {
|
fn test_time() {
|
||||||
|
|||||||
+6
-13
@@ -24,19 +24,6 @@ use std::process::{ChildStdout, Command};
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
struct CPUTimes {
|
|
||||||
/// Total amount of time spent executing in user mode
|
|
||||||
pub user_usec: i64,
|
|
||||||
|
|
||||||
/// Total amount of time spent executing in kernel mode
|
|
||||||
pub system_usec: i64,
|
|
||||||
|
|
||||||
/// Maximum amount of memory used by the process, in bytes
|
|
||||||
pub memory_usage_byte: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Discard the output of a child process.
|
/// Discard the output of a child process.
|
||||||
fn discard(output: ChildStdout) {
|
fn discard(output: ChildStdout) {
|
||||||
const CHUNK_SIZE: usize = 64 << 10;
|
const CHUNK_SIZE: usize = 64 << 10;
|
||||||
@@ -95,9 +82,15 @@ pub fn execute_and_measure(mut command: Command) -> Result<Measurement> {
|
|||||||
discard(output);
|
discard(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let (time_user, time_system, peak_memory_usage, exit_status) = cpu_timer.stop(child)?;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
let exit_status = child.wait()?;
|
let exit_status = child.wait()?;
|
||||||
|
|
||||||
let time_wall_clock = wallclock_timer.stop();
|
let time_wall_clock = wallclock_timer.stop();
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
let (time_user, time_system, peak_memory_usage) = cpu_timer.stop();
|
let (time_user, time_system, peak_memory_usage) = cpu_timer.stop();
|
||||||
|
|
||||||
Ok(Measurement {
|
Ok(Measurement {
|
||||||
|
|||||||
+68
-87
@@ -1,108 +1,89 @@
|
|||||||
#![cfg(not(windows))]
|
#![cfg(not(windows))]
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::io;
|
||||||
use std::mem;
|
use std::mem::MaybeUninit;
|
||||||
|
use std::os::unix::process::ExitStatusExt;
|
||||||
|
use std::process::{Child, ExitStatus};
|
||||||
|
|
||||||
use crate::quantity::{byte, microsecond, Information, Time};
|
use anyhow::Result;
|
||||||
use crate::timer::CPUTimes;
|
|
||||||
|
use crate::quantity::{byte, kibibyte, microsecond, second, Information, Time};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct CPUInterval {
|
struct ResourceUsage {
|
||||||
/// Total amount of time spent executing in user mode
|
/// Total amount of time spent executing in user mode
|
||||||
pub user: Time,
|
pub time_user: Time,
|
||||||
|
|
||||||
/// Total amount of time spent executing in kernel mode
|
/// Total amount of time spent executing in kernel mode
|
||||||
pub system: Time,
|
pub time_system: Time,
|
||||||
|
|
||||||
|
/// Maximum amount of memory used by the process, in bytes
|
||||||
|
pub memory_usage: Information,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CPUTimer {
|
#[allow(clippy::useless_conversion)]
|
||||||
start_cpu: CPUTimes,
|
fn convert_timeval(tv: libc::timeval) -> Time {
|
||||||
|
let sec = tv.tv_sec as f64;
|
||||||
|
let usec = tv.tv_usec as f64;
|
||||||
|
|
||||||
|
Time::new::<second>(sec) + Time::new::<microsecond>(usec)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CPUTimer {
|
#[allow(clippy::useless_conversion)]
|
||||||
pub fn start() -> Self {
|
fn wait4(mut child: Child) -> io::Result<(ExitStatus, ResourceUsage)> {
|
||||||
CPUTimer {
|
drop(child.stdin.take());
|
||||||
start_cpu: get_cpu_times(),
|
|
||||||
|
let pid = child.id() as i32;
|
||||||
|
let mut status = 0;
|
||||||
|
let mut rusage = MaybeUninit::zeroed();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let result = unsafe { libc::wait4(pid, &mut status, 0, rusage.as_mut_ptr()) };
|
||||||
|
if result >= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let error = io::Error::last_os_error();
|
||||||
|
if error.kind() != io::ErrorKind::Interrupted {
|
||||||
|
return Err(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&self) -> (Time, Time, Information) {
|
let rusage = unsafe { rusage.assume_init() };
|
||||||
let end_cpu = get_cpu_times();
|
|
||||||
let cpu_interval = cpu_time_interval(&self.start_cpu, &end_cpu);
|
|
||||||
(
|
|
||||||
cpu_interval.user,
|
|
||||||
cpu_interval.system,
|
|
||||||
Information::new::<byte>(end_cpu.memory_usage_byte as f64),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read CPU execution times ('user' and 'system')
|
let memory_usage_byte = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
|
||||||
fn get_cpu_times() -> CPUTimes {
|
// Linux and *BSD return the value in KibiBytes, Darwin flavors in bytes
|
||||||
use libc::{getrusage, rusage, RUSAGE_CHILDREN};
|
// A f64 can represent integers up to 2^53 exactly, so we can represent
|
||||||
|
// all values up to 2^53 bytes = 8 PiB exactly. Beyond that, our precision
|
||||||
let result: rusage = unsafe {
|
// is larger than 1 byte, but that's considered acceptable.
|
||||||
let mut buf = mem::zeroed();
|
Information::new::<byte>(rusage.ru_maxrss as f64)
|
||||||
let success = getrusage(RUSAGE_CHILDREN, &mut buf);
|
|
||||||
assert_eq!(0, success);
|
|
||||||
buf
|
|
||||||
};
|
|
||||||
|
|
||||||
const MICROSEC_PER_SEC: i64 = 1000 * 1000;
|
|
||||||
|
|
||||||
// Linux and *BSD return the value in KibiBytes, Darwin flavors in bytes
|
|
||||||
let max_rss_byte = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
|
|
||||||
result.ru_maxrss
|
|
||||||
} else {
|
} else {
|
||||||
result.ru_maxrss * 1024
|
Information::new::<kibibyte>(rusage.ru_maxrss as f64)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(clippy::useless_conversion)]
|
Ok((
|
||||||
CPUTimes {
|
ExitStatus::from_raw(status),
|
||||||
user_usec: i64::from(result.ru_utime.tv_sec) * MICROSEC_PER_SEC
|
ResourceUsage {
|
||||||
+ i64::from(result.ru_utime.tv_usec),
|
time_user: convert_timeval(rusage.ru_utime),
|
||||||
system_usec: i64::from(result.ru_stime.tv_sec) * MICROSEC_PER_SEC
|
time_system: convert_timeval(rusage.ru_stime),
|
||||||
+ i64::from(result.ru_stime.tv_usec),
|
memory_usage: memory_usage_byte.into(),
|
||||||
memory_usage_byte: u64::try_from(max_rss_byte).unwrap_or(0),
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CPUTimer {}
|
||||||
|
|
||||||
|
impl CPUTimer {
|
||||||
|
pub fn start() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&self, child: Child) -> Result<(Time, Time, Information, ExitStatus)> {
|
||||||
|
let (status, usage) = wait4(child)?;
|
||||||
|
Ok((
|
||||||
|
usage.time_user,
|
||||||
|
usage.time_system,
|
||||||
|
usage.memory_usage,
|
||||||
|
status,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the time intervals in between two `CPUTimes` snapshots
|
|
||||||
fn cpu_time_interval(start: &CPUTimes, end: &CPUTimes) -> CPUInterval {
|
|
||||||
CPUInterval {
|
|
||||||
user: Time::new::<microsecond>((end.user_usec - start.user_usec) as f64),
|
|
||||||
system: Time::new::<microsecond>((end.system_usec - start.system_usec) as f64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use crate::quantity::second;
|
|
||||||
#[cfg(test)]
|
|
||||||
use approx::assert_relative_eq;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cpu_time_interval() {
|
|
||||||
let t_a = CPUTimes {
|
|
||||||
user_usec: 12345,
|
|
||||||
system_usec: 54321,
|
|
||||||
memory_usage_byte: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let t_b = CPUTimes {
|
|
||||||
user_usec: 20000,
|
|
||||||
system_usec: 70000,
|
|
||||||
memory_usage_byte: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let t_zero = cpu_time_interval(&t_a, &t_a);
|
|
||||||
assert!(t_zero.user.get::<second>().abs() < f64::EPSILON);
|
|
||||||
assert!(t_zero.system.get::<second>().abs() < f64::EPSILON);
|
|
||||||
|
|
||||||
let t_ab = cpu_time_interval(&t_a, &t_b);
|
|
||||||
assert_relative_eq!(0.007655, t_ab.user.get::<second>());
|
|
||||||
assert_relative_eq!(0.015679, t_ab.system.get::<second>());
|
|
||||||
|
|
||||||
let t_ba = cpu_time_interval(&t_b, &t_a);
|
|
||||||
assert_relative_eq!(-0.007655, t_ba.user.get::<second>());
|
|
||||||
assert_relative_eq!(-0.015679, t_ba.system.get::<second>());
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user