use crate::Component;
#[cfg(target_os = "linux")]
use crate::cpu::CpuState;
#[cfg(target_os = "linux")]
use crate::cpu::read_proc_stat;
use crate::cpu::{AppCPUUtilization, CPUUtilization, ProcessCPUUtilization};
use crate::energy::{CPUEnergy, GPUEnergy, PlatformEnergy};
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub struct MonitorSample {
pub timestamp: u64,
pub cpu_power: f64,
pub gpu_power: f64,
pub total_power: f64,
pub cpu_usage: f64,
pub process_power: Option<f64>,
pub app_power: Option<(f64, usize)>,
}
impl MonitorSample {
pub fn pid_app_power(&self) -> f64 {
self.process_power
.or_else(|| self.app_power.map(|(p, _)| p))
.unwrap_or(0.0)
}
}
pub struct JoularCoreMonitor {
pub platform: Box<dyn PlatformEnergy>,
pub cpu_energy: Box<dyn CPUEnergy>,
pub gpu_energy: Box<dyn GPUEnergy>,
pub cpu_usage: Box<dyn CPUUtilization>,
pub process_tracker: Option<Box<dyn ProcessCPUUtilization>>,
pub app_tracker: Option<Box<dyn AppCPUUtilization>>,
pub cpu_idle_baseline: f64,
#[cfg(target_os = "linux")]
prev_cpu_state: Option<(u64, u64)>,
}
impl JoularCoreMonitor {
pub fn new(
platform: Box<dyn PlatformEnergy>,
cpu_energy: Box<dyn CPUEnergy>,
gpu_energy: Box<dyn GPUEnergy>,
cpu_usage: Box<dyn CPUUtilization>,
process_tracker: Option<Box<dyn ProcessCPUUtilization>>,
app_tracker: Option<Box<dyn AppCPUUtilization>>,
cpu_idle_baseline: f64,
) -> Self {
Self {
platform,
cpu_energy,
gpu_energy,
cpu_usage,
process_tracker,
app_tracker,
cpu_idle_baseline,
#[cfg(target_os = "linux")]
prev_cpu_state: None,
}
}
pub fn loop_init(&mut self) {
self.cpu_energy.get_power();
self.gpu_energy.get_power();
#[cfg(target_os = "linux")]
{
self.prev_cpu_state = read_proc_stat();
}
#[cfg(not(target_os = "linux"))]
{
self.cpu_usage.get_cpu_utilization();
}
}
pub fn set_cpu_idle_baseline(&mut self, baseline: f64) {
self.cpu_idle_baseline = baseline.max(0.0);
}
pub fn calibrate_cpu_idle_baseline(&mut self, samples: usize, interval: Duration) -> f64 {
self.loop_init();
let sample_count = samples.max(1);
let mut total_power = 0.0;
for _ in 0..sample_count {
thread::sleep(interval);
total_power += self.cpu_energy.get_power();
}
let baseline = total_power / sample_count as f64;
self.set_cpu_idle_baseline(baseline);
baseline
}
pub fn poll(
&mut self,
pid: Option<u32>,
app_name: Option<&str>,
component: Option<&Component>,
) -> MonitorSample {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let cpu_active = !matches!(component, Some(Component::Gpu));
let gpu_active = !matches!(component, Some(Component::Cpu));
let cpu_power = if cpu_active {
self.cpu_energy.get_power()
} else {
0.0
};
let gpu_power = if gpu_active {
self.gpu_energy.get_power()
} else {
0.0
};
let total_power = cpu_power + gpu_power;
let attributed_cpu_power = (cpu_power - self.cpu_idle_baseline).max(0.0);
let cpu_usage_val;
let cpu_snapshot: Option<(u64, u64)>;
if cpu_active {
#[cfg(target_os = "linux")]
{
let snapshot = read_proc_stat();
cpu_snapshot = snapshot;
cpu_usage_val = match (self.prev_cpu_state, snapshot) {
(Some((prev_total, prev_idle)), Some((curr_total, curr_idle))) => {
self.prev_cpu_state = Some((curr_total, curr_idle));
CpuState::new(curr_total, curr_idle)
.cpu_usage(&CpuState::new(prev_total, prev_idle))
* 100.0
}
(_, Some((curr_total, curr_idle))) => {
self.prev_cpu_state = Some((curr_total, curr_idle));
0.0
}
_ => 0.0,
};
}
#[cfg(not(target_os = "linux"))]
{
cpu_snapshot = None;
cpu_usage_val = self.cpu_usage.get_cpu_utilization() * 100.0;
}
} else {
cpu_snapshot = None;
cpu_usage_val = 0.0;
}
let mut process_power = None;
let mut app_power = None;
if cpu_active
&& let Some(pid_val) = pid
&& let Some(tracker) = &mut self.process_tracker
{
let util = tracker.get_process_cpu_utilization_with_total(
pid_val,
cpu_snapshot.map(|(total, _)| total),
);
let pwr = self
.platform
.process_energy(util, attributed_cpu_power, cpu_usage_val);
process_power = Some(pwr);
}
if cpu_active
&& let Some(name) = app_name
&& let Some(tracker) = &mut self.app_tracker
{
let (util, pids) =
tracker.get_app_snapshot_with_total(name, cpu_snapshot.map(|(total, _)| total));
let pwr = self
.platform
.app_energy(util, attributed_cpu_power, cpu_usage_val);
app_power = Some((pwr, pids.len()));
}
MonitorSample {
timestamp,
cpu_power,
gpu_power,
total_power,
cpu_usage: cpu_usage_val,
process_power,
app_power,
}
}
}