| // Take a look at the license at the top of the repository in the LICENSE file. |
| |
| use crate::sys::utils::{ |
| get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, init_mib, VecSwitcher, |
| }; |
| use crate::{Cpu, CpuRefreshKind}; |
| |
| use libc::{c_int, c_ulong}; |
| |
| pub(crate) unsafe fn get_nb_cpus() -> usize { |
| let mut smp: c_int = 0; |
| let mut nb_cpus: c_int = 1; |
| |
| if !get_sys_value_by_name(b"kern.smp.active\0", &mut smp) { |
| smp = 0; |
| } |
| #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. |
| if smp != 0 { |
| if !get_sys_value_by_name(b"kern.smp.cpus\0", &mut nb_cpus) || nb_cpus < 1 { |
| nb_cpus = 1; |
| } |
| } |
| nb_cpus as usize |
| } |
| |
| pub(crate) struct CpusWrapper { |
| pub(crate) global_cpu: Cpu, |
| pub(crate) cpus: Vec<Cpu>, |
| got_cpu_frequency: bool, |
| mib_cp_time: [c_int; 2], |
| mib_cp_times: [c_int; 2], |
| // For the global CPU usage. |
| cp_time: VecSwitcher<c_ulong>, |
| // For each CPU usage. |
| cp_times: VecSwitcher<c_ulong>, |
| nb_cpus: usize, |
| } |
| |
| impl CpusWrapper { |
| pub(crate) fn new() -> Self { |
| let mut mib_cp_time = [0; 2]; |
| let mut mib_cp_times = [0; 2]; |
| |
| unsafe { |
| let nb_cpus = get_nb_cpus(); |
| init_mib(b"kern.cp_time\0", &mut mib_cp_time); |
| init_mib(b"kern.cp_times\0", &mut mib_cp_times); |
| Self { |
| global_cpu: Cpu { |
| inner: CpuInner::new(String::new(), String::new(), 0), |
| }, |
| cpus: Vec::with_capacity(nb_cpus), |
| got_cpu_frequency: false, |
| mib_cp_time, |
| mib_cp_times, |
| cp_time: VecSwitcher::new(vec![0; libc::CPUSTATES as usize]), |
| cp_times: VecSwitcher::new(vec![0; nb_cpus * libc::CPUSTATES as usize]), |
| nb_cpus, |
| } |
| } |
| } |
| |
| pub(crate) fn refresh(&mut self, refresh_kind: CpuRefreshKind) { |
| if self.cpus.is_empty() { |
| let mut frequency = 0; |
| |
| // We get the CPU vendor ID in here. |
| let vendor_id = |
| get_sys_value_str_by_name(b"hw.model\0").unwrap_or_else(|| "<unknown>".to_owned()); |
| |
| for pos in 0..self.nb_cpus { |
| if refresh_kind.frequency() { |
| unsafe { |
| frequency = get_frequency_for_cpu(pos); |
| } |
| } |
| self.cpus.push(Cpu { |
| inner: CpuInner::new(format!("cpu {pos}"), vendor_id.clone(), frequency), |
| }); |
| } |
| self.got_cpu_frequency = refresh_kind.frequency(); |
| } else if refresh_kind.frequency() && !self.got_cpu_frequency { |
| for (pos, proc_) in self.cpus.iter_mut().enumerate() { |
| unsafe { |
| proc_.inner.frequency = get_frequency_for_cpu(pos); |
| } |
| } |
| self.got_cpu_frequency = true; |
| } |
| if refresh_kind.cpu_usage() { |
| self.get_cpu_usage(); |
| } |
| } |
| |
| fn get_cpu_usage(&mut self) { |
| unsafe { |
| get_sys_value_array(&self.mib_cp_time, self.cp_time.get_mut()); |
| get_sys_value_array(&self.mib_cp_times, self.cp_times.get_mut()); |
| } |
| |
| fn fill_cpu(proc_: &mut Cpu, new_cp_time: &[c_ulong], old_cp_time: &[c_ulong]) { |
| let mut total_new: u64 = 0; |
| let mut total_old: u64 = 0; |
| let mut cp_diff: c_ulong = 0; |
| |
| for i in 0..(libc::CPUSTATES as usize) { |
| // We obviously don't want to get the idle part of the CPU usage, otherwise |
| // we would always be at 100%... |
| if i != libc::CP_IDLE as usize { |
| cp_diff = cp_diff.saturating_add(new_cp_time[i].saturating_sub(old_cp_time[i])); |
| } |
| total_new = total_new.saturating_add(new_cp_time[i] as _); |
| total_old = total_old.saturating_add(old_cp_time[i] as _); |
| } |
| |
| let total_diff = total_new.saturating_sub(total_old); |
| if total_diff < 1 { |
| proc_.inner.cpu_usage = 0.; |
| } else { |
| proc_.inner.cpu_usage = cp_diff as f32 / total_diff as f32 * 100.; |
| } |
| } |
| |
| fill_cpu( |
| &mut self.global_cpu, |
| self.cp_time.get_new(), |
| self.cp_time.get_old(), |
| ); |
| let old_cp_times = self.cp_times.get_old(); |
| let new_cp_times = self.cp_times.get_new(); |
| for (pos, proc_) in self.cpus.iter_mut().enumerate() { |
| let index = pos * libc::CPUSTATES as usize; |
| |
| fill_cpu(proc_, &new_cp_times[index..], &old_cp_times[index..]); |
| } |
| } |
| } |
| |
| pub(crate) struct CpuInner { |
| pub(crate) cpu_usage: f32, |
| name: String, |
| pub(crate) vendor_id: String, |
| pub(crate) frequency: u64, |
| } |
| |
| impl CpuInner { |
| pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Self { |
| Self { |
| cpu_usage: 0., |
| name, |
| vendor_id, |
| frequency, |
| } |
| } |
| |
| pub(crate) fn cpu_usage(&self) -> f32 { |
| self.cpu_usage |
| } |
| |
| pub(crate) fn name(&self) -> &str { |
| &self.name |
| } |
| |
| pub(crate) fn frequency(&self) -> u64 { |
| self.frequency |
| } |
| |
| pub(crate) fn vendor_id(&self) -> &str { |
| &self.vendor_id |
| } |
| |
| pub(crate) fn brand(&self) -> &str { |
| "" |
| } |
| } |
| |
| pub(crate) fn physical_core_count() -> Option<usize> { |
| let mut physical_core_count: u32 = 0; |
| |
| unsafe { |
| if get_sys_value_by_name(b"hw.ncpu\0", &mut physical_core_count) { |
| Some(physical_core_count as _) |
| } else { |
| None |
| } |
| } |
| } |
| |
| unsafe fn get_frequency_for_cpu(cpu_nb: usize) -> u64 { |
| let mut frequency: c_int = 0; |
| |
| // The information can be missing if it's running inside a VM. |
| if !get_sys_value_by_name( |
| format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(), |
| &mut frequency, |
| ) { |
| frequency = 0; |
| } |
| frequency as _ |
| } |