use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Uid};
use std::ffi::OsString;
use std::path::PathBuf;
use super::utils::{WrapMap, get_sys_value_str, get_sysctl_raw};
#[doc(hidden)]
impl From<libc::c_char> for ProcessStatus {
fn from(status: libc::c_char) -> ProcessStatus {
match status {
libc::SIDL => ProcessStatus::Idle,
libc::SRUN => ProcessStatus::Run,
libc::SSLEEP => ProcessStatus::Sleep,
libc::SSTOP => ProcessStatus::Stop,
libc::SZOMB => ProcessStatus::Zombie,
libc::SWAIT => ProcessStatus::Dead,
libc::SLOCK => ProcessStatus::LockBlocked,
x => ProcessStatus::Unknown(x as _),
}
}
}
pub(crate) struct ProcessInner {
pub(crate) name: OsString,
pub(crate) cmd: Vec<OsString>,
pub(crate) exe: Option<PathBuf>,
pub(crate) pid: Pid,
pub(crate) parent: Option<Pid>,
pub(crate) environ: Vec<OsString>,
pub(crate) cwd: Option<PathBuf>,
pub(crate) root: Option<PathBuf>,
pub(crate) memory: u64,
pub(crate) virtual_memory: u64,
pub(crate) updated: bool,
pub(crate) cpu_usage: f32,
pub(crate) start_time: u64,
pub(crate) run_time: u64,
pub(crate) status: ProcessStatus,
pub(crate) user_id: Uid,
pub(crate) effective_user_id: Uid,
pub(crate) group_id: Gid,
pub(crate) effective_group_id: Gid,
pub(crate) read_bytes: u64,
pub(crate) old_read_bytes: u64,
pub(crate) written_bytes: u64,
pub(crate) old_written_bytes: u64,
pub(crate) accumulated_cpu_time: u64,
pub(crate) exists: bool,
}
impl ProcessInner {
pub(crate) fn disk_usage(&self) -> DiskUsage {
DiskUsage {
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
}
}
pub(crate) fn open_files(&self) -> Option<usize> {
let mib = &[
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_FILEDESC,
self.pid.0 as _,
];
let mut len = 0;
unsafe {
if get_sysctl_raw(mib, std::ptr::null_mut(), &mut len).is_none() {
sysinfo_debug!("Failed to query `open_files` info");
return None;
}
let Some(data) = AllocatedPtr::<()>::new(len) else {
sysinfo_debug!("Failed to allocate memory to get `open_files` info");
return None;
};
len = len * 4 / 3;
if get_sysctl_raw(mib, data.0, &mut len).is_none() {
sysinfo_debug!("Couldn't retrieve `open_files` data");
return None;
}
let mut current = data.0;
let end = current.byte_add(len);
let mut count = 0;
while current < end {
let t = current as *mut libc::kinfo_file;
if t.is_null() || (*t).kf_structsize == 0 {
break;
}
current = current.byte_add((*t).kf_structsize as _);
count += 1;
}
Some(count)
}
}
pub(crate) fn open_files_limit(&self) -> Option<usize> {
crate::System::open_files_limit()
}
}
struct AllocatedPtr<T>(*mut T);
impl<T> AllocatedPtr<T> {
fn new(size: libc::size_t) -> Option<Self> {
unsafe {
let ptr = libc::malloc(size);
if ptr.is_null() {
None
} else {
Some(Self(ptr as _))
}
}
}
}
impl<T> Drop for AllocatedPtr<T> {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
libc::free(self.0 as _);
}
}
}
}
#[inline]
fn get_accumulated_cpu_time(kproc: &libc::kinfo_proc) -> u64 {
kproc.ki_runtime / 1_000
}
pub(crate) unsafe fn get_process_data(
kproc: &libc::kinfo_proc,
wrap: &WrapMap,
page_size: isize,
fscale: f32,
now: u64,
refresh_kind: ProcessRefreshKind,
) -> Result<Option<Process>, ()> {
if kproc.ki_pid != 1 && (kproc.ki_flag as libc::c_int & libc::P_SYSTEM) != 0 {
return Err(());
}
let cpu_usage = if refresh_kind.cpu() {
Some((100 * kproc.ki_pctcpu) as f32 / fscale)
} else {
None
};
let parent = if kproc.ki_ppid != 0 {
Some(Pid(kproc.ki_ppid))
} else {
None
};
let status = ProcessStatus::from(kproc.ki_stat);
let (virtual_memory, memory) = if refresh_kind.memory() {
(
kproc.ki_size as _,
(kproc.ki_rssize as u64).saturating_mul(page_size as _),
)
} else {
(0, 0)
};
let start_time = kproc.ki_start.tv_sec as u64;
if let Some(proc_) = unsafe { (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) } {
let proc_ = &mut proc_.inner;
proc_.updated = true;
if proc_.start_time == start_time {
if let Some(cpu_usage) = cpu_usage {
proc_.cpu_usage = cpu_usage;
}
proc_.parent = parent;
proc_.status = status;
if refresh_kind.memory() {
proc_.virtual_memory = virtual_memory;
proc_.memory = memory;
}
proc_.run_time = now.saturating_sub(proc_.start_time);
if refresh_kind.disk_usage() {
proc_.old_read_bytes = proc_.read_bytes;
proc_.read_bytes = kproc.ki_rusage.ru_inblock as _;
proc_.old_written_bytes = proc_.written_bytes;
proc_.written_bytes = kproc.ki_rusage.ru_oublock as _;
}
if refresh_kind.cpu() {
proc_.accumulated_cpu_time = get_accumulated_cpu_time(kproc);
}
return Ok(None);
}
}
Ok(Some(Process {
inner: ProcessInner {
pid: Pid(kproc.ki_pid),
parent,
user_id: Uid(kproc.ki_ruid),
effective_user_id: Uid(kproc.ki_uid),
group_id: Gid(kproc.ki_rgid),
effective_group_id: Gid(kproc.ki_svgid),
start_time,
run_time: now.saturating_sub(start_time),
cpu_usage: cpu_usage.unwrap_or(0.),
virtual_memory,
memory,
cwd: None,
exe: None,
name: OsString::new(),
cmd: Vec::new(),
root: None,
environ: Vec::new(),
status,
read_bytes: kproc.ki_rusage.ru_inblock as _,
old_read_bytes: 0,
written_bytes: kproc.ki_rusage.ru_oublock as _,
old_written_bytes: 0,
accumulated_cpu_time: if refresh_kind.cpu() {
get_accumulated_cpu_time(kproc)
} else {
0
},
updated: true,
exists: true,
},
}))
}
pub(crate) unsafe fn get_exe(
exe: &mut Option<PathBuf>,
pid: crate::Pid,
refresh_kind: ProcessRefreshKind,
) {
if refresh_kind.exe().needs_update(|| exe.is_none()) {
let mut buffer = [0; libc::PATH_MAX as usize + 1];
unsafe {
*exe = get_sys_value_str(
&[
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_PATHNAME,
pid.0,
],
&mut buffer,
)
.map(PathBuf::from);
}
}
}