use objc2::{msg_send, rc::Retained};
use objc2_foundation::NSString;
use super::{CpuMetrics, FrequencyMetrics, FrequencyMonitor};
#[cfg(test)]
use crate::hardware::iokit::mock::MockIOKit;
use crate::{
error::Result,
hardware::iokit::{IOKit, IOKitImpl},
};
#[derive(Debug)]
pub struct CPU {
physical_cores: u32,
logical_cores: u32,
frequency_mhz: f64,
core_usage: Vec<f64>,
model_name: String,
temperature: Option<f64>,
iokit: Box<dyn IOKit>,
frequency_monitor: FrequencyMonitor,
frequency_metrics: Option<FrequencyMetrics>,
}
impl CPU {
pub fn new() -> Result<Self> {
let mut cpu = Self {
physical_cores: 0,
logical_cores: 0,
frequency_mhz: 0.0,
core_usage: Vec::new(),
model_name: String::new(),
temperature: None,
iokit: Box::new(IOKitImpl),
frequency_monitor: FrequencyMonitor::new(),
frequency_metrics: None,
};
cpu.update()?;
Ok(cpu)
}
pub fn update(&mut self) -> Result<()> {
let service = self.iokit.get_service("AppleACPICPU")?;
self.physical_cores = unsafe { msg_send![&*service, numberOfCores] };
self.logical_cores = unsafe { msg_send![&*service, numberOfProcessorCores] };
match self.frequency_monitor.get_metrics() {
Ok(metrics) => {
self.frequency_mhz = metrics.current;
self.frequency_metrics = Some(metrics);
},
Err(_) => {
let frequency: f64 = unsafe { msg_send![&*service, currentProcessorClockSpeed] };
self.frequency_mhz = frequency / 1_000_000.0;
self.frequency_metrics = None;
},
}
self.core_usage = self.fetch_core_usage()?;
let ns_name: Retained<NSString> = unsafe { msg_send![&*service, name] };
self.model_name = ns_name.to_string();
self.temperature = self.fetch_cpu_temperature();
Ok(())
}
fn fetch_core_usage(&self) -> Result<Vec<f64>> {
let mut usages = Vec::with_capacity(self.logical_cores as usize);
for i in 0..self.logical_cores {
let service = self.iokit.get_service("AppleACPICPU")?;
let usage: f64 = unsafe { msg_send![&*service, getCoreUsage: i] };
usages.push(usage);
}
Ok(usages)
}
fn fetch_cpu_temperature(&self) -> Option<f64> {
self.iokit.get_cpu_temperature().ok()
}
pub fn physical_cores(&self) -> u32 {
self.physical_cores
}
pub fn logical_cores(&self) -> u32 {
self.logical_cores
}
pub fn frequency_mhz(&self) -> f64 {
self.frequency_mhz
}
pub fn core_usage(&self) -> &[f64] {
&self.core_usage
}
pub fn model_name(&self) -> &str {
&self.model_name
}
pub fn temperature(&self) -> Option<f64> {
self.temperature
}
pub fn frequency_metrics(&self) -> Option<&FrequencyMetrics> {
self.frequency_metrics.as_ref()
}
pub fn min_frequency_mhz(&self) -> Option<f64> {
self.frequency_metrics.as_ref().map(|m| m.min)
}
pub fn max_frequency_mhz(&self) -> Option<f64> {
self.frequency_metrics.as_ref().map(|m| m.max)
}
pub fn available_frequencies(&self) -> Option<&[f64]> {
self.frequency_metrics.as_ref().map(|m| m.available.as_slice())
}
}
impl CpuMetrics for CPU {
fn get_cpu_usage(&self) -> f64 {
self.core_usage.iter().sum::<f64>() / self.logical_cores as f64
}
fn get_cpu_temperature(&self) -> Option<f64> {
self.temperature
}
fn get_cpu_frequency(&self) -> f64 {
self.frequency_mhz
}
}
#[cfg(test)]
mod tests {
use super::*;
impl CPU {
pub fn new_with_mock() -> Result<Self> {
let mut mock = MockIOKit::new();
mock.expect_get_cpu_temperature().returning(|| Ok(45.5));
mock.expect_get_service().returning(|_| {
use crate::utils::test_utils;
Ok(test_utils::create_test_object())
});
let cpu = Self {
physical_cores: 8,
logical_cores: 16,
frequency_mhz: 3200.0,
core_usage: vec![0.3, 0.5, 0.2, 0.8, 0.1, 0.3, 0.4, 0.6],
model_name: "Apple M1 Pro".to_string(),
temperature: Some(45.5),
iokit: Box::new(mock),
frequency_monitor: FrequencyMonitor::new(),
frequency_metrics: Some(FrequencyMetrics {
current: 3200.0,
min: 1200.0,
max: 3600.0,
available: vec![1200.0, 1800.0, 2400.0, 3000.0, 3600.0],
}),
};
Ok(cpu)
}
}
#[test]
fn test_cpu_initialization() {
let cpu = CPU::new_with_mock().expect("Failed to create CPU instance");
assert_eq!(cpu.physical_cores(), 8);
assert_eq!(cpu.logical_cores(), 16);
assert_eq!(cpu.frequency_mhz(), 3200.0);
assert_eq!(cpu.model_name(), "Apple M1 Pro");
assert_eq!(cpu.temperature(), Some(45.5));
assert_eq!(cpu.core_usage().len(), 8);
}
#[test]
fn test_cpu_usage() {
let cpu = CPU::new_with_mock().expect("Failed to create CPU instance");
assert_eq!(cpu.core_usage().len(), 8);
let expected_usage = 0.2; assert_eq!(cpu.get_cpu_usage(), expected_usage);
}
#[test]
fn test_cpu_metrics_trait() {
let cpu = CPU::new_with_mock().expect("Failed to create CPU instance");
assert_eq!(cpu.get_cpu_frequency(), 3200.0);
assert_eq!(cpu.get_cpu_temperature(), Some(45.5));
assert!(cpu.get_cpu_usage() >= 0.0 && cpu.get_cpu_usage() <= 1.0);
}
#[test]
fn test_frequency_metrics() {
let cpu = CPU::new_with_mock().expect("Failed to create CPU instance");
assert_eq!(cpu.frequency_mhz(), 3200.0);
assert_eq!(cpu.min_frequency_mhz(), Some(1200.0));
assert_eq!(cpu.max_frequency_mhz(), Some(3600.0));
let available = cpu.available_frequencies().unwrap();
assert_eq!(available.len(), 5);
assert_eq!(available[0], 1200.0);
assert_eq!(available[4], 3600.0);
let metrics = cpu.frequency_metrics().unwrap();
assert_eq!(metrics.current, 3200.0);
assert_eq!(metrics.min, 1200.0);
assert_eq!(metrics.max, 3600.0);
assert_eq!(metrics.available.len(), 5);
}
}