cleanup: use nix version of getrusage

Change the behavior when `getrusage` fails. Previously, failure was
masked by using 0 values for everything. This is misleading. Instead, we
now panic on such failures, because they should never occur with our
usage of the function.

Closes #12502
This commit is contained in:
Daniel Rainer
2026-03-04 17:59:48 +01:00
committed by Johannes Altmanninger
parent 0223edc639
commit 310eba7156
3 changed files with 59 additions and 91 deletions

View File

@@ -42,7 +42,7 @@
history::{self, start_private_mode},
io::IoChain,
locale::set_libc_locales,
nix::{RUsage, getrusage, isatty},
nix::isatty,
panic::panic_handler,
parse_constants::{ParseErrorList, ParseTreeFlags},
parse_tree::ParsedSource,
@@ -63,7 +63,10 @@
};
use fish_wcstringutil::wcs2bytes;
use libc::STDIN_FILENO;
use nix::unistd::{AccessFlags, getpid};
use nix::{
sys::resource::{UsageWho, getrusage},
unistd::{AccessFlags, getpid},
};
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::os::unix::prelude::*;
@@ -102,28 +105,30 @@ struct FishCmdOpts {
/// Return a timeval converted to milliseconds.
#[allow(clippy::unnecessary_cast)]
fn tv_to_msec(tv: &libc::timeval) -> i64 {
fn nix_tv_to_ms(tv: nix::sys::time::TimeVal) -> i64 {
// milliseconds per second
let mut msec = tv.tv_sec as i64 * 1000;
let mut ms = tv.tv_sec() as i64 * 1000;
// microseconds per millisecond
msec += tv.tv_usec as i64 / 1000;
msec
ms += tv.tv_usec() as i64 / 1000;
ms
}
fn print_rusage_self() {
let rs = getrusage(RUsage::RSelf);
// `getrusage` should never fail with this usage.
// If it does, it suggests a non-POSIX-compliant OS.
let usage = getrusage(UsageWho::RUSAGE_SELF).unwrap();
let rss_kb = if cfg!(apple) {
// mac use bytes.
rs.ru_maxrss / 1024
// Macs use bytes,
// Source: commit b6555a0dc462f669e1b5a370c3efae0f5948a1ef
// The docs at https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrusage.2.html say otherwise.
usage.max_rss() / 1024
} else {
// Everyone else uses KB.
rs.ru_maxrss
usage.max_rss()
};
let user_time = tv_to_msec(&rs.ru_utime);
let sys_time = tv_to_msec(&rs.ru_stime);
let user_time = nix_tv_to_ms(usage.user_time());
let sys_time = nix_tv_to_ms(usage.system_time());
let total_time = user_time + sys_time;
let signals = rs.ru_nsignals;
let signals = usage.signals();
eprintf!(" rusage self:\n");
eprintf!(" user time: %s ms\n", sys_time.to_string());

View File

@@ -1,64 +1,7 @@
//! Safe wrappers around various libc functions that we might want to reuse across modules.
use fish_util::perror;
use std::time::Duration;
#[allow(clippy::unnecessary_cast)]
pub const fn timeval_to_duration(val: &libc::timeval) -> Duration {
let micros = val.tv_sec as i64 * (1E6 as i64) + val.tv_usec as i64;
Duration::from_micros(micros as u64)
}
pub trait TimevalExt {
fn as_micros(&self) -> i64;
fn as_duration(&self) -> Duration;
}
impl TimevalExt for libc::timeval {
fn as_micros(&self) -> i64 {
timeval_to_duration(self).as_micros() as i64
}
fn as_duration(&self) -> Duration {
timeval_to_duration(self)
}
}
pub fn isatty(fd: i32) -> bool {
// This returns false if the fd is valid but not a tty, or is invalid.
// No place we currently call it really cares about the difference.
(unsafe { libc::isatty(fd) }) == 1
}
/// An enumeration of supported libc rusage types used by [`getrusage()`].
/// NB: RUSAGE_THREAD is not supported on macOS.
pub enum RUsage {
RSelf, // "Self" is a reserved keyword
RChildren,
}
/// A safe wrapper around `libc::getrusage()`.
pub fn getrusage(resource: RUsage) -> libc::rusage {
let mut rusage = std::mem::MaybeUninit::uninit();
let result = unsafe {
libc::getrusage(
match resource {
RUsage::RSelf => libc::RUSAGE_SELF,
RUsage::RChildren => libc::RUSAGE_CHILDREN,
},
rusage.as_mut_ptr(),
)
};
// getrusage(2) says the syscall can only fail if the dest address is invalid (EFAULT) or if the
// requested resource type is invalid. Since we're in control of both, we can assume it won't
// fail. In case it does anyway (e.g. OS where the syscall isn't implemented), we can just
// return an empty value.
match result {
0 => unsafe { rusage.assume_init() },
_ => {
perror("getrusage");
unsafe { std::mem::zeroed() }
}
}
}

View File

@@ -14,12 +14,12 @@
//! but it's still the best we can do because we don't know how long of a time might elapse between
//! `TimerSnapshot` instances and need to avoid rollover.
use nix::sys::resource::{Usage, UsageWho, getrusage};
use nix::sys::time::TimeVal;
use std::fmt::Write as _;
use std::io::Write as _;
use std::time::{Duration, Instant};
use crate::nix::{RUsage, getrusage};
enum Unit {
Minutes,
Seconds,
@@ -29,8 +29,8 @@ enum Unit {
struct TimerSnapshot {
wall_time: Instant,
cpu_fish: libc::rusage,
cpu_children: libc::rusage,
cpu_fish: Usage,
cpu_children: Usage,
}
/// Create a `TimerSnapshot` and return a `PrintElapsedOnDrop` object that will print upon
@@ -44,8 +44,11 @@ pub fn push_timer() -> PrintElapsedOnDrop {
impl TimerSnapshot {
pub fn take() -> TimerSnapshot {
TimerSnapshot {
cpu_fish: getrusage(RUsage::RSelf),
cpu_children: getrusage(RUsage::RChildren),
// getrusage should never fail.
// POSIX rusage getrusage only fails if the who value is invalid. Both `RUSAGE_SELF` and
// `RUSAGE_CHILDREN` are valid, so this should never fail.
cpu_fish: getrusage(UsageWho::RUSAGE_SELF).unwrap(),
cpu_children: getrusage(UsageWho::RUSAGE_CHILDREN).unwrap(),
wall_time: Instant::now(),
}
}
@@ -54,12 +57,17 @@ pub fn take() -> TimerSnapshot {
/// instances. The returned string can take one of two formats, depending on the value of the
/// `verbose` parameter.
pub fn get_delta(t1: &TimerSnapshot, t2: &TimerSnapshot, verbose: bool) -> String {
use crate::nix::timeval_to_duration as from;
#[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.ru_stime) - from(&t1.cpu_fish.ru_stime);
let mut fish_usr = from(&t2.cpu_fish.ru_utime) - from(&t1.cpu_fish.ru_utime);
let mut child_sys = from(&t2.cpu_children.ru_stime) - from(&t1.cpu_children.ru_stime);
let mut child_usr = from(&t2.cpu_children.ru_utime) - from(&t1.cpu_children.ru_utime);
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());
// The result from getrusage is not necessarily realtime, it may be cached from a few
// microseconds ago. In the event that execution completes extremely quickly or there is
@@ -192,16 +200,28 @@ mod tests {
#[test]
fn timer_format_and_alignment() {
let mut t1 = TimerSnapshot::take();
t1.cpu_fish.ru_utime.tv_usec = 0;
t1.cpu_fish.ru_stime.tv_usec = 0;
t1.cpu_children.ru_utime.tv_usec = 0;
t1.cpu_children.ru_stime.tv_usec = 0;
{
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();
t2.cpu_fish.ru_utime.tv_usec = 999995;
t2.cpu_fish.ru_stime.tv_usec = 999994;
t2.cpu_children.ru_utime.tv_usec = 1000;
t2.cpu_children.ru_stime.tv_usec = 500;
{
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#"