From ae0035ad56e949cdc36f74e5faf9689f74cea0f0 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 5 Oct 2026 14:14:25 +0200 Subject: [PATCH] Use wait4 instead of getrusage --- src/benchmark/benchmark_result.rs | 13 +++--- src/benchmark/mod.rs | 26 +++++++---- src/benchmark/relative_speed.rs | 4 +- src/benchmark/scheduler.rs | 20 ++++----- src/export/csv.rs | 16 +++++-- src/export/tests.rs | 64 ++++++++++++++++++++------- src/timer/mod.rs | 15 ++++++- src/timer/unix_timer.rs | 73 ++++++++++++++----------------- src/timer/windows_timer.rs | 10 ++--- 9 files changed, 143 insertions(+), 98 deletions(-) diff --git a/src/benchmark/benchmark_result.rs b/src/benchmark/benchmark_result.rs index 5913c83..5d0e638 100644 --- a/src/benchmark/benchmark_result.rs +++ b/src/benchmark/benchmark_result.rs @@ -18,6 +18,12 @@ pub struct BenchmarkRun { /// Time spent in kernel mode 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, } use crate::benchmark::measurement::Measurements; @@ -45,13 +51,6 @@ pub struct BenchmarkResult { /// All run time measurements pub runs: Vec, - /// Maximum memory usage of the process, in bytes - #[serde(skip_serializing_if = "Option::is_none")] - pub memory_usage_byte: Option>, - - /// Exit codes of all command invocations - pub exit_codes: Vec>, - /// Parameter values for this benchmark pub parameters: BTreeMap, } diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs index 4a23dbf..f4cf90a 100644 --- a/src/benchmark/mod.rs +++ b/src/benchmark/mod.rs @@ -492,15 +492,25 @@ impl<'a> Benchmark<'a> { command_with_unused_parameters: self.command.get_name_with_unused_parameters(), runs: times_real .iter() - .zip(times_user.iter().zip(times_system.iter())) - .map(|(wall_clock_time, (user_time, system_time))| BenchmarkRun { - wall_clock_time: *wall_clock_time, - user_time: *user_time, - system_time: *system_time, - }) + .zip(times_user.iter()) + .zip(times_system.iter()) + .zip(memory_usage_byte.iter()) + .zip(exit_codes.iter()) + .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(), - memory_usage_byte: Some(memory_usage_byte), - exit_codes, parameters: self .command .get_parameters() diff --git a/src/benchmark/relative_speed.rs b/src/benchmark/relative_speed.rs index f20b5de..ac8b4e6 100644 --- a/src/benchmark/relative_speed.rs +++ b/src/benchmark/relative_speed.rs @@ -156,9 +156,9 @@ fn create_result(name: &str, mean: f64) -> BenchmarkResult { wall_clock_time: mean, user_time: mean, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }], - memory_usage_byte: None, - exit_codes: Vec::new(), parameters: BTreeMap::new(), } } diff --git a/src/benchmark/scheduler.rs b/src/benchmark/scheduler.rs index a1b1953..2944c22 100644 --- a/src/benchmark/scheduler.rs +++ b/src/benchmark/scheduler.rs @@ -209,29 +209,25 @@ fn scheduler_basic() -> Result<()> { - wall_clock_time: 0.123 user_time: 0 system_time: 0 + memory_usage_byte: 0 + exit_code: 0 - wall_clock_time: 0.123 user_time: 0 system_time: 0 - memory_usage_byte: - - 0 - - 0 - exit_codes: - - 0 - - 0 + memory_usage_byte: 0 + exit_code: 0 - command: sleep 0.456 runs: - wall_clock_time: 0.456 user_time: 0 system_time: 0 + memory_usage_byte: 0 + exit_code: 0 - wall_clock_time: 0.456 user_time: 0 system_time: 0 - memory_usage_byte: - - 0 - - 0 - exit_codes: - - 0 - - 0 + memory_usage_byte: 0 + exit_code: 0 "#); Ok(()) diff --git a/src/export/csv.rs b/src/export/csv.rs index c9d81b8..ce54e3a 100644 --- a/src/export/csv.rs +++ b/src/export/csv.rs @@ -90,20 +90,24 @@ fn test_csv() { wall_clock_time: 7.0, user_time: 7.0, system_time: 0.0, + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 8.0, user_time: 8.0, system_time: 0.0, + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 12.0, user_time: 12.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: { let mut params = BTreeMap::new(); params.insert( @@ -131,20 +135,24 @@ fn test_csv() { wall_clock_time: 17.0, user_time: 17.0, system_time: 0.0, + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 18.0, user_time: 18.0, system_time: 0.0, + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 19.0, user_time: 19.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: { let mut params = BTreeMap::new(); params.insert( diff --git a/src/export/tests.rs b/src/export/tests.rs index 298cd0d..1366e50 100644 --- a/src/export/tests.rs +++ b/src/export/tests.rs @@ -33,20 +33,24 @@ fn test_markup_export_auto_ms() { wall_clock_time: 0.09, user_time: 0.09, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.10, user_time: 0.10, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.14, user_time: 0.14, 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(), }, BenchmarkResult { @@ -57,20 +61,24 @@ fn test_markup_export_auto_ms() { wall_clock_time: 2.0, user_time: 2.0, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 3.0, user_time: 3.0, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 4.0, user_time: 4.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(), }, ]; @@ -126,20 +134,24 @@ fn test_markup_export_auto_s() { wall_clock_time: 2.1, user_time: 2.1, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 2.2, user_time: 2.2, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 2.3, user_time: 2.3, 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(), }, BenchmarkResult { @@ -150,20 +162,24 @@ fn test_markup_export_auto_s() { wall_clock_time: 0.1, user_time: 0.1, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.2, user_time: 0.2, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.3, user_time: 0.3, 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(), }, ]; @@ -219,20 +235,24 @@ fn test_markup_export_manual_ms() { wall_clock_time: 2.1, user_time: 2.1, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 2.2, user_time: 2.2, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 2.3, user_time: 2.3, 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(), }, BenchmarkResult { @@ -243,20 +263,24 @@ fn test_markup_export_manual_ms() { wall_clock_time: 0.1, user_time: 0.1, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.2, user_time: 0.2, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.3, user_time: 0.3, 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(), }, ]; @@ -311,20 +335,24 @@ fn test_markup_export_manual_s() { wall_clock_time: 2.01, user_time: 2.01, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 2.02, user_time: 2.02, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 2.03, user_time: 2.03, 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(), }, BenchmarkResult { @@ -335,20 +363,24 @@ fn test_markup_export_manual_s() { wall_clock_time: 0.11, user_time: 0.11, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.12, user_time: 0.12, system_time: 0., + memory_usage_byte: 1024, + exit_code: Some(0), }, BenchmarkRun { wall_clock_time: 0.13, user_time: 0.13, 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(), }, ]; diff --git a/src/timer/mod.rs b/src/timer/mod.rs index be00286..9b7df54 100644 --- a/src/timer/mod.rs +++ b/src/timer/mod.rs @@ -24,6 +24,17 @@ use std::process::{ChildStdout, Command}; 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. fn discard(output: ChildStdout) { const CHUNK_SIZE: usize = 64 << 10; @@ -82,8 +93,8 @@ pub fn execute_and_measure(mut command: Command) -> Result { discard(output); } - let (time_user, time_system, peak_memory_usage, exit_status) = cpu_timer.stop(child)?; - let time_wall_clock = wallclock_timer.stop(); + let (status, time_user, time_system, memory_usage_byte) = cpu_timer.stop(child)?; + let time_real = wallclock_timer.stop(); Ok(Measurement { time_wall_clock, diff --git a/src/timer/unix_timer.rs b/src/timer/unix_timer.rs index 699a541..b76438c 100644 --- a/src/timer/unix_timer.rs +++ b/src/timer/unix_timer.rs @@ -1,5 +1,6 @@ #![cfg(not(windows))] +use std::convert::TryFrom; use std::io; use std::mem::MaybeUninit; use std::os::unix::process::ExitStatusExt; @@ -7,26 +8,24 @@ use std::process::{Child, ExitStatus}; use anyhow::Result; -use crate::quantity::{byte, kibibyte, microsecond, second, Information, Time}; +use crate::util::units::Second; #[derive(Debug, Copy, Clone)] struct ResourceUsage { /// 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 - pub time_system: Time, + pub system_usec: Second, /// Maximum amount of memory used by the process, in bytes - pub memory_usage: Information, + pub memory_usage_byte: i64, } #[allow(clippy::useless_conversion)] -fn convert_timeval(tv: libc::timeval) -> Time { - let sec = tv.tv_sec as f64; - let usec = tv.tv_usec as f64; - - Time::new::(sec) + Time::new::(usec) +fn timeval_to_seconds(tv: libc::timeval) -> Second { + const MICROSEC_PER_SEC: i64 = 1000 * 1000; + (i64::from(tv.tv_sec) * MICROSEC_PER_SEC + i64::from(tv.tv_usec)) as f64 * 1e-6 } #[allow(clippy::useless_conversion)] @@ -37,37 +36,29 @@ fn wait4(mut child: Child) -> io::Result<(ExitStatus, ResourceUsage)> { 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); - } - } + let result = unsafe { libc::wait4(pid, &mut status, 0, rusage.as_mut_ptr()) }; - let rusage = unsafe { rusage.assume_init() }; - - 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::(rusage.ru_maxrss as f64) + if result < 0 { + Err(io::Error::last_os_error()) } else { - Information::new::(rusage.ru_maxrss as f64) - }; + let rusage = unsafe { rusage.assume_init() }; - Ok(( - ExitStatus::from_raw(status), - ResourceUsage { - time_user: convert_timeval(rusage.ru_utime), - time_system: convert_timeval(rusage.ru_stime), - memory_usage: memory_usage_byte.into(), - }, - )) + 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 + rusage.ru_maxrss + } else { + rusage.ru_maxrss * 1024 + }; + + 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 {} @@ -77,13 +68,13 @@ impl CPUTimer { 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)?; Ok(( - usage.time_user, - usage.time_system, - usage.memory_usage, status, + usage.user_usec, + usage.system_usec, + u64::try_from(usage.memory_usage_byte).unwrap_or(0), )) } } diff --git a/src/timer/windows_timer.rs b/src/timer/windows_timer.rs index e48e288..5c380dc 100644 --- a/src/timer/windows_timer.rs +++ b/src/timer/windows_timer.rs @@ -84,7 +84,7 @@ impl CPUTimer { 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 mut job_object_info = @@ -113,12 +113,10 @@ impl CPUTimer { // The `TotalKernelTime` is "The total amount of kernel-mode execution time // for all active processes associated with the job, as well as all terminated // processes no longer associated with the job, in 100-nanosecond ticks." - let system_time = - Time::new::((job_object_info.TotalKernelTime as f64) * 100.0); - - Ok((user_time, system_time, Information::zero(), status)) + let kernel: i64 = job_object_info.TotalKernelTime / HUNDRED_NS_PER_MS; + Ok((status, user as f64 * 1e-6, kernel as f64 * 1e-6, 0)) } else { - Ok((Time::zero(), Time::zero(), Information::zero(), status)) + Ok((status, 0.0, 0.0, 0)) } } }