Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for total accumulated process CPU usage #1044

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/lib.rs
Expand Up @@ -175,6 +175,9 @@ mod doctest {

#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::time::Instant;

use crate::*;

#[cfg(feature = "unknown-ci")]
Expand Down Expand Up @@ -255,6 +258,63 @@ mod test {
.any(|(_, proc_)| proc_.cpu_usage() > 0.0));
}

#[test]
fn check_processes_total_accumulated_cpu_usage() {
if System::IS_SUPPORTED {
let mut s = System::new();

// Grab the intial accumulated CPU usages
s.refresh_cpu();
s.refresh_processes();
s.refresh_processes(); // Needed on some OS to fully populate the accumulated CPU usage
let first_time = Instant::now();
let all_procs: HashMap<_, _> = s
.processes()
.iter()
.map(|(pid, proc)| (*pid, proc.total_accumulated_cpu_usage()))
.collect();

// All accumulated CPU usages will be non-negative.
all_procs.values().for_each(|&usage| assert!(usage >= 0.0));
// At least one will be positive.
assert!(all_procs.values().any(|&usage| usage > 0.0));

// Wait a bit to update CPU usage values
std::thread::sleep(System::MINIMUM_CPU_UPDATE_INTERVAL);
s.refresh_processes();
let duration = Instant::now().duration_since(first_time).as_secs_f32();

// They will still all be non-negative.
s.processes()
.values()
.for_each(|proc| assert!(proc.total_accumulated_cpu_usage() >= 0.0));

// They will all have either remained the same or
// increased no more than a valid amount.
let max_delta = s.cpus().len() as f32 * duration;
s.processes().iter().for_each(|(pid, proc)| {
if let Some(prev) = all_procs.get(pid) {
let delta = proc.total_accumulated_cpu_usage() - prev;
assert!(
delta >= 0.0 && delta <= max_delta,
"CPU time delta is out of range delta={} max_delta={} pid={}",
delta,
max_delta,
pid,
);
}
});

// At least one of them will have accumulated some CPU time.
#[cfg(not(windows))] // Windows CPU timers appear to have insufficient resolution
assert!(s.processes().iter().any(|(pid, proc)| {
all_procs
.get(pid)
.map_or(false, |&prev| proc.total_accumulated_cpu_usage() > prev)
}));
}
}

