diff --git a/Cargo.toml b/Cargo.toml index 64e5999..1a79f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,12 @@ nom = "7.0" version = "0.3" features = ["fileapi", "sysinfoapi", "minwindef", "winbase", "winerror", "ws2def", "ws2ipdef", "pdh"] +[target.'cfg(target_vendor = "apple")'.dependencies] +mach2 = "0.6" + +[target.'cfg(any(target_os = "illumos", target_os = "solaris"))'.dependencies] +kstat-rs = "0.2" + [package.metadata.docs.rs] targets = [ "x86_64-unknown-freebsd", diff --git a/src/platform/illumos.rs b/src/platform/illumos.rs index 4aa62c6..397abe9 100644 --- a/src/platform/illumos.rs +++ b/src/platform/illumos.rs @@ -5,6 +5,40 @@ use std::{io, path}; pub struct PlatformImpl; +fn named_u64(named: &[kstat_rs::Named], key: &str) -> usize { + for n in named { + if n.name == key { + if let kstat_rs::NamedData::UInt64(v) = n.value { + return v as usize; + } + } + } + 0 +} + +fn measure_cpu() -> io::Result> { + let ctl = kstat_rs::Ctl::new() + .map_err(|e| io::Error::other(e.to_string()))?; + let mut result = Vec::new(); + for mut ks in ctl.filter(Some("cpu"), None, Some("sys")) { + let instance = ks.ks_instance; + let data = ctl.read(&mut ks) + .map_err(|e| io::Error::other(e.to_string()))?; + if let kstat_rs::Data::Named(named) = data { + result.push((instance, CpuTime { + user: named_u64(&named, "cpu_ticks_user"), + nice: 0, + system: named_u64(&named, "cpu_ticks_kernel"), + interrupt: 0, + idle: named_u64(&named, "cpu_ticks_idle"), + other: named_u64(&named, "cpu_ticks_wait"), + })); + } + } + result.sort_by_key(|(instance, _)| *instance); + Ok(result.into_iter().map(|(_, cpu)| cpu).collect()) +} + /// An implementation of `Platform` for illumos. /// See `Platform` for documentation. impl Platform for PlatformImpl { @@ -14,7 +48,12 @@ impl Platform for PlatformImpl { } fn cpu_load(&self) -> io::Result>> { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + let loads = measure_cpu()?; + Ok(DelayedMeasurement::new( + Box::new(move || Ok(loads.iter() + .zip(measure_cpu()?.iter()) + .map(|(prev, now)| (*now - prev).to_cpuload()) + .collect::>())))) } fn load_average(&self) -> io::Result { @@ -22,50 +61,67 @@ impl Platform for PlatformImpl { } fn memory(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn swap(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn boot_time(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + let ctl = kstat_rs::Ctl::new() + .map_err(|e| io::Error::other(e.to_string()))?; + let mut ks = ctl.filter(Some("unix"), Some(0), Some("system_misc")) + .next() + .ok_or_else(|| io::Error::other("kstat unix:0:system_misc not found"))?; + let data = ctl.read(&mut ks) + .map_err(|e| io::Error::other(e.to_string()))?; + if let kstat_rs::Data::Named(named) = data { + for n in &named { + if n.name == "boot_time" { + if let kstat_rs::NamedData::UInt32(v) = n.value { + return OffsetDateTime::from_unix_timestamp(v as i64) + .map_err(|e| io::Error::other(e.to_string())); + } + } + } + } + Err(io::Error::other("boot_time not found in kstat")) } fn battery_life(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn on_ac_power(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn mounts(&self) -> io::Result> { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn mount_at>(&self, _: P) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn block_device_statistics(&self) -> io::Result> { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn networks(&self) -> io::Result> { unix::networks() } - fn network_stats(&self, interface: &str) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + fn network_stats(&self, _interface: &str) -> io::Result { + Err(io::Error::other("Not supported")) } fn cpu_temp(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn socket_stats(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 479417b..d25d254 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,9 +1,14 @@ use std::{io, ptr, mem::{self, MaybeUninit}, ffi, slice}; use libc::{ - c_int, c_void, host_statistics64, mach_host_self, size_t, statfs, sysconf, sysctl, - sysctlnametomib, timeval, vm_statistics64, xsw_usage, CTL_VM, HOST_VM_INFO64, - HOST_VM_INFO64_COUNT, KERN_SUCCESS, VM_SWAPUSAGE, _SC_PHYS_PAGES, + c_int, c_void, host_processor_info, host_statistics64, mach_msg_type_number_t, + natural_t, processor_cpu_load_info, processor_info_array_t, size_t, statfs, + sysconf, sysctl, sysctlnametomib, timeval, vm_address_t, vm_deallocate, vm_size_t, + vm_statistics64, xsw_usage, CTL_VM, CPU_STATE_IDLE, CPU_STATE_NICE, CPU_STATE_SYSTEM, + CPU_STATE_USER, HOST_VM_INFO64, HOST_VM_INFO64_COUNT, KERN_SUCCESS, + PROCESSOR_CPU_LOAD_INFO, VM_SWAPUSAGE, _SC_PHYS_PAGES, }; +use mach2::mach_init::mach_host_self; +use mach2::traps::mach_task_self; use crate::data::*; use super::common::*; use super::unix; @@ -30,7 +35,7 @@ macro_rules! sysctl { let mut size = $size; if unsafe { sysctl(&mib[0] as *const _ as *mut _, mib.len() as u32, $dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck { - return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) + return Err(io::Error::other("sysctl() failed")) } size } @@ -53,7 +58,12 @@ impl Platform for PlatformImpl { } fn cpu_load(&self) -> io::Result>> { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + let loads = measure_cpu()?; + Ok(DelayedMeasurement::new( + Box::new(move || Ok(loads.iter() + .zip(measure_cpu()?.iter()) + .map(|(prev, now)| (*now - prev).to_cpuload()) + .collect::>())))) } fn load_average(&self) -> io::Result { @@ -64,10 +74,7 @@ impl Platform for PlatformImpl { // Get Total Memory let total = match unsafe { sysconf(_SC_PHYS_PAGES) } { -1 => { - return Err(io::Error::new( - io::ErrorKind::Other, - "sysconf(_SC_PHYS_PAGES) failed", - )) + return Err(io::Error::other("sysconf(_SC_PHYS_PAGES) failed")) } n => n as u64, }; @@ -87,10 +94,7 @@ impl Platform for PlatformImpl { }; if ret != KERN_SUCCESS { - return Err(io::Error::new( - io::ErrorKind::Other, - "host_statistics64() failed", - )); + return Err(io::Error::other("host_statistics64() failed")); } let stat = unsafe { stat.assume_init() }; @@ -107,7 +111,7 @@ impl Platform for PlatformImpl { external: ByteSize::kib((stat.external_page_count as u64) << *bsd::PAGESHIFT), internal: ByteSize::kib((stat.internal_page_count as u64) << *bsd::PAGESHIFT), uncompressed_in_compressor: ByteSize::kib( - (stat.total_uncompressed_pages_in_compressor as u64) << *bsd::PAGESHIFT, + stat.total_uncompressed_pages_in_compressor << *bsd::PAGESHIFT, ), }; @@ -143,30 +147,30 @@ impl Platform for PlatformImpl { fn boot_time(&self) -> io::Result { let mut data: timeval = unsafe { mem::zeroed() }; sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); - let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); + let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); Ok(ts) } fn battery_life(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn on_ac_power(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn mounts(&self) -> io::Result> { let mut mptr: *mut statfs = ptr::null_mut(); let len = unsafe { getmntinfo(&mut mptr, 2_i32) }; if len < 1 { - return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) + return Err(io::Error::other("getmntinfo() failed")) } let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; Ok(mounts.iter().map(statfs_to_fs).collect::>()) } fn block_device_statistics(&self) -> io::Result> { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn networks(&self) -> io::Result> { @@ -174,18 +178,65 @@ impl Platform for PlatformImpl { } fn network_stats(&self, _interface: &str) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn cpu_temp(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } fn socket_stats(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + Err(io::Error::other("Not supported")) } } +fn measure_cpu() -> io::Result> { + let mut num_cpus: natural_t = 0; + let mut info: processor_info_array_t = ptr::null_mut(); + let mut info_count: mach_msg_type_number_t = 0; + + let ret = unsafe { + host_processor_info( + mach_host_self(), + PROCESSOR_CPU_LOAD_INFO, + &mut num_cpus, + &mut info, + &mut info_count, + ) + }; + + if ret != KERN_SUCCESS { + return Err(io::Error::other("host_processor_info() failed")); + } + + let loads = unsafe { + let cpus = slice::from_raw_parts( + info as *const processor_cpu_load_info, + num_cpus as usize, + ); + cpus.iter() + .map(|cpu| CpuTime { + user: cpu.cpu_ticks[CPU_STATE_USER as usize] as usize, + nice: cpu.cpu_ticks[CPU_STATE_NICE as usize] as usize, + system: cpu.cpu_ticks[CPU_STATE_SYSTEM as usize] as usize, + interrupt: 0, + idle: cpu.cpu_ticks[CPU_STATE_IDLE as usize] as usize, + other: 0, + }) + .collect::>() + }; + + unsafe { + vm_deallocate( + mach_task_self(), + info as vm_address_t, + info_count as vm_size_t * mem::size_of::() as vm_size_t, + ); + } + + Ok(loads) +} + fn statfs_to_fs(x: &statfs) -> Filesystem { Filesystem { files: (x.f_files as usize).saturating_sub(x.f_ffree as usize), diff --git a/src/platform/netbsd.rs b/src/platform/netbsd.rs index 89a5662..9ba224f 100644 --- a/src/platform/netbsd.rs +++ b/src/platform/netbsd.rs @@ -2,7 +2,7 @@ use super::common::*; use super::unix; use crate::data::*; -use libc::{c_int, c_void, sysctl, CTL_VM}; +use libc::{c_int, c_void, sysctl, CTL_HW, CTL_KERN, CTL_VM, HW_NCPU, KERN_CP_TIME}; use std::{io, mem, path, ptr}; pub struct PlatformImpl; @@ -44,7 +44,12 @@ impl Platform for PlatformImpl { } fn cpu_load(&self) -> io::Result>> { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + let loads = measure_cpu()?; + Ok(DelayedMeasurement::new( + Box::new(move || Ok(loads.iter() + .zip(measure_cpu()?.iter()) + .map(|(prev, now)| (*now - prev).to_cpuload()) + .collect::>())))) } fn load_average(&self) -> io::Result { @@ -147,6 +152,41 @@ impl PlatformMemory { } } +fn measure_cpu() -> io::Result> { + let mut ncpu: usize = 0; + sysctl!([CTL_HW, HW_NCPU], &mut ncpu, mem::size_of::()); + let mut data: Vec = Vec::with_capacity(ncpu); + unsafe { data.set_len(ncpu) }; + for i in 0..ncpu { + let mib = [CTL_KERN, KERN_CP_TIME, i as c_int]; + sysctl!(mib, &mut data[i], mem::size_of::()); + } + Ok(data.into_iter().map(|cpu| cpu.into()).collect()) +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct cp_time { + user: u64, + nice: u64, + system: u64, + interrupt: u64, + idle: u64, +} + +impl From for CpuTime { + fn from(cpu: cp_time) -> CpuTime { + CpuTime { + user: cpu.user as usize, + nice: cpu.nice as usize, + system: cpu.system as usize, + interrupt: cpu.interrupt as usize, + idle: cpu.idle as usize, + other: 0, + } + } +} + // https://github.com/NetBSD/src/blob/038135cba4b80f5c8d1e32fbc5b73c91c2f276d9/sys/uvm/uvm_extern.h#L420-L515 #[repr(C)] #[derive(Debug, Default)]