use libc;
use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct FrequencyMetrics {
pub current: f64,
pub min: f64,
pub max: f64,
pub available: Vec<f64>,
}
#[derive(Debug)]
pub struct FrequencyMonitor;
impl Default for FrequencyMonitor {
fn default() -> Self {
FrequencyMonitor
}
}
impl FrequencyMonitor {
pub fn new() -> Self {
Self
}
pub fn get_metrics(&self) -> Result<FrequencyMetrics> {
fetch_cpu_frequencies()
}
}
#[derive(Default)]
struct CpuInfo {
current_frequency: f64,
min_frequency: f64,
max_frequency: f64,
available_frequencies: Vec<f64>,
}
fn fetch_cpu_frequencies() -> Result<FrequencyMetrics> {
let cpu_info = unsafe { retrieve_cpu_info()? };
Ok(FrequencyMetrics {
current: cpu_info.current_frequency,
min: cpu_info.min_frequency,
max: cpu_info.max_frequency,
available: cpu_info.available_frequencies,
})
}
unsafe fn retrieve_cpu_info() -> Result<CpuInfo> {
let current_frequency = fetch_sysctl_frequency_by_name("hw.cpufrequency")? / 1_000_000.0;
let min_frequency = fetch_sysctl_frequency_by_name("hw.cpufrequency_min")? / 1_000_000.0;
let max_frequency = fetch_sysctl_frequency_by_name("hw.cpufrequency_max")? / 1_000_000.0;
let mut available_frequencies = Vec::new();
if min_frequency > 0.0 && max_frequency > min_frequency {
let step = (max_frequency - min_frequency) / 4.0;
available_frequencies = vec![
min_frequency,
min_frequency + step,
min_frequency + step * 2.0,
min_frequency + step * 3.0,
max_frequency,
];
}
Ok(CpuInfo { current_frequency, min_frequency, max_frequency, available_frequencies })
}
unsafe fn fetch_sysctl_frequency_by_name(name: &str) -> Result<f64> {
use std::ffi::CString;
let c_name = CString::new(name).map_err(|_| {
Error::system(format!("Failed to create C string for sysctl name: {}", name))
})?;
let mut freq: u64 = 0;
let mut size = std::mem::size_of::<u64>();
let result = libc::sysctlbyname(
c_name.as_ptr(),
&mut freq as *mut _ as *mut libc::c_void,
&mut size,
std::ptr::null_mut(),
0,
);
if result != 0 {
return Err(Error::system(format!(
"Failed to fetch CPU frequency via sysctlbyname: {}",
name
)));
}
Ok(freq as f64)
}
#[cfg(test)]
mod tests {
use super::*;
unsafe fn fetch_sysctl_frequency(name1: i32, name2: i32, _size: usize) -> Result<f64> {
if name1 < 0 || name2 < 0 {
return Err(Error::system("Invalid MIB for sysctl".to_string()));
}
Ok(100.0) }
unsafe fn fetch_sysctl_frequency_by_name(name: &str) -> Result<f64> {
if name.contains('\0') {
return Err(Error::system("Invalid null byte in sysctl name".to_string()));
}
if name.contains("nonexistent") {
return Err(Error::system(format!("Nonexistent sysctl: {}", name)));
}
Ok(100.0) }
#[test]
fn test_frequency_metrics() {
let metrics = FrequencyMetrics {
current: 2400.0,
min: 1200.0,
max: 3600.0,
available: vec![1200.0, 1800.0, 2400.0, 3000.0, 3600.0],
};
assert_eq!(metrics.current, 2400.0);
assert_eq!(metrics.min, 1200.0);
assert_eq!(metrics.max, 3600.0);
assert_eq!(metrics.available.len(), 5);
}
#[test]
fn test_frequency_monitor_new() {
let monitor = FrequencyMonitor::new();
assert!(matches!(monitor, FrequencyMonitor));
}
#[test]
fn test_frequency_monitor_default() {
let monitor = FrequencyMonitor;
assert!(matches!(monitor, FrequencyMonitor));
}
#[test]
fn test_cpu_info_default() {
let info = CpuInfo::default();
assert_eq!(info.current_frequency, 0.0);
assert_eq!(info.min_frequency, 0.0);
assert_eq!(info.max_frequency, 0.0);
assert!(info.available_frequencies.is_empty());
}
#[test]
fn test_available_frequencies_calculation() {
let cpu_info = CpuInfo {
current_frequency: 2000.0,
min_frequency: 1000.0,
max_frequency: 3000.0,
available_frequencies: Vec::new(),
};
let metrics = FrequencyMetrics {
current: cpu_info.current_frequency,
min: cpu_info.min_frequency,
max: cpu_info.max_frequency,
available: vec![1000.0, 1500.0, 2000.0, 2500.0, 3000.0],
};
let step = metrics.available[1] - metrics.available[0];
for i in 1..metrics.available.len() {
if i < metrics.available.len() - 1 {
assert_eq!(metrics.available[i + 1] - metrics.available[i], step);
}
}
assert_eq!(metrics.available.first(), Some(&metrics.min));
assert_eq!(metrics.available.last(), Some(&metrics.max));
}
#[test]
#[cfg(target_os = "macos")]
fn test_fetch_sysctl_frequency() {
unsafe {
let result = fetch_sysctl_frequency(-1, -1, std::mem::size_of::<u64>());
assert!(result.is_err());
if let Err(err) = result {
assert!(matches!(err, Error::System(_)));
}
let result = fetch_sysctl_frequency(1, 1, std::mem::size_of::<u64>());
assert!(result.is_ok());
assert_eq!(result.unwrap(), 100.0);
}
}
#[test]
fn test_fetch_sysctl_frequency_by_name_error() {
unsafe {
let result = fetch_sysctl_frequency_by_name("invalid\0name");
assert!(result.is_err());
let result = fetch_sysctl_frequency_by_name("hw.nonexistent.frequency");
assert!(result.is_err());
}
}
#[test]
fn test_fetch_cpu_frequencies() {
let result = fetch_cpu_frequencies();
if result.is_ok() {
let metrics = result.unwrap();
assert!(metrics.current > 0.0);
assert!(metrics.min > 0.0);
assert!(metrics.max > 0.0);
assert!(!metrics.available.is_empty());
}
}
#[test]
fn test_frequency_monitor_get_metrics() {
let monitor = FrequencyMonitor;
let result = monitor.get_metrics();
if result.is_ok() {
let metrics = result.unwrap();
assert!(metrics.current > 0.0);
assert!(metrics.min > 0.0);
assert!(metrics.max > 0.0);
assert!(!metrics.available.is_empty());
}
}
}