// Take a look at the license at the top of the repository in the LICENSE file. use crate::SystemExt; #[allow(deprecated)] use libc::{mach_timebase_info, mach_timebase_info_data_t}; use libc::{ host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info, processor_cpu_load_info_t, sysconf, vm_page_size, PROCESSOR_CPU_LOAD_INFO, _SC_CLK_TCK, }; use std::ptr::null_mut; struct ProcessorCpuLoadInfo { cpu_load: processor_cpu_load_info_t, cpu_count: natural_t, } impl ProcessorCpuLoadInfo { fn new(port: mach_port_t) -> Option { let mut info_size = std::mem::size_of::() as _; let mut cpu_count = 0; let mut cpu_load: processor_cpu_load_info_t = null_mut(); unsafe { if host_processor_info( port, PROCESSOR_CPU_LOAD_INFO, &mut cpu_count, &mut cpu_load as *mut _ as *mut _, &mut info_size, ) != 0 { sysinfo_debug!("host_processor_info failed, not updating CPU ticks usage..."); None } else if cpu_count < 1 || cpu_load.is_null() { None } else { Some(Self { cpu_load, cpu_count, }) } } } } impl Drop for ProcessorCpuLoadInfo { fn drop(&mut self) { unsafe { munmap(self.cpu_load as _, vm_page_size); } } } pub(crate) struct SystemTimeInfo { timebase_to_ns: f64, clock_per_sec: f64, old_cpu_info: ProcessorCpuLoadInfo, } unsafe impl Send for SystemTimeInfo {} unsafe impl Sync for SystemTimeInfo {} impl SystemTimeInfo { #[allow(deprecated)] // Everything related to mach_timebase_info_data_t pub fn new(port: mach_port_t) -> Option { unsafe { let clock_ticks_per_sec = sysconf(_SC_CLK_TCK); // FIXME: Maybe check errno here? Problem is that if errno is not 0 before this call, // we will get an error which isn't related... // if let Some(er) = std::io::Error::last_os_error().raw_os_error() { // if err != 0 { // println!("==> {:?}", er); // sysinfo_debug!("Failed to get _SC_CLK_TCK value, using old CPU tick measure system"); // return None; // } // } let mut info = mach_timebase_info_data_t { numer: 0, denom: 0 }; if mach_timebase_info(&mut info) != libc::KERN_SUCCESS { sysinfo_debug!("mach_timebase_info failed, using default value of 1"); info.numer = 1; info.denom = 1; } let old_cpu_info = match ProcessorCpuLoadInfo::new(port) { Some(cpu_info) => cpu_info, None => { sysinfo_debug!("host_processor_info failed, using old CPU tick measure system"); return None; } }; let nano_per_seconds = 1_000_000_000.; sysinfo_debug!(""); 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, old_cpu_info, }) } } pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { let mut total = 0; let new_cpu_info = match ProcessorCpuLoadInfo::new(port) { Some(cpu_info) => cpu_info, None => return 0., }; let cpu_count = std::cmp::min(self.old_cpu_info.cpu_count, new_cpu_info.cpu_count); unsafe { for i in 0..cpu_count { let new_load: &processor_cpu_load_info = &*new_cpu_info.cpu_load.offset(i as _); let old_load: &processor_cpu_load_info = &*self.old_cpu_info.cpu_load.offset(i as _); for (new, old) in new_load.cpu_ticks.iter().zip(old_load.cpu_ticks.iter()) { if new > old { total += new - old; } } } self.old_cpu_info = new_cpu_info; // Now we convert the ticks to nanoseconds (if the interval is less than // `MINIMUM_CPU_UPDATE_INTERVAL`, we replace it with it instead): let base_interval = total as f64 / cpu_count as f64 * self.clock_per_sec; let smallest = crate::System::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0; if base_interval < smallest { smallest } else { base_interval / self.timebase_to_ns } } } } #[cfg(test)] mod test { use super::*; /// Regression test for . #[test] fn test_getting_time_interval() { if !crate::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let port = unsafe { libc::mach_host_self() }; let mut info = SystemTimeInfo::new(port).unwrap(); info.get_time_interval(port); std::thread::sleep(crate::System::MINIMUM_CPU_UPDATE_INTERVAL.saturating_mul(5)); let val = info.get_time_interval(port); assert_ne!( val, crate::System::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0 ); } }