Skip to content

Commit

Permalink
Add getrusage wrapper
Browse files Browse the repository at this point in the history
Includes an enum to specify what to get resource usage for, and a new
struct that provides a more readable view into libc::rusage, including
using TimeVal for user and system CPU time.
  • Loading branch information
kov committed Jun 21, 2022
1 parent 01a5927 commit 2d1b323
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1703](https://github.com/nix-rust/nix/pull/1703))
- Added `ptrace::read_user` and `ptrace::write_user` for Linux.
(#[1697](https://github.com/nix-rust/nix/pull/1697))
- Added `getrusage` and helper types `UsageWho` and `Usage`
(#[1747](https://github.com/nix-rust/nix/pull/1747)

### Changed

Expand Down
186 changes: 180 additions & 6 deletions src/sys/resource.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Configure the process resource limits.
use cfg_if::cfg_if;
use libc::{c_int, c_long, rusage};

use crate::errno::Errno;
use crate::sys::time::TimeVal;
use crate::Result;
pub use libc::rlim_t;
use std::mem;
Expand All @@ -19,7 +21,7 @@ cfg_if! {
target_os = "dragonfly",
all(target_os = "linux", not(target_env = "gnu"))
))]{
use libc::{c_int, rlimit};
use libc::rlimit;
}
}

Expand Down Expand Up @@ -242,11 +244,7 @@ pub fn getrlimit(resource: Resource) -> Result<(rlim_t, rlim_t)> {
/// [`Resource`]: enum.Resource.html
///
/// Note: `setrlimit` provides a safe wrapper to libc's `setrlimit`.
pub fn setrlimit(
resource: Resource,
soft_limit: rlim_t,
hard_limit: rlim_t,
) -> Result<()> {
pub fn setrlimit(resource: Resource, soft_limit: rlim_t, hard_limit: rlim_t) -> Result<()> {
let new_rlim = rlimit {
rlim_cur: soft_limit,
rlim_max: hard_limit,
Expand All @@ -261,3 +259,179 @@ pub fn setrlimit(

Errno::result(res).map(drop)
}

libc_enum! {
/// Whose resource usage should be returned by [`getrusage`].
#[repr(i32)]
#[non_exhaustive]
pub enum UsageWho {
/// Resource usage for the current process.
RUSAGE_SELF,

/// Resource usage for all the children that have terminated and been waited for.
RUSAGE_CHILDREN,

#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
#[cfg_attr(docsrs, doc(cfg(all())))]
/// Resource usage for the calling thread.
RUSAGE_THREAD,
}
}

/// Output of `getrusage` with information about resource usage. Some of the fields
/// may be unused in some platforms, and will be always zeroed out. See their manuals
/// for details.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Usage(rusage);

impl AsRef<rusage> for Usage {
fn as_ref(&self) -> &rusage {
&self.0
}
}

impl AsMut<rusage> for Usage {
fn as_mut(&mut self) -> &mut rusage {
&mut self.0
}
}

impl Usage {
/// Total amount of time spent executing in user mode.
pub fn user_time(&self) -> TimeVal {
TimeVal::from(self.0.ru_utime)
}

/// Total amount of time spent executing in kernel mode.
pub fn system_time(&self) -> TimeVal {
TimeVal::from(self.0.ru_stime)
}

/// The resident set size at its peak, in kilobytes.
pub fn max_rss(&self) -> c_long {
self.0.ru_maxrss
}

/// Integral value expressed in kilobytes times ticks of execution indicating
/// the amount of text memory shared with other processes.
pub fn shared_integral(&self) -> c_long {
self.0.ru_ixrss
}

/// Integral value expressed in kilobytes times ticks of execution indicating
/// the amount of unshared memory used by data.
pub fn unshared_data_integral(&self) -> c_long {
self.0.ru_idrss
}

/// Integral value expressed in kilobytes times ticks of execution indicating
/// the amount of unshared memory used for stack space.
pub fn unshared_stack_integral(&self) -> c_long {
self.0.ru_isrss
}

/// Number of page faults that were served without resorting to I/O, with pages
/// that have been allocated previously by the kernel.
pub fn minor_page_faults(&self) -> c_long {
self.0.ru_minflt
}

/// Number of page faults that were served through I/O (i.e. swap).
pub fn major_page_faults(&self) -> c_long {
self.0.ru_majflt
}

/// Number of times all of the memory was fully swapped out.
pub fn full_swaps(&self) -> c_long {
self.0.ru_nswap
}

/// Number of times a read was done from a block device.
pub fn block_reads(&self) -> c_long {
self.0.ru_inblock
}

/// Number of times a write was done to a block device.
pub fn block_writes(&self) -> c_long {
self.0.ru_oublock
}

/// Number of IPC messages sent.
pub fn ipc_sends(&self) -> c_long {
self.0.ru_msgsnd
}

/// Number of IPC messages received.
pub fn ipc_receives(&self) -> c_long {
self.0.ru_msgrcv
}

/// Number of signals received.
pub fn signals(&self) -> c_long {
self.0.ru_nsignals
}

/// Number of times a context switch was voluntarily invoked.
pub fn voluntary_context_switches(&self) -> c_long {
self.0.ru_nvcsw
}

/// Number of times a context switch was imposed by the kernel (usually due to
/// time slice expiring or preemption by a higher priority process).
pub fn involuntary_context_switches(&self) -> c_long {
self.0.ru_nivcsw
}
}

/// Get usage information for a process, its children or the current thread
///
/// Real time information can be obtained for either the current process or (in some
/// systems) thread, but information about children processes is only provided for
/// those that have terminated and been waited for (see [`super::wait::wait`]).
///
/// Some information may be missing depending on the platform, and the way information
/// is provided for children may also vary. Check the manuals for details.
///
/// # References
///
/// * [getrusage(2)](https://pubs.opengroup.org/onlinepubs/009696699/functions/getrusage.html)
/// * [Linux](https://man7.org/linux/man-pages/man2/getrusage.2.html)
/// * [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=getrusage)
/// * [NetBSD](https://man.netbsd.org/getrusage.2)
/// * [MacOS](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrusage.2.html)
///
/// [`UsageWho`]: enum.UsageWho.html
///
/// Note: `getrusage` provides a safe wrapper to libc's [`libc::getrusage`].
pub fn getrusage(who: UsageWho) -> Result<Usage> {
unsafe {
let mut rusage = mem::MaybeUninit::<rusage>::uninit();
let res = libc::getrusage(who as c_int, rusage.as_mut_ptr());
Errno::result(res).map(|_| Usage(rusage.assume_init()))
}
}

#[cfg(test)]
mod test {
use super::{getrusage, UsageWho};

#[test]
pub fn test_self_cpu_time() {
// Make sure some CPU time is used.
let mut numbers: Vec<i32> = (1..1_000_000).collect();
numbers.iter_mut().for_each(|item| *item *= 2);

// FIXME: this is here to help ensure the compiler does not optimize the whole
// thing away. Replace the assert with test::black_box once stabilized.
assert_eq!(numbers[100..200].iter().sum::<i32>(), 30_100);

let usage = getrusage(UsageWho::RUSAGE_SELF).expect("Failed to call getrusage for SELF");
let rusage = usage.as_ref();

let user = usage.user_time();
assert!(user.tv_sec() > 0 || user.tv_usec() > 0);
assert_eq!(user.tv_sec(), rusage.ru_utime.tv_sec);
assert_eq!(user.tv_usec(), rusage.ru_utime.tv_usec);
}
}

0 comments on commit 2d1b323

Please sign in to comment.