use crate::cpu::{
AppCPUUtilization, AppMonitor, CPUUtilization, ProcStatCpuUsage, ProcStatProcessUtil,
ProcessCPUUtilization,
};
use crate::energy::{CPUEnergy, GPUEnergy, PlatformEnergy};
use crate::platform::amdgpu;
use crate::platform::nvidia;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;
pub struct LinuxCpu {
rapl: Mutex<Option<IntelRapl>>,
warned_read_failure: AtomicBool,
}
impl LinuxCpu {
pub fn new() -> Self {
match IntelRapl::new() {
Ok(rapl) => Self {
rapl: std::sync::Mutex::new(Some(rapl)),
warned_read_failure: AtomicBool::new(false),
},
Err(e) => {
crate::logging::print_warning(&format!(
"RAPL is not available: {}. Continuing without CPU power data",
e
));
Self {
rapl: std::sync::Mutex::new(None),
warned_read_failure: AtomicBool::new(false),
}
}
}
}
}
impl CPUEnergy for LinuxCpu {
fn get_power(&self) -> f64 {
let mut rapl = self.rapl.lock().unwrap_or_else(|e| e.into_inner());
match rapl.as_mut() {
Some(rapl) => match rapl.calculate_power() {
Ok(power) => power,
Err(e) => {
if !self.warned_read_failure.swap(true, Ordering::Relaxed) {
tracing::warn!(error = %e, "RAPL read failed; reporting 0 W until it recovers");
}
0.0
}
},
None => 0.0,
}
}
}
pub struct LinuxGpu;
impl GPUEnergy for LinuxGpu {
fn get_power(&self) -> f64 {
let mut gpu_energy = 0.0;
if nvidia::is_nvidia_supported() {
gpu_energy += nvidia::get_nvidia_power();
}
if amdgpu::is_amdgpu_supported() {
gpu_energy += amdgpu::get_amdgpu_power();
}
gpu_energy
}
}
pub struct LinuxPlatform;
impl LinuxPlatform {
pub fn new() -> Self {
LinuxPlatform
}
}
impl PlatformEnergy for LinuxPlatform {
fn cpu(&self) -> Box<dyn CPUEnergy> {
Box::new(LinuxCpu::new())
}
fn gpu(&self) -> Box<dyn GPUEnergy> {
Box::new(LinuxGpu)
}
fn cpu_usage(&self) -> Box<dyn CPUUtilization> {
Box::new(ProcStatCpuUsage::new())
}
fn process_cpu_usage(&self) -> Option<Box<dyn ProcessCPUUtilization>> {
Some(Box::new(ProcStatProcessUtil::new()))
}
fn app_cpu_usage(
&self,
refresh_interval: std::time::Duration,
) -> Option<Box<dyn AppCPUUtilization>> {
Some(Box::new(AppMonitor::new(
ProcStatProcessUtil::new,
refresh_interval,
)))
}
}
const RAPL_PATH: &str = "/sys/class/powercap/intel-rapl";
pub struct IntelRapl {
energy_path: PathBuf,
max_energy_range: f64,
last_energy: f64,
last_read_at: Option<Instant>,
}
impl IntelRapl {
pub fn new() -> Result<Self, String> {
let pkg_dir = PathBuf::from(format!("{}/intel-rapl:0", RAPL_PATH));
let name_path = pkg_dir.join("name");
let energy_path = pkg_dir.join("energy_uj");
let max_energy_range_path = pkg_dir.join("max_energy_range_uj");
let name_ok = fs::read_to_string(&name_path)
.map(|name| name.trim() == "package-0")
.unwrap_or(false);
if !name_ok {
return Err("No supported RAPL package found (expected pkg/package-0)".to_string());
}
let max_energy_range = fs::read_to_string(&max_energy_range_path)
.map_err(|e| format!("Failed to read max_energy_range: {}", e))?
.trim()
.parse::<f64>()
.map_err(|e| format!("Failed to parse max_energy_range: {}", e))?
/ 1_000_000.0;
Ok(Self {
energy_path,
max_energy_range,
last_energy: 0.0,
last_read_at: None,
})
}
pub fn read_energy(&self) -> Result<f64, io::Error> {
let microjoules = fs::read_to_string(&self.energy_path)?
.trim()
.parse::<f64>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(microjoules / 1_000_000.0)
}
pub fn max_energy_range(&self) -> f64 {
self.max_energy_range
}
pub fn calculate_power(&mut self) -> io::Result<f64> {
let current_energy: f64 = self.read_energy()?;
let current_read_at = Instant::now();
let Some(last_read_at) = self.last_read_at else {
self.last_energy = current_energy;
self.last_read_at = Some(current_read_at);
return Ok(0.0);
};
let energy_delta = if current_energy >= self.last_energy {
current_energy - self.last_energy
} else {
current_energy - self.last_energy + self.max_energy_range()
};
let elapsed_secs = current_read_at.duration_since(last_read_at).as_secs_f64();
self.last_energy = current_energy;
self.last_read_at = Some(current_read_at);
if elapsed_secs <= 0.0 || !elapsed_secs.is_finite() {
return Ok(0.0);
}
Ok(energy_delta / elapsed_secs)
}
}