Use wait4 instead of getrusage

This commit is contained in:
David Peter authored and David Peter committed 2026-10-05 15:56:37 +02:00
1 parent 50232feb22
commit ae0035ad56
9 files changed
+143 -98

No files matched your search

+6 -7
View File
@@ -18,6 +18,12 @@ pub struct BenchmarkRun {
/// Time spent in kernel mode /// Time spent in kernel mode
pub system_time: Second, pub system_time: Second,
/// Maximum memory usage of the process, in bytes
pub memory_usage_byte: u64,
/// Exit codes of the process
pub exit_code: Option<i32>,
} }
use crate::benchmark::measurement::Measurements; use crate::benchmark::measurement::Measurements;
@@ -45,13 +51,6 @@ pub struct BenchmarkResult {
/// All run time measurements /// All run time measurements
pub runs: Vec<BenchmarkRun>, pub runs: Vec<BenchmarkRun>,
/// Maximum memory usage of the process, in bytes
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_usage_byte: Option<Vec<u64>>,
/// Exit codes of all command invocations
pub exit_codes: Vec<Option<i32>>,
/// Parameter values for this benchmark /// Parameter values for this benchmark
pub parameters: BTreeMap<String, Parameter>, pub parameters: BTreeMap<String, Parameter>,
} }
+18 -8
View File
@@ -492,15 +492,25 @@ impl<'a> Benchmark<'a> {
command_with_unused_parameters: self.command.get_name_with_unused_parameters(), command_with_unused_parameters: self.command.get_name_with_unused_parameters(),
runs: times_real runs: times_real
.iter() .iter()
.zip(times_user.iter().zip(times_system.iter())) .zip(times_user.iter())
.map(|(wall_clock_time, (user_time, system_time))| BenchmarkRun { .zip(times_system.iter())
wall_clock_time: *wall_clock_time, .zip(memory_usage_byte.iter())
user_time: *user_time, .zip(exit_codes.iter())
system_time: *system_time, .map(
}) |(
(((wall_clock_time, user_time), system_time), memory_usage_byte),
exit_code,
)| {
BenchmarkRun {
wall_clock_time: *wall_clock_time,
user_time: *user_time,
system_time: *system_time,
memory_usage_byte: *memory_usage_byte,
exit_code: *exit_code,
}
},
)
.collect(), .collect(),
memory_usage_byte: Some(memory_usage_byte),
exit_codes,
parameters: self parameters: self
.command .command
.get_parameters() .get_parameters()
+2 -2
View File
@@ -156,9 +156,9 @@ fn create_result(name: &str, mean: f64) -> BenchmarkResult {
wall_clock_time: mean, wall_clock_time: mean,
user_time: mean, user_time: mean,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}], }],
memory_usage_byte: None,
exit_codes: Vec::new(),
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
} }
} }
+8 -12
View File
@@ -209,29 +209,25 @@ fn scheduler_basic() -> Result<()> {
- wall_clock_time: 0.123 - wall_clock_time: 0.123
user_time: 0 user_time: 0
system_time: 0 system_time: 0
memory_usage_byte: 0
exit_code: 0
- wall_clock_time: 0.123 - wall_clock_time: 0.123
user_time: 0 user_time: 0
system_time: 0 system_time: 0
memory_usage_byte: memory_usage_byte: 0
- 0 exit_code: 0
- 0
exit_codes:
- 0
- 0
- command: sleep 0.456 - command: sleep 0.456
runs: runs:
- wall_clock_time: 0.456 - wall_clock_time: 0.456
user_time: 0 user_time: 0
system_time: 0 system_time: 0
memory_usage_byte: 0
exit_code: 0
- wall_clock_time: 0.456 - wall_clock_time: 0.456
user_time: 0 user_time: 0
system_time: 0 system_time: 0
memory_usage_byte: memory_usage_byte: 0
- 0 exit_code: 0
- 0
exit_codes:
- 0
- 0
"#); "#);
Ok(()) Ok(())
+12 -4
View File
@@ -90,20 +90,24 @@ fn test_csv() {
wall_clock_time: 7.0, wall_clock_time: 7.0,
user_time: 7.0, user_time: 7.0,
system_time: 0.0, system_time: 0.0,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 8.0, wall_clock_time: 8.0,
user_time: 8.0, user_time: 8.0,
system_time: 0.0, system_time: 0.0,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 12.0, wall_clock_time: 12.0,
user_time: 12.0, user_time: 12.0,
system_time: 0.0, system_time: 0.0,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: { parameters: {
let mut params = BTreeMap::new(); let mut params = BTreeMap::new();
params.insert( params.insert(
@@ -131,20 +135,24 @@ fn test_csv() {
wall_clock_time: 17.0, wall_clock_time: 17.0,
user_time: 17.0, user_time: 17.0,
system_time: 0.0, system_time: 0.0,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 18.0, wall_clock_time: 18.0,
user_time: 18.0, user_time: 18.0,
system_time: 0.0, system_time: 0.0,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 19.0, wall_clock_time: 19.0,
user_time: 19.0, user_time: 19.0,
system_time: 0.0, system_time: 0.0,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: { parameters: {
let mut params = BTreeMap::new(); let mut params = BTreeMap::new();
params.insert( params.insert(
+48 -16
View File
@@ -33,20 +33,24 @@ fn test_markup_export_auto_ms() {
wall_clock_time: 0.09, wall_clock_time: 0.09,
user_time: 0.09, user_time: 0.09,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.10, wall_clock_time: 0.10,
user_time: 0.10, user_time: 0.10,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.14, wall_clock_time: 0.14,
user_time: 0.14, user_time: 0.14,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
BenchmarkResult { BenchmarkResult {
@@ -57,20 +61,24 @@ fn test_markup_export_auto_ms() {
wall_clock_time: 2.0, wall_clock_time: 2.0,
user_time: 2.0, user_time: 2.0,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 3.0, wall_clock_time: 3.0,
user_time: 3.0, user_time: 3.0,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 4.0, wall_clock_time: 4.0,
user_time: 4.0, user_time: 4.0,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
]; ];
@@ -126,20 +134,24 @@ fn test_markup_export_auto_s() {
wall_clock_time: 2.1, wall_clock_time: 2.1,
user_time: 2.1, user_time: 2.1,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 2.2, wall_clock_time: 2.2,
user_time: 2.2, user_time: 2.2,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 2.3, wall_clock_time: 2.3,
user_time: 2.3, user_time: 2.3,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
BenchmarkResult { BenchmarkResult {
@@ -150,20 +162,24 @@ fn test_markup_export_auto_s() {
wall_clock_time: 0.1, wall_clock_time: 0.1,
user_time: 0.1, user_time: 0.1,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.2, wall_clock_time: 0.2,
user_time: 0.2, user_time: 0.2,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.3, wall_clock_time: 0.3,
user_time: 0.3, user_time: 0.3,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
]; ];
@@ -219,20 +235,24 @@ fn test_markup_export_manual_ms() {
wall_clock_time: 2.1, wall_clock_time: 2.1,
user_time: 2.1, user_time: 2.1,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 2.2, wall_clock_time: 2.2,
user_time: 2.2, user_time: 2.2,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 2.3, wall_clock_time: 2.3,
user_time: 2.3, user_time: 2.3,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
BenchmarkResult { BenchmarkResult {
@@ -243,20 +263,24 @@ fn test_markup_export_manual_ms() {
wall_clock_time: 0.1, wall_clock_time: 0.1,
user_time: 0.1, user_time: 0.1,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.2, wall_clock_time: 0.2,
user_time: 0.2, user_time: 0.2,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.3, wall_clock_time: 0.3,
user_time: 0.3, user_time: 0.3,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
]; ];
@@ -311,20 +335,24 @@ fn test_markup_export_manual_s() {
wall_clock_time: 2.01, wall_clock_time: 2.01,
user_time: 2.01, user_time: 2.01,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 2.02, wall_clock_time: 2.02,
user_time: 2.02, user_time: 2.02,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 2.03, wall_clock_time: 2.03,
user_time: 2.03, user_time: 2.03,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
BenchmarkResult { BenchmarkResult {
@@ -335,20 +363,24 @@ fn test_markup_export_manual_s() {
wall_clock_time: 0.11, wall_clock_time: 0.11,
user_time: 0.11, user_time: 0.11,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.12, wall_clock_time: 0.12,
user_time: 0.12, user_time: 0.12,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
BenchmarkRun { BenchmarkRun {
wall_clock_time: 0.13, wall_clock_time: 0.13,
user_time: 0.13, user_time: 0.13,
system_time: 0., system_time: 0.,
memory_usage_byte: 1024,
exit_code: Some(0),
}, },
], ],
memory_usage_byte: None,
exit_codes: vec![Some(0), Some(0), Some(0)],
parameters: BTreeMap::new(), parameters: BTreeMap::new(),
}, },
]; ];
+13 -2
View File
@@ -24,6 +24,17 @@ use std::process::{ChildStdout, Command};
use anyhow::Result; use anyhow::Result;
/// Used to indicate the result of running a command
#[derive(Debug, Copy, Clone)]
pub struct TimerResult {
pub time_real: Second,
pub time_user: Second,
pub time_system: Second,
pub memory_usage_byte: u64,
/// The exit status of the process
pub status: ExitStatus,
}
/// 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;
@@ -82,8 +93,8 @@ pub fn execute_and_measure(mut command: Command) -> Result<Measurement> {
discard(output); discard(output);
} }
let (time_user, time_system, peak_memory_usage, exit_status) = cpu_timer.stop(child)?; let (status, time_user, time_system, memory_usage_byte) = cpu_timer.stop(child)?;
let time_wall_clock = wallclock_timer.stop(); let time_real = wallclock_timer.stop();
Ok(Measurement { Ok(Measurement {
time_wall_clock, time_wall_clock,
+32 -41
View File
@@ -1,5 +1,6 @@
#![cfg(not(windows))] #![cfg(not(windows))]
use std::convert::TryFrom;
use std::io; use std::io;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::os::unix::process::ExitStatusExt; use std::os::unix::process::ExitStatusExt;
@@ -7,26 +8,24 @@ use std::process::{Child, ExitStatus};
use anyhow::Result; use anyhow::Result;
use crate::quantity::{byte, kibibyte, microsecond, second, Information, Time}; use crate::util::units::Second;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct ResourceUsage { struct ResourceUsage {
/// Total amount of time spent executing in user mode /// Total amount of time spent executing in user mode
pub time_user: Time, pub user_usec: Second,
/// Total amount of time spent executing in kernel mode /// Total amount of time spent executing in kernel mode
pub time_system: Time, pub system_usec: Second,
/// Maximum amount of memory used by the process, in bytes /// Maximum amount of memory used by the process, in bytes
pub memory_usage: Information, pub memory_usage_byte: i64,
} }
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
fn convert_timeval(tv: libc::timeval) -> Time { fn timeval_to_seconds(tv: libc::timeval) -> Second {
let sec = tv.tv_sec as f64; const MICROSEC_PER_SEC: i64 = 1000 * 1000;
let usec = tv.tv_usec as f64; (i64::from(tv.tv_sec) * MICROSEC_PER_SEC + i64::from(tv.tv_usec)) as f64 * 1e-6
Time::new::<second>(sec) + Time::new::<microsecond>(usec)
} }
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
@@ -37,37 +36,29 @@ fn wait4(mut child: Child) -> io::Result<(ExitStatus, ResourceUsage)> {
let mut status = 0; let mut status = 0;
let mut rusage = MaybeUninit::zeroed(); let mut rusage = MaybeUninit::zeroed();
loop { let result = unsafe { libc::wait4(pid, &mut status, 0, rusage.as_mut_ptr()) };
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);
}
}
let rusage = unsafe { rusage.assume_init() }; if result < 0 {
Err(io::Error::last_os_error())
let memory_usage_byte = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
// Linux and *BSD return the value in KibiBytes, Darwin flavors in bytes
// 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
// is larger than 1 byte, but that's considered acceptable.
Information::new::<byte>(rusage.ru_maxrss as f64)
} else { } else {
Information::new::<kibibyte>(rusage.ru_maxrss as f64) let rusage = unsafe { rusage.assume_init() };
};
Ok(( let memory_usage_byte = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
ExitStatus::from_raw(status), // Linux and *BSD return the value in KibiBytes, Darwin flavors in bytes
ResourceUsage { rusage.ru_maxrss
time_user: convert_timeval(rusage.ru_utime), } else {
time_system: convert_timeval(rusage.ru_stime), rusage.ru_maxrss * 1024
memory_usage: memory_usage_byte.into(), };
},
)) Ok((
ExitStatus::from_raw(status),
ResourceUsage {
user_usec: timeval_to_seconds(rusage.ru_utime),
system_usec: timeval_to_seconds(rusage.ru_stime),
memory_usage_byte: memory_usage_byte.into(),
},
))
}
} }
pub struct CPUTimer {} pub struct CPUTimer {}
@@ -77,13 +68,13 @@ impl CPUTimer {
Self {} Self {}
} }
pub fn stop(&self, child: Child) -> Result<(Time, Time, Information, ExitStatus)> { pub fn stop(&self, child: Child) -> Result<(ExitStatus, Second, Second, u64)> {
let (status, usage) = wait4(child)?; let (status, usage) = wait4(child)?;
Ok(( Ok((
usage.time_user,
usage.time_system,
usage.memory_usage,
status, status,
usage.user_usec,
usage.system_usec,
u64::try_from(usage.memory_usage_byte).unwrap_or(0),
)) ))
} }
} }
+4 -6
View File
@@ -84,7 +84,7 @@ impl CPUTimer {
Self { job_object } Self { job_object }
} }
pub fn stop(&self, mut child: Child) -> Result<(Time, Time, Information, ExitStatus)> { pub fn stop(&self, mut child: Child) -> Result<(ExitStatus, Second, Second, u64)> {
let status = child.wait()?; let status = child.wait()?;
let mut job_object_info = let mut job_object_info =
@@ -113,12 +113,10 @@ impl CPUTimer {
// The `TotalKernelTime` is "The total amount of kernel-mode execution time // The `TotalKernelTime` is "The total amount of kernel-mode execution time
// for all active processes associated with the job, as well as all terminated // for all active processes associated with the job, as well as all terminated
// processes no longer associated with the job, in 100-nanosecond ticks." // processes no longer associated with the job, in 100-nanosecond ticks."
let system_time = let kernel: i64 = job_object_info.TotalKernelTime / HUNDRED_NS_PER_MS;
Time::new::<nanosecond>((job_object_info.TotalKernelTime as f64) * 100.0); Ok((status, user as f64 * 1e-6, kernel as f64 * 1e-6, 0))
Ok((user_time, system_time, Information::zero(), status))
} else { } else {
Ok((Time::zero(), Time::zero(), Information::zero(), status)) Ok((status, 0.0, 0.0, 0))
} }
} }
} }