use crate::sys::cpu::*;
use crate::sys::ffi;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use crate::sys::process::*;
use crate::sys::utils::{get_sys_value, get_sys_value_by_name};
use crate::{
Cpu, CpuRefreshKind, Error, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind,
ProcessesToUpdate,
};
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::ffi::CStr;
use std::mem;
use std::time::Duration;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use std::time::SystemTime;
use libc::{
_SC_PAGESIZE, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, timeval,
vm_statistics64,
};
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
declare_signals! {
libc::c_int,
Signal::Hangup => libc::SIGHUP,
Signal::Interrupt => libc::SIGINT,
Signal::Quit => libc::SIGQUIT,
Signal::Illegal => libc::SIGILL,
Signal::Trap => libc::SIGTRAP,
Signal::Abort => libc::SIGABRT,
Signal::IOT => libc::SIGIOT,
Signal::Bus => libc::SIGBUS,
Signal::FloatingPointException => libc::SIGFPE,
Signal::Kill => libc::SIGKILL,
Signal::User1 => libc::SIGUSR1,
Signal::Segv => libc::SIGSEGV,
Signal::User2 => libc::SIGUSR2,
Signal::Pipe => libc::SIGPIPE,
Signal::Alarm => libc::SIGALRM,
Signal::Term => libc::SIGTERM,
Signal::Child => libc::SIGCHLD,
Signal::Continue => libc::SIGCONT,
Signal::Stop => libc::SIGSTOP,
Signal::TSTP => libc::SIGTSTP,
Signal::TTIN => libc::SIGTTIN,
Signal::TTOU => libc::SIGTTOU,
Signal::Urgent => libc::SIGURG,
Signal::XCPU => libc::SIGXCPU,
Signal::XFSZ => libc::SIGXFSZ,
Signal::VirtualAlarm => libc::SIGVTALRM,
Signal::Profiling => libc::SIGPROF,
Signal::Winch => libc::SIGWINCH,
Signal::IO => libc::SIGIO,
Signal::Poll => libc::SIGIO,
Signal::Sys => libc::SIGSYS,
_ => None,
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
declare_signals! {
libc::c_int,
_ => None,
}
#[doc = include_str!("../../../md_doc/supported_signals.md")]
pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
#[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")]
pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);
pub(crate) struct SystemInner {
process_list: HashMap<Pid, Process>,
mem_total: u64,
mem_free: u64,
mem_used: u64,
mem_available: u64,
swap_total: u64,
swap_free: u64,
page_size_b: u64,
port: mach_port_t,
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
clock_info: Option<crate::sys::macos::system::SystemTimeInfo>,
cpus: CpusWrapper,
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>);
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
unsafe impl Send for Wrap<'_> {}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
unsafe impl Sync for Wrap<'_> {}
fn boot_time() -> Result<u64, Error> {
let mut boot_time = timeval {
tv_sec: 0,
tv_usec: 0,
};
let mut len = std::mem::size_of::<timeval>();
let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME];
unsafe {
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
&mut boot_time as *mut timeval as *mut _,
&mut len,
std::ptr::null_mut(),
0,
) < 0
{
Err(Error::from("sysctl failed"))
} else {
Ok(boot_time.tv_sec as _)
}
}
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
fn get_now() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|n| n.as_secs())
.unwrap_or(0)
}
impl SystemInner {
pub(crate) fn new() -> Result<Self, Error> {
unsafe {
let port = ffi::mach_host_self();
Ok(Self {
process_list: HashMap::with_capacity(200),
mem_total: 0,
mem_free: 0,
mem_available: 0,
mem_used: 0,
swap_total: 0,
swap_free: 0,
page_size_b: sysconf(_SC_PAGESIZE) as _,
port,
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
clock_info: crate::sys::macos::system::SystemTimeInfo::new(port),
cpus: CpusWrapper::new(),
})
}
}
pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
let mut mib = [libc::CTL_VM as _, libc::VM_SWAPUSAGE as _];
unsafe {
if refresh_kind.swap() {
let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>();
if get_sys_value(
mem::size_of::<libc::xsw_usage>(),
&mut xs as *mut _ as *mut c_void,
&mut mib,
) {
self.swap_total = xs.xsu_total;
self.swap_free = xs.xsu_avail;
}
}
if refresh_kind.ram() {
mib[0] = libc::CTL_HW as _;
mib[1] = libc::HW_MEMSIZE as _;
if self.mem_total < 1 {
get_sys_value(
mem::size_of::<u64>(),
&mut self.mem_total as *mut u64 as *mut c_void,
&mut mib,
);
}
let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _;
let mut stat = mem::zeroed::<vm_statistics64>();
if host_statistics64(
self.port,
libc::HOST_VM_INFO64,
&mut stat as *mut vm_statistics64 as *mut _,
&mut count,
) == libc::KERN_SUCCESS
{
self.mem_available = u64::from(stat.active_count)
.saturating_add(u64::from(stat.inactive_count))
.saturating_add(u64::from(stat.free_count))
.saturating_mul(self.page_size_b);
self.mem_used = u64::from(stat.internal_page_count)
.saturating_sub(u64::from(stat.purgeable_count))
.saturating_add(u64::from(stat.wire_count))
.saturating_add(u64::from(stat.compressor_page_count))
.saturating_mul(self.page_size_b);
self.mem_free = u64::from(stat.free_count)
.saturating_sub(u64::from(stat.speculative_count))
.saturating_mul(self.page_size_b);
}
}
}
}
pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
None
}
pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus.refresh(refresh_kind, self.port);
}
pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
self.cpus = CpusWrapper::new();
self.cpus.refresh(refresh_kind, self.port);
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
pub(crate) fn refresh_processes_specifics(
&mut self,
_processes_to_update: ProcessesToUpdate<'_>,
_refresh_kind: ProcessRefreshKind,
) -> usize {
0
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
pub(crate) fn refresh_processes_specifics(
&mut self,
processes_to_update: ProcessesToUpdate<'_>,
refresh_kind: ProcessRefreshKind,
) -> usize {
use crate::utils::into_iter;
use std::sync::atomic::{AtomicUsize, Ordering};
unsafe {
let count = libc::proc_listallpids(::std::ptr::null_mut(), 0);
if count < 1 {
return 0;
}
}
if let Some(pids) = get_proc_list() {
#[inline(always)]
fn real_filter(e: Pid, filter: &[Pid]) -> bool {
filter.contains(&e)
}
#[inline(always)]
fn empty_filter(_e: Pid, _filter: &[Pid]) -> bool {
true
}
#[allow(clippy::type_complexity)]
let (filter, filter_callback): (
&[Pid],
&(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send),
) = match processes_to_update {
ProcessesToUpdate::All => (&[], &empty_filter),
ProcessesToUpdate::Some(pids_to_refresh) => {
if pids_to_refresh.is_empty() {
return 0;
}
(pids_to_refresh, &real_filter)
}
};
let nb_updated = AtomicUsize::new(0);
let now = get_now();
let port = self.port;
let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port));
let timebase_to_ms = self
.clock_info
.as_ref()
.map(|c| c.timebase_to_ms)
.unwrap_or_default();
let entries: Vec<Process> = {
let wrap = &Wrap(UnsafeCell::new(&mut self.process_list));
#[cfg(feature = "multithread")]
use rayon::iter::ParallelIterator;
into_iter(pids)
.flat_map(|pid| {
if !filter_callback(pid, filter) {
return None;
}
nb_updated.fetch_add(1, Ordering::Relaxed);
update_process(
wrap,
pid,
time_interval,
now,
refresh_kind,
false,
timebase_to_ms,
)
.unwrap_or_default()
})
.collect()
};
entries.into_iter().for_each(|entry| {
self.process_list.insert(entry.pid(), entry);
});
#[cfg(feature = "gpu")]
if refresh_kind.gpu_usage() {
self::gpu::get_processes_gpu_usage(&mut self.process_list);
}
nb_updated.into_inner()
} else {
0
}
}
pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
&mut self.process_list
}
pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
self.process_list.get(&pid)
}
pub(crate) fn global_cpu_usage(&self) -> f32 {
self.cpus.global_cpu.percent()
}
pub(crate) fn cpus(&self) -> &[Cpu] {
&self.cpus.cpus
}
pub(crate) fn total_memory(&self) -> u64 {
self.mem_total
}
pub(crate) fn free_memory(&self) -> u64 {
self.mem_free
}
pub(crate) fn available_memory(&self) -> u64 {
self.mem_available
}
pub(crate) fn used_memory(&self) -> u64 {
self.mem_used
}
pub(crate) fn total_swap(&self) -> u64 {
self.swap_total
}
pub(crate) fn free_swap(&self) -> u64 {
self.swap_free
}
pub(crate) fn used_swap(&self) -> u64 {
self.swap_total - self.swap_free
}
pub(crate) fn uptime() -> Result<u64, Error> {
unsafe {
let csec = libc::time(::std::ptr::null_mut());
Ok(libc::difftime(csec, Self::boot_time()? as _) as _)
}
}
pub(crate) fn load_average() -> Result<LoadAvg, Error> {
let mut loads = vec![0f64; 3];
unsafe {
if libc::getloadavg(loads.as_mut_ptr(), 3) == -1 {
Err(Error::from("getloadavg failed"))
} else {
Ok(LoadAvg {
one: loads[0],
five: loads[1],
fifteen: loads[2],
})
}
}
}
pub(crate) fn boot_time() -> Result<u64, Error> {
boot_time()
}
pub(crate) fn name() -> Result<String, Error> {
match get_system_info(libc::KERN_OSTYPE) {
Some(v) => Ok(v),
None => Ok("Darwin".to_owned()),
}
}
pub(crate) fn long_os_version() -> Result<String, Error> {
#[cfg(target_os = "macos")]
{
let Ok(os_version) = Self::os_version() else {
return Ok("macOS".to_owned());
};
for (version_prefix, macos_spelling, friendly_name) in [
("26", "macOS", "Tahoe"),
("15", "macOS", "Sequoia"),
("14", "macOS", "Sonoma"),
("13", "macOS", "Ventura"),
("12", "macOS", "Monterey"),
("11", "macOS", "Big Sur"),
("10.16", "macOS", "Big Sur"),
("10.15", "macOS", "Catalina"),
("10.14", "macOS", "Mojave"),
("10.13", "macOS", "High Sierra"),
("10.12", "macOS", "Sierra"),
("10.11", "OS X", "El Capitan"),
("10.10", "OS X", "Yosemite"),
("10.9", "OS X", "Mavericks"),
("10.8", "OS X", "Mountain Lion"),
("10.7", "Mac OS X", "Lion"),
("10.6", "Mac OS X", "Snow Leopard"),
("10.5", "Mac OS X", "Leopard"),
("10.4", "Mac OS X", "Tiger"),
("10.3", "Mac OS X", "Panther"),
("10.2", "Mac OS X", "Jaguar"),
("10.1", "Mac OS X", "Puma"),
("10.0", "Mac OS X", "Cheetah"),
] {
if os_version.starts_with(version_prefix) {
return Ok(format!("{macos_spelling} {os_version} {friendly_name}"));
}
}
Ok(format!("macOS {os_version}"))
}
#[cfg(target_os = "ios")]
{
let mut long_name = "iOS".to_owned();
if let Ok(os_version) = Self::os_version() {
long_name.push(' ');
long_name.push_str(&os_version);
}
Ok(long_name)
}
}
pub(crate) fn host_name() -> Result<String, Error> {
get_system_info(libc::KERN_HOSTNAME)
.ok_or_else(|| Error::Other("failed to retrieve host name".into()))
}
pub(crate) fn kernel_version() -> Result<String, Error> {
get_system_info(libc::KERN_OSRELEASE)
.ok_or_else(|| Error::Other("failed to retrieve kernel version".into()))
}
pub(crate) fn os_version() -> Result<String, Error> {
unsafe {
let mut size = 0;
if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut())
&& size > 0
{
let mut buf = vec![0_u8; size as _];
if get_sys_value_by_name(
b"kern.osproductversion\0",
&mut size,
buf.as_mut_ptr() as *mut c_void,
) {
if let Some(pos) = buf.iter().position(|x| *x == 0) {
buf.resize(pos, 0);
}
if let Ok(value) = String::from_utf8(buf) {
return Ok(value);
}
}
}
Err(Error::Other("failed to retrieve OS version".into()))
}
}
pub(crate) fn distribution_id() -> String {
std::env::consts::OS.to_owned()
}
pub(crate) fn distribution_id_like() -> Vec<String> {
Vec::new()
}
pub(crate) fn kernel_name() -> Option<&'static str> {
Some("Darwin")
}
pub(crate) fn cpu_arch() -> Option<String> {
let mut arch_str: [u8; 32] = [0; 32];
let mut mib = [libc::CTL_HW as _, libc::HW_MACHINE as _];
unsafe {
if get_sys_value(
mem::size_of::<[u8; 32]>(),
arch_str.as_mut_ptr() as *mut _,
&mut mib,
) {
CStr::from_bytes_until_nul(&arch_str)
.ok()
.and_then(|res| match res.to_str() {
Ok(arch) => Some(arch.to_string()),
Err(_) => None,
})
} else {
None
}
}
}
pub(crate) fn physical_core_count() -> Result<usize, Error> {
let mut physical_core_count = 0;
unsafe {
if get_sys_value_by_name(
b"hw.physicalcpu\0",
&mut mem::size_of::<u32>(),
&mut physical_core_count as *mut usize as *mut c_void,
) {
Ok(physical_core_count)
} else {
Err(Error::Other(
"failed to retrieve physical core count".into(),
))
}
}
}
pub(crate) fn open_files_limit() -> Result<usize, Error> {
let rlim_cur = unsafe {
getrlimit()
.ok_or(Error::Other("failed to retrieve open files limit".into()))?
.rlim_cur
};
let mut maxfilesperproc: c_int = 0;
let mut len = std::mem::size_of::<c_int>();
let ret = unsafe {
get_sys_value_by_name(
b"kern.maxfilesperproc\0",
&mut len,
&mut maxfilesperproc as *mut _ as *mut c_void,
)
};
if ret && maxfilesperproc >= 0 {
Ok(std::cmp::min(rlim_cur as usize, maxfilesperproc as usize))
} else {
Err(Error::Other("failed to retrieve open files limit".into()))
}
}
}
fn get_system_info(value: c_int) -> Option<String> {
let mut mib: [c_int; 2] = [libc::CTL_KERN, value];
let mut size = 0;
unsafe {
sysctl(
mib.as_mut_ptr(),
mib.len() as _,
std::ptr::null_mut(),
&mut size,
std::ptr::null_mut(),
0,
);
if size != 0 {
let mut buf = vec![0_u8; size as _];
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
buf.as_mut_ptr() as _,
&mut size,
std::ptr::null_mut(),
0,
) != -1
{
if let Some(pos) = buf.iter().position(|x| *x == 0) {
buf.resize(pos, 0);
}
return String::from_utf8(buf).ok();
}
}
None
}
}
unsafe fn getrlimit() -> Option<libc::rlimit> {
let mut limits = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) } != 0 {
None
} else {
Some(limits)
}
}
#[cfg(feature = "gpu")]
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
mod gpu {
use crate::sys::utils::IOReleaser;
use crate::{Pid, Process};
use std::collections::HashMap;
use std::ffi::CStr;
use objc2_core_foundation::{CFArray, CFDictionary, CFNumber, CFRetained, CFString, CFType};
use objc2_io_kit::{
IOIteratorNext, IORegistryEntryCreateCFProperties, IORegistryEntryGetChildIterator,
IORegistryGetRootEntry, kIODeviceTreePlane, kIOMainPortDefault, kIOReturnSuccess,
kIOServicePlane,
};
struct Keys {
accumulated_gpu_time: CFRetained<CFString>,
app_usage: CFRetained<CFString>,
io_user: CFRetained<CFString>,
}
impl Keys {
fn new() -> Self {
Self {
accumulated_gpu_time: CFString::from_str("accumulatedGPUTime"),
app_usage: CFString::from_str("AppUsage"),
io_user: CFString::from_str("IOUserClientCreator"),
}
}
}
pub fn get_processes_gpu_usage(processes: &mut HashMap<Pid, Process>) {
let mut result = HashMap::new();
unsafe {
let root = IORegistryGetRootEntry(kIOMainPortDefault);
let Some(root) = IOReleaser::new(root) else {
sysinfo_debug!("processes GPU: IORegistryGetRootEntry failed");
return;
};
let keys = Keys::new();
for plane in [kIODeviceTreePlane, kIOServicePlane] {
walk_plane(&root, plane, &mut result, &keys);
}
}
if !result.is_empty() {
let instant = std::time::Instant::now();
for (pid, gpu_time) in result {
if let Some(proc) = processes.get_mut(&pid) {
proc.inner.compute_gpu_usage(gpu_time, instant);
}
}
}
}
unsafe fn walk_plane(
entry: &IOReleaser,
plane: &CStr,
result: &mut HashMap<Pid, u64>,
keys: &Keys,
) {
unsafe {
inspect(entry, result, keys);
let mut iter = 0;
if IORegistryEntryGetChildIterator(entry.inner(), plane.as_ptr() as *mut _, &mut iter)
!= kIOReturnSuccess
{
return;
}
let Some(iter) = IOReleaser::new(iter) else {
return;
};
while let Some(child) = IOReleaser::new(IOIteratorNext(iter.inner())) {
walk_plane(&child, plane, result, keys);
}
}
}
unsafe fn inspect(entry: &IOReleaser, result: &mut HashMap<Pid, u64>, keys: &Keys) {
let mut props: Option<std::ptr::NonNull<CFDictionary<CFString, CFType>>> = None;
unsafe {
if IORegistryEntryCreateCFProperties(
entry.inner(),
&mut props as *mut _ as *mut _,
None,
0,
) == kIOReturnSuccess
&& let Some(props) = props
&& let props = CFRetained::<CFDictionary<CFString, CFType>>::from_raw(props)
&& let Some(pid) = props.get(&keys.io_user)
&& let Some(pid) = pid.downcast::<CFString>().ok().map(|s| s.to_string())
&& let Some(pid) = pid.strip_prefix("pid ")
&& let Some(pid) = pid.split(',').next()
&& let Ok(pid) = pid.trim().parse::<libc::pid_t>().map(Pid)
{
if let Some(gpu_time) = get_u64(&props, &keys.accumulated_gpu_time) {
if gpu_time != 0 {
result
.entry(pid)
.and_modify(|v| *v += gpu_time)
.or_insert(gpu_time);
}
} else if let Some(array) = get_array(&props, &keys.app_usage) {
let array = array.cast_unchecked::<CFType>();
for item in array.iter() {
if let Ok(item) = item.downcast::<CFDictionary>()
&& let Some(gpu_time) = get_u64(
item.cast_unchecked::<CFString, CFType>(),
&keys.accumulated_gpu_time,
)
&& gpu_time != 0
{
result
.entry(pid)
.and_modify(|v| *v += gpu_time)
.or_insert(gpu_time);
}
}
}
}
}
}
unsafe fn get_u64(dict: &CFDictionary<CFString, CFType>, key: &CFString) -> Option<u64> {
unsafe {
let dict = dict.cast_unchecked::<CFString, CFType>();
dict.get(key)?
.downcast::<CFNumber>()
.ok()?
.as_i64()
.map(|x| x as u64)
}
}
unsafe fn get_array(
dict: &CFDictionary<CFString, CFType>,
key: &CFString,
) -> Option<CFRetained<CFArray>> {
unsafe {
let dict = dict.cast_unchecked::<CFString, CFType>();
dict.get(key)?.downcast::<CFArray>().ok()
}
}
}