#[test]
fn check_cpu_usage() {
if !System::IS_SUPPORTED {
Expand Down
14 changes: 14 additions & 0 deletions src/traits.rs
Expand Up @@ -375,6 +375,20 @@ pub trait ProcessExt: Debug {
/// ```
fn cpu_usage(&self) -> f32;

/// Returns the total accumulated CPU usage (in
/// CPU-seconds). Notice that it might be bigger than the total
/// clock run time of a process if run on a multi-core machine.
///
/// ```no_run
/// use sysinfo::{Pid, ProcessExt, System, SystemExt};
///
/// let s = System::new_all();
/// if let Some(process) = s.process(Pid::from(1337)) {
/// println!("{}sec", process.total_accumulated_cpu_usage());
/// }
/// ```
fn total_accumulated_cpu_usage(&self) -> f32;

/// Returns number of bytes read and written to disk.
///
/// ⚠️ On Windows and FreeBSD, this method actually returns **ALL** I/O read and written bytes.
Expand Down
4 changes: 4 additions & 0 deletions src/unix/apple/app_store/process.rs
Expand Up @@ -68,6 +68,10 @@ impl ProcessExt for Process {
0.0
}

fn total_accumulated_cpu_usage(&self) -> f32 {
0.0
}

fn disk_usage(&self) -> DiskUsage {
DiskUsage::default()
}
Expand Down
6 changes: 3 additions & 3 deletions src/unix/apple/cpu.rs
Expand Up @@ -255,7 +255,7 @@ pub(crate) fn update_cpu_usage<F: FnOnce(Arc<CpuData>, *mut i32) -> (f32, usize)
let mut cpu_info: *mut i32 = std::ptr::null_mut();
let mut num_cpu_info = 0u32;

let mut total_cpu_usage = 0f32;
let mut total_accumulated_cpu_usage = 0f32;

unsafe {
if host_processor_info(
Expand All @@ -268,9 +268,9 @@ pub(crate) fn update_cpu_usage<F: FnOnce(Arc<CpuData>, *mut i32) -> (f32, usize)
{
let (total_percentage, len) =
f(Arc::new(CpuData::new(cpu_info, num_cpu_info)), cpu_info);
total_cpu_usage = total_percentage / len as f32;
total_accumulated_cpu_usage = total_percentage / len as f32;
}
global_cpu.set_cpu_usage(total_cpu_usage);
global_cpu.set_cpu_usage(total_accumulated_cpu_usage);
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/unix/apple/macos/process.rs
Expand Up @@ -32,6 +32,7 @@ pub struct Process {
run_time: u64,
pub(crate) updated: bool,
cpu_usage: f32,
accum_cpu_usage: f32,
user_id: Option<Uid>,
effective_user_id: Option<Uid>,
group_id: Option<Gid>,
Expand Down Expand Up @@ -62,6 +63,7 @@ impl Process {
memory: 0,
virtual_memory: 0,
cpu_usage: 0.,
accum_cpu_usage: 0.,
old_utime: 0,
old_stime: 0,
updated: true,
Expand Down Expand Up @@ -93,6 +95,7 @@ impl Process {
memory: 0,
virtual_memory: 0,
cpu_usage: 0.,
accum_cpu_usage: 0.,
old_utime: 0,
old_stime: 0,
updated: true,
Expand Down Expand Up @@ -181,6 +184,10 @@ impl ProcessExt for Process {
self.cpu_usage
}

fn total_accumulated_cpu_usage(&self) -> f32 {
self.accum_cpu_usage
}

fn disk_usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
Expand Down Expand Up @@ -558,6 +565,7 @@ unsafe fn create_new_process(
Ok(Some(p))
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn update_process(
wrap: &Wrap,
pid: Pid,
Expand All @@ -566,6 +574,7 @@ pub(crate) fn update_process(
now: u64,
refresh_kind: ProcessRefreshKind,
check_if_alive: bool,
timebase_to_seconds: f64,
) -> Result<Option<Process>, ()> {
unsafe {
if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) {
Expand Down Expand Up @@ -614,6 +623,10 @@ pub(crate) fn update_process(
if refresh_kind.cpu() {
compute_cpu_usage(p, task_info, system_time, user_time, time_interval);
}
p.accum_cpu_usage = (task_info
.pti_total_user
.saturating_add(task_info.pti_total_system) as f64
* timebase_to_seconds) as f32;

p.memory = task_info.pti_resident_size;
p.virtual_memory = task_info.pti_virtual_size;
Expand Down
10 changes: 7 additions & 3 deletions src/unix/apple/macos/system.rs
Expand Up @@ -11,6 +11,8 @@ use libc::{
};
use std::ptr::null_mut;

pub(crate) const NANOS_PER_SECOND: f64 = 1_000_000_000.;

struct ProcessorCpuLoadInfo {
cpu_load: processor_cpu_load_info_t,
cpu_count: natural_t,
Expand Down Expand Up @@ -55,6 +57,7 @@ impl Drop for ProcessorCpuLoadInfo {

pub(crate) struct SystemTimeInfo {
timebase_to_ns: f64,
pub timebase_to_sec: f64,
clock_per_sec: f64,
old_cpu_info: ProcessorCpuLoadInfo,
}
Expand Down Expand Up @@ -93,11 +96,12 @@ impl SystemTimeInfo {
}
};

let nano_per_seconds = 1_000_000_000.;
sysinfo_debug!("");
let timebase_to_ns = info.numer as f64 / info.denom as f64;
Some(Self {
timebase_to_ns: info.numer as f64 / info.denom as f64,
clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64,
timebase_to_ns,
timebase_to_sec: timebase_to_ns / NANOS_PER_SECOND,
clock_per_sec: NANOS_PER_SECOND / clock_ticks_per_sec as f64,
old_cpu_info,
})
}
Expand Down
8 changes: 8 additions & 0 deletions src/unix/apple/system.rs
Expand Up @@ -237,6 +237,10 @@ impl SystemExt for System {
let arg_max = get_arg_max();
let port = self.port;
let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port));
let timebase_to_sec = self
.clock_info
.as_ref()
.map_or(1.0, |ci| ci.timebase_to_sec);
let entries: Vec<Process> = {
let wrap = &Wrap(UnsafeCell::new(&mut self.process_list));

Expand All @@ -253,6 +257,7 @@ impl SystemExt for System {
now,
refresh_kind,
false,
timebase_to_sec,
) {
Ok(x) => x,
_ => None,
Expand Down Expand Up @@ -293,6 +298,9 @@ impl SystemExt for System {
now,
refresh_kind,
true,
self.clock_info
.as_ref()
.map_or(1.0, |ci| ci.timebase_to_sec),
)
} {
Ok(Some(p)) => {
Expand Down
9 changes: 9 additions & 0 deletions src/unix/freebsd/process.rs
Expand Up @@ -54,6 +54,7 @@ pub struct Process {
pub(crate) virtual_memory: u64,
pub(crate) updated: bool,
cpu_usage: f32,
accum_cpu_usage: f32,
start_time: u64,
run_time: u64,
pub(crate) status: ProcessStatus,
Expand Down Expand Up @@ -129,6 +130,10 @@ impl ProcessExt for Process {
self.cpu_usage
}

fn total_accumulated_cpu_usage(&self) -> f32 {
self.accum_cpu_usage
}

fn disk_usage(&self) -> DiskUsage {
DiskUsage {
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
Expand Down Expand Up @@ -207,6 +212,9 @@ pub(crate) unsafe fn get_process_data(
};
let status = ProcessStatus::from(kproc.ki_stat);

// from FreeBSD source /bin/ps/print.c
let accum_cpu_usage = (kproc.ki_runtime as f64 / 1000000.0) as f32;

// from FreeBSD source /src/usr.bin/top/machine.c
let virtual_memory = kproc.ki_size as _;
let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _);
Expand Down Expand Up @@ -275,6 +283,7 @@ pub(crate) unsafe fn get_process_data(
start_time,
run_time: now.saturating_sub(start_time),
cpu_usage,
accum_cpu_usage,
virtual_memory,
memory,
// procstat_getfiles
Expand Down
13 changes: 11 additions & 2 deletions src/unix/linux/process.rs
Expand Up @@ -89,10 +89,11 @@ pub struct Process {
old_written_bytes: u64,
read_bytes: u64,
written_bytes: u64,
clock_cycle: u64,
}

impl Process {
pub(crate) fn new(pid: Pid) -> Process {
pub(crate) fn new(pid: Pid, info: &SystemInfo) -> Process {
Process {
name: String::with_capacity(20),
pid,
Expand Down Expand Up @@ -128,6 +129,7 @@ impl Process {
old_written_bytes: 0,
read_bytes: 0,
written_bytes: 0,
clock_cycle: info.clock_cycle,
}
}
}
Expand Down Expand Up @@ -194,6 +196,13 @@ impl ProcessExt for Process {
self.cpu_usage
}

fn total_accumulated_cpu_usage(&self) -> f32 {
// The external values for CPU times are in "ticks", which are
// scaled by "HZ", which is pegged externally at 100
// ticks/second.
self.utime.saturating_add(self.stime) as f32 / self.clock_cycle as f32
}

fn disk_usage(&self) -> DiskUsage {
DiskUsage {
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
Expand Down Expand Up @@ -369,7 +378,7 @@ fn retrieve_all_new_process_info(
refresh_kind: ProcessRefreshKind,
uptime: u64,
) -> Process {
let mut p = Process::new(pid);
let mut p = Process::new(pid, info);
let mut tmp = PathHandler::new(path);
let name = parts[1];

Expand Down
5 changes: 3 additions & 2 deletions src/unix/linux/system.rs
Expand Up @@ -215,7 +215,8 @@ impl SystemExt for System {
const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);

fn new_with_specifics(refreshes: RefreshKind) -> System {
let process_list = Process::new(Pid(0));
let info = SystemInfo::new();
let process_list = Process::new(Pid(0), &info);
let mut s = System {
process_list,
mem_total: 0,
Expand All @@ -229,7 +230,7 @@ impl SystemExt for System {
swap_free: 0,
cpus: CpusWrapper::new(),
users: Vec::new(),
info: SystemInfo::new(),
info,
};
s.refresh_specifics(refreshes);
s
Expand Down
4 changes: 4 additions & 0 deletions src/unknown/process.rs
Expand Up @@ -78,6 +78,10 @@ impl ProcessExt for Process {
0.0
}

fn total_accumulated_cpu_usage(&self) -> f32 {
0.0
}

fn disk_usage(&self) -> DiskUsage {
DiskUsage::default()
}
Expand Down