blob: 12abbd23b4728c1d97630d938e6b7d9d2f1ad660 [file] [log] [blame]
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::component::Component;
use crate::sys::cpu::*;
use crate::sys::disk::*;
use crate::sys::network::Networks;
use crate::sys::process::*;
use crate::{
CpuExt, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User,
};
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use crate::ProcessExt;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::mem;
use std::sync::Arc;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use std::time::SystemTime;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
use libc::size_t;
use libc::{
c_char, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, sysctlbyname, timeval,
vm_statistics64, _SC_PAGESIZE,
};
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
use super::inner::component::Components;
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
declare_signals! {
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,
// SIGPOLL doesn't exist on apple targets but since it's an equivalent of SIGIO on unix,
// we simply use the SIGIO constant.
Signal::Poll => libc::SIGIO,
Signal::Sys => libc::SIGSYS,
_ => None,
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
declare_signals! {
c_int,
_ => None,
}
#[doc = include_str!("../../md_doc/system.md")]
pub struct System {
process_list: HashMap<Pid, Process>,
mem_total: u64,
mem_free: u64,
mem_available: u64,
swap_total: u64,
swap_free: u64,
global_cpu: Cpu,
cpus: Vec<Cpu>,
page_size_kb: u64,
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
components: Components,
disks: Vec<Disk>,
networks: Networks,
port: mach_port_t,
users: Vec<User>,
boot_time: u64,
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
clock_info: Option<crate::sys::macos::system::SystemTimeInfo>,
got_cpu_frequency: bool,
}
pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>);
unsafe impl<'a> Send for Wrap<'a> {}
unsafe impl<'a> Sync for Wrap<'a> {}
fn boot_time() -> u64 {
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
{
0
} else {
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 SystemExt for System {
const IS_SUPPORTED: bool = true;
const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals();
fn new_with_specifics(refreshes: RefreshKind) -> System {
unsafe {
let port = libc::mach_host_self();
let mut s = System {
process_list: HashMap::with_capacity(200),
mem_total: 0,
mem_free: 0,
mem_available: 0,
swap_total: 0,
swap_free: 0,
global_cpu: Cpu::new(
"0".to_owned(),
Arc::new(CpuData::new(std::ptr::null_mut(), 0)),
0,
String::new(),
String::new(),
),
cpus: Vec::new(),
page_size_kb: sysconf(_SC_PAGESIZE) as _,
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
components: Components::new(),
disks: Vec::with_capacity(1),
networks: Networks::new(),
port,
users: Vec::new(),
boot_time: boot_time(),
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
clock_info: crate::sys::macos::system::SystemTimeInfo::new(port),
got_cpu_frequency: false,
};
s.refresh_specifics(refreshes);
s
}
}
fn refresh_memory(&mut self) {
let mut mib = [0, 0];
unsafe {
// get system values
// get swap info
let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>();
if get_sys_value(
libc::CTL_VM as _,
libc::VM_SWAPUSAGE as _,
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;
}
// get ram info
if self.mem_total < 1 {
get_sys_value(
libc::CTL_HW as _,
libc::HW_MEMSIZE as _,
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
{
// From the apple documentation:
//
// /*
// * NB: speculative pages are already accounted for in "free_count",
// * so "speculative_count" is the number of "free" pages that are
// * used to hold data that was read speculatively from disk but
// * haven't actually been used by anyone so far.
// */
self.mem_available = self.mem_total.saturating_sub(
u64::from(stat.active_count)
.saturating_add(u64::from(stat.inactive_count))
.saturating_add(u64::from(stat.wire_count))
.saturating_add(u64::from(stat.speculative_count))
.saturating_sub(u64::from(stat.purgeable_count))
.saturating_mul(self.page_size_kb),
);
self.mem_free = u64::from(stat.free_count).saturating_mul(self.page_size_kb);
}
}
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
fn refresh_components_list(&mut self) {}
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
fn refresh_components_list(&mut self) {
self.components.refresh();
}
fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
let cpus = &mut self.cpus;
if cpus.is_empty() {
init_cpus(self.port, cpus, &mut self.global_cpu, refresh_kind);
self.got_cpu_frequency = refresh_kind.frequency();
return;
}
if refresh_kind.frequency() && !self.got_cpu_frequency {
let frequency = get_cpu_frequency();
for proc_ in cpus.iter_mut() {
proc_.set_frequency(frequency);
}
self.got_cpu_frequency = true;
}
if refresh_kind.cpu_usage() {
update_cpu_usage(self.port, &mut self.global_cpu, |proc_data, cpu_info| {
let mut percentage = 0f32;
let mut offset = 0;
for proc_ in cpus.iter_mut() {
let cpu_usage = compute_usage_of_cpu(proc_, cpu_info, offset);
proc_.update(cpu_usage, Arc::clone(&proc_data));
percentage += proc_.cpu_usage();
offset += libc::CPU_STATE_MAX as isize;
}
(percentage, cpus.len())
});
}
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) {
use crate::utils::into_iter;
unsafe {
let count = libc::proc_listallpids(::std::ptr::null_mut(), 0);
if count < 1 {
return;
}
}
if let Some(pids) = get_proc_list() {
let now = get_now();
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 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| {
match update_process(
wrap,
pid,
arg_max as size_t,
time_interval,
now,
refresh_kind,
false,
) {
Ok(x) => x,
_ => None,
}
})
.collect()
};
entries.into_iter().for_each(|entry| {
self.process_list.insert(entry.pid(), entry);
});
self.process_list
.retain(|_, proc_| std::mem::replace(&mut proc_.updated, false));
}
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool {
false
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool {
let now = get_now();
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));
match {
let wrap = Wrap(UnsafeCell::new(&mut self.process_list));
update_process(
&wrap,
pid,
arg_max as size_t,
time_interval,
now,
refresh_kind,
true,
)
} {
Ok(Some(p)) => {
self.process_list.insert(p.pid(), p);
true
}
Ok(_) => true,
Err(_) => false,
}
}
fn refresh_disks_list(&mut self) {
self.disks = unsafe { get_disks() };
}
fn refresh_users_list(&mut self) {
self.users = crate::apple::users::get_users_list();
}
// COMMON PART
//
// Need to be moved into a "common" file to avoid duplication.
fn processes(&self) -> &HashMap<Pid, Process> {
&self.process_list
}
fn process(&self, pid: Pid) -> Option<&Process> {
self.process_list.get(&pid)
}
fn global_cpu_info(&self) -> &Cpu {
&self.global_cpu
}
fn cpus(&self) -> &[Cpu] {
&self.cpus
}
fn physical_core_count(&self) -> Option<usize> {
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,
) {
Some(physical_core_count)
} else {
None
}
}
}
fn networks(&self) -> &Networks {
&self.networks
}
fn networks_mut(&mut self) -> &mut Networks {
&mut self.networks
}
fn total_memory(&self) -> u64 {
self.mem_total
}
fn free_memory(&self) -> u64 {
self.mem_free
}
fn available_memory(&self) -> u64 {
self.mem_available
}
fn used_memory(&self) -> u64 {
self.mem_total - self.mem_free
}
fn total_swap(&self) -> u64 {
self.swap_total
}
fn free_swap(&self) -> u64 {
self.swap_free
}
// TODO: need to be checked
fn used_swap(&self) -> u64 {
self.swap_total - self.swap_free
}
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
fn components(&self) -> &[Component] {
&self.components.inner
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
fn components(&self) -> &[Component] {
&[]
}
#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))]
fn components_mut(&mut self) -> &mut [Component] {
&mut self.components.inner
}
#[cfg(any(target_os = "ios", feature = "apple-sandbox"))]
fn components_mut(&mut self) -> &mut [Component] {
&mut []
}
fn disks(&self) -> &[Disk] {
&self.disks
}
fn disks_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
fn sort_disks_by<F>(&mut self, compare: F)
where
F: FnMut(&Disk, &Disk) -> std::cmp::Ordering,
{
self.disks.sort_unstable_by(compare);
}
fn uptime(&self) -> u64 {
unsafe {
let csec = libc::time(::std::ptr::null_mut());
libc::difftime(csec, self.boot_time as _) as u64
}
}
fn load_average(&self) -> LoadAvg {
let mut loads = vec![0f64; 3];
unsafe {
libc::getloadavg(loads.as_mut_ptr(), 3);
LoadAvg {
one: loads[0],
five: loads[1],
fifteen: loads[2],
}
}
}
fn users(&self) -> &[User] {
&self.users
}
fn boot_time(&self) -> u64 {
self.boot_time
}
fn name(&self) -> Option<String> {
get_system_info(libc::KERN_OSTYPE, Some("Darwin"))
}
fn long_os_version(&self) -> Option<String> {
#[cfg(target_os = "macos")]
let friendly_name = match self.os_version().unwrap_or_default() {
f_n if f_n.starts_with("10.16")
| f_n.starts_with("11.0")
| f_n.starts_with("11.1")
| f_n.starts_with("11.2") =>
{
"Big Sur"
}
f_n if f_n.starts_with("10.15") => "Catalina",
f_n if f_n.starts_with("10.14") => "Mojave",
f_n if f_n.starts_with("10.13") => "High Sierra",
f_n if f_n.starts_with("10.12") => "Sierra",
f_n if f_n.starts_with("10.11") => "El Capitan",
f_n if f_n.starts_with("10.10") => "Yosemite",
f_n if f_n.starts_with("10.9") => "Mavericks",
f_n if f_n.starts_with("10.8") => "Mountain Lion",
f_n if f_n.starts_with("10.7") => "Lion",
f_n if f_n.starts_with("10.6") => "Snow Leopard",
f_n if f_n.starts_with("10.5") => "Leopard",
f_n if f_n.starts_with("10.4") => "Tiger",
f_n if f_n.starts_with("10.3") => "Panther",
f_n if f_n.starts_with("10.2") => "Jaguar",
f_n if f_n.starts_with("10.1") => "Puma",
f_n if f_n.starts_with("10.0") => "Cheetah",
_ => "",
};
#[cfg(target_os = "macos")]
let long_name = Some(format!(
"MacOS {} {}",
self.os_version().unwrap_or_default(),
friendly_name
));
#[cfg(target_os = "ios")]
let long_name = Some(format!("iOS {}", self.os_version().unwrap_or_default()));
long_name
}
fn host_name(&self) -> Option<String> {
get_system_info(libc::KERN_HOSTNAME, None)
}
fn kernel_version(&self) -> Option<String> {
get_system_info(libc::KERN_OSRELEASE, None)
}
fn os_version(&self) -> Option<String> {
unsafe {
// get the size for the buffer first
let mut size = 0;
if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut())
&& size > 0
{
// now create a buffer with the size and get the real value
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) {
// Shrink buffer to terminate the null bytes
buf.resize(pos, 0);
}
String::from_utf8(buf).ok()
} else {
// getting the system value failed
None
}
} else {
// getting the system value failed, or did not return a buffer size
None
}
}
}
fn distribution_id(&self) -> String {
std::env::consts::OS.to_owned()
}
}
impl Default for System {
fn default() -> System {
System::new()
}
}
#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))]
fn get_arg_max() -> usize {
let mut mib = [libc::CTL_KERN, libc::KERN_ARGMAX];
let mut arg_max = 0i32;
let mut size = mem::size_of::<c_int>();
unsafe {
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
(&mut arg_max) as *mut i32 as *mut c_void,
&mut size,
std::ptr::null_mut(),
0,
) == -1
{
4096 // We default to this value
} else {
arg_max as usize
}
}
}
pub(crate) unsafe fn get_sys_value(
high: u32,
low: u32,
mut len: usize,
value: *mut c_void,
mib: &mut [i32; 2],
) -> bool {
mib[0] = high as i32;
mib[1] = low as i32;
sysctl(
mib.as_mut_ptr(),
mib.len() as _,
value,
&mut len as *mut usize,
std::ptr::null_mut(),
0,
) == 0
}
unsafe fn get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool {
sysctlbyname(
name.as_ptr() as *const c_char,
value,
len,
std::ptr::null_mut(),
0,
) == 0
}
fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> {
let mut mib: [c_int; 2] = [libc::CTL_KERN, value];
let mut size = 0;
unsafe {
// Call first to get size
sysctl(
mib.as_mut_ptr(),
mib.len() as _,
std::ptr::null_mut(),
&mut size,
std::ptr::null_mut(),
0,
);
// exit early if we did not update the size
if size == 0 {
default.map(|s| s.to_owned())
} else {
// set the buffer to the correct size
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 command fails return default
default.map(|s| s.to_owned())
} else {
if let Some(pos) = buf.iter().position(|x| *x == 0) {
// Shrink buffer to terminate the null bytes
buf.resize(pos, 0);
}
String::from_utf8(buf).ok()
}
}
}
}