#[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::collectors::command::{run_stdout, CommandTimeout};
use crate::collectors::CollectMode;
use crate::error::Result;
use std::thread;
use std::time::Duration;
use sysinfo::System;
#[derive(Debug, Clone)]
pub struct CpuInfo {
pub brand: String,
pub physical_cores: usize,
pub logical_cores: usize,
pub sockets: Option<usize>,
pub frequency_mhz: u64,
pub usage_percent: f32,
pub load_1m: Option<f64>,
pub load_5m: Option<f64>,
pub load_15m: Option<f64>,
}
pub fn collect(mode: CollectMode) -> Result<CpuInfo> {
let mut sys = System::new();
sys.refresh_cpu_all();
if mode == CollectMode::Full {
thread::sleep(Duration::from_millis(200));
sys.refresh_cpu_all();
}
let cpus = sys.cpus();
let physical_cores = sys.physical_core_count().unwrap_or(cpus.len());
let logical_cores = cpus.len();
let brand = cpus
.first()
.map(|c| c.brand().to_string())
.unwrap_or_else(|| "Unknown CPU".to_string());
let brand = platform_cpu_brand(brand);
let sysinfo_mhz = cpus.first().map(|c| c.frequency()).unwrap_or(0);
let frequency_mhz_raw = cpuid_16h_max_mhz();
#[cfg(target_os = "windows")]
let frequency_mhz_raw = frequency_mhz_raw.or_else(|| cpu_max_mhz_windows(logical_cores));
#[cfg(target_os = "macos")]
let frequency_mhz_raw = frequency_mhz_raw
.or_else(|| crate::collectors::platform::macos::apple_silicon_max_frequency_mhz(&brand));
let frequency_mhz = frequency_mhz_raw
.map(|v| v.max(sysinfo_mhz))
.unwrap_or(sysinfo_mhz);
let usage_percent: f32 = if cpus.is_empty() {
0.0
} else {
cpus.iter().map(|c| c.cpu_usage()).sum::<f32>() / cpus.len() as f32
};
let (load_1m, load_5m, load_15m) = get_load_averages(mode, logical_cores, usage_percent);
let sockets = if mode == CollectMode::Fast {
None } else {
Some(get_socket_count())
};
Ok(CpuInfo {
brand,
physical_cores,
logical_cores,
sockets,
frequency_mhz,
usage_percent,
load_1m,
load_5m,
load_15m,
})
}
#[cfg(unix)]
fn get_load_averages(
_mode: CollectMode,
core_count: usize,
_current_usage: f32,
) -> (Option<f64>, Option<f64>, Option<f64>) {
use std::fs;
if let Ok(content) = fs::read_to_string("/proc/loadavg") {
let parts: Vec<&str> = content.split_whitespace().collect();
if parts.len() >= 3 {
let load1: f64 = parts[0].parse().unwrap_or(0.0);
let load5: f64 = parts[1].parse().unwrap_or(0.0);
let load15: f64 = parts[2].parse().unwrap_or(0.0);
let max_load = core_count.max(1) as f64;
return (
Some((load1 / max_load * 100.0).min(100.0)),
Some((load5 / max_load * 100.0).min(100.0)),
Some((load15 / max_load * 100.0).min(100.0)),
);
}
}
let mut loadavg: [f64; 3] = [0.0; 3];
unsafe {
if libc::getloadavg(loadavg.as_mut_ptr(), 3) == 3 {
let max_load = core_count.max(1) as f64;
return (
Some((loadavg[0] / max_load * 100.0).min(100.0)),
Some((loadavg[1] / max_load * 100.0).min(100.0)),
Some((loadavg[2] / max_load * 100.0).min(100.0)),
);
}
}
(Some(0.0), Some(0.0), Some(0.0))
}
#[cfg(windows)]
fn get_load_averages(
mode: CollectMode,
_core_count: usize,
current_usage: f32,
) -> (Option<f64>, Option<f64>, Option<f64>) {
if mode == CollectMode::Fast {
return (None, None, None);
}
let usage = current_usage as f64;
(Some(usage), Some(usage), Some(usage))
}
#[cfg(not(any(unix, windows)))]
fn get_load_averages(
mode: CollectMode,
_core_count: usize,
current_usage: f32,
) -> (Option<f64>, Option<f64>, Option<f64>) {
if mode == CollectMode::Fast {
return (None, None, None);
}
let usage = current_usage as f64;
(Some(usage), Some(usage), Some(usage))
}
#[cfg(target_os = "linux")]
fn get_socket_count() -> usize {
if let Some(stdout) = run_stdout("lscpu", std::iter::empty::<&str>(), CommandTimeout::Normal) {
for line in stdout.lines() {
if line.starts_with("Socket(s):") {
if let Some(num) = line.split(':').nth(1) {
if let Ok(sockets) = num.trim().parse::<usize>() {
return sockets;
}
}
}
}
}
1
}
#[cfg(target_os = "windows")]
fn get_socket_count() -> usize {
crate::collectors::platform::windows::get_socket_count_native()
.unwrap_or_else(crate::collectors::platform::windows::get_socket_count_wmi)
}
#[cfg(target_os = "macos")]
fn get_socket_count() -> usize {
if let Some(stdout) = run_stdout("sysctl", ["-n", "hw.packages"], CommandTimeout::Normal) {
if let Ok(count) = stdout.trim().parse::<usize>() {
return count.max(1);
}
}
1
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
fn get_socket_count() -> usize {
1
}
#[cfg(target_os = "linux")]
fn linux_cpu_brand_fallback() -> Option<String> {
use std::fs;
if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
for key in ["model name", "Hardware", "Processor"] {
for line in cpuinfo.lines() {
if let Some((name, value)) = line.split_once(':') {
if name.trim() == key {
let value = value.trim();
if !value.is_empty() {
return Some(value.to_string());
}
}
}
}
}
}
if let Ok(model) = fs::read_to_string("/sys/firmware/devicetree/base/model") {
let model = model.trim_matches(char::from(0)).trim();
if !model.is_empty() {
return Some(model.to_string());
}
}
None
}
#[cfg(target_os = "macos")]
fn platform_cpu_brand(brand: String) -> String {
crate::collectors::platform::macos::get_cpu_brand().unwrap_or(brand)
}
#[cfg(target_os = "linux")]
fn platform_cpu_brand(brand: String) -> String {
if brand.trim().is_empty() || brand == "Unknown CPU" {
linux_cpu_brand_fallback().unwrap_or(brand)
} else {
brand
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn platform_cpu_brand(brand: String) -> String {
brand
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn cpuid_16h_max_mhz() -> Option<u64> {
#[cfg(target_arch = "x86")]
use std::arch::x86::__cpuid;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::__cpuid;
let max_leaf = __cpuid(0).eax;
if max_leaf < 0x16 {
return None;
}
let info = __cpuid(0x16);
if info.ebx > 0 {
Some(info.ebx as u64)
} else {
None
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn cpuid_16h_max_mhz() -> Option<u64> {
None
}
#[cfg(target_os = "windows")]
#[link(name = "powrprof")]
extern "system" {
fn CallNtPowerInformation(
InformationLevel: u32,
InputBuffer: *mut std::ffi::c_void,
InputBufferLength: u32,
OutputBuffer: *mut std::ffi::c_void,
OutputBufferLength: u32,
) -> i32;
}
#[cfg(target_os = "windows")]
#[repr(C)]
#[derive(Default, Copy, Clone)]
struct ProcessorPowerInformation {
number: u32,
max_mhz: u32,
current_mhz: u32,
mhz_limit: u32,
max_idle_state: u32,
current_idle_state: u32,
}
#[cfg(target_os = "windows")]
fn cpu_max_mhz_windows(logical_cores: usize) -> Option<u64> {
if logical_cores == 0 {
return None;
}
const PROCESSOR_INFORMATION: u32 = 11;
let mut buf: Vec<ProcessorPowerInformation> =
vec![ProcessorPowerInformation::default(); logical_cores];
let buf_size = (logical_cores * std::mem::size_of::<ProcessorPowerInformation>()) as u32;
let status = unsafe {
CallNtPowerInformation(
PROCESSOR_INFORMATION,
std::ptr::null_mut(),
0,
buf.as_mut_ptr() as *mut _,
buf_size,
)
};
if status != 0 {
return None;
}
buf.iter()
.map(|p| p.max_mhz as u64)
.max()
.filter(|&v| v > 0)
}
impl CpuInfo {
pub fn frequency_ghz(&self) -> String {
format!("{:.2} GHz", self.frequency_mhz as f64 / 1000.0)
}
pub fn cores_string(&self) -> String {
if self.physical_cores == self.logical_cores {
format!("{} cores", self.physical_cores)
} else {
format!(
"{} cores / {} threads",
self.physical_cores, self.logical_cores
)
}
}
}