use std::time::Instant;
use crate::app_state::AppState;
use crate::common::config::AppConfig;
use crate::metrics::energy::EnergyKey;
pub struct DataAggregator;
impl DataAggregator {
pub fn new() -> Self {
Self
}
pub fn update_utilization_history(&self, state: &mut AppState) {
self.update_cpu_history(state);
self.update_gpu_history(state);
}
pub fn update_energy_counters(&self, state: &mut AppState) {
let now = Instant::now();
let mut samples: Vec<(EnergyKey, f64)> = Vec::with_capacity(
state.gpu_info.len() + state.cpu_info.len() + state.chassis_info.len(),
);
for gpu in &state.gpu_info {
if let Some(watts) = gpu.power_consumption_reading() {
let key = EnergyKey::gpu(gpu.hostname.clone(), gpu.uuid.clone());
samples.push((key, watts));
}
}
for cpu in &state.cpu_info {
if let Some(power) = cpu.power_consumption {
let key = EnergyKey::cpu(cpu.hostname.clone());
samples.push((key, power));
}
}
for chassis in &state.chassis_info {
if let Some(power) = chassis.total_power_watts {
let key = EnergyKey::chassis(chassis.hostname.clone());
samples.push((key, power));
}
}
let wal_index = &mut state.energy_wal_replay;
let integrator = state.energy.integrator_mut();
for (key, watts) in samples {
if !integrator.has_samples(&key) && !wal_index.is_empty() {
wal_index.seed_if_matches(&key, integrator);
}
integrator.record_sample(key, now, watts);
}
}
fn update_cpu_history(&self, state: &mut AppState) {
if state.cpu_info.is_empty() {
return;
}
let avg_cpu_utilization = state
.cpu_info
.iter()
.map(|cpu| cpu.utilization)
.sum::<f64>()
/ state.cpu_info.len() as f64;
let avg_system_memory_usage = if !state.memory_info.is_empty() {
state
.memory_info
.iter()
.map(|mem| {
if mem.total_bytes > 0 {
(mem.used_bytes as f64 / mem.total_bytes as f64) * 100.0
} else {
0.0
}
})
.sum::<f64>()
/ state.memory_info.len() as f64
} else {
0.0
};
let cpu_temps: Vec<f64> = state
.cpu_info
.iter()
.filter_map(|cpu| cpu.temperature.map(|t| t as f64))
.collect();
let avg_cpu_temperature = if !cpu_temps.is_empty() {
cpu_temps.iter().sum::<f64>() / cpu_temps.len() as f64
} else {
0.0
};
state.cpu_utilization_history.push_back(avg_cpu_utilization);
state
.system_memory_history
.push_back(avg_system_memory_usage);
state.cpu_temperature_history.push_back(avg_cpu_temperature);
if state.cpu_utilization_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.cpu_utilization_history.pop_front();
}
if state.system_memory_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.system_memory_history.pop_front();
}
if state.cpu_temperature_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.cpu_temperature_history.pop_front();
}
}
fn update_gpu_history(&self, state: &mut AppState) {
let has_gpu_data = !state.gpu_info.is_empty();
let is_apple_silicon = state.gpu_info.iter().any(|gpu| {
gpu.detail
.get("architecture")
.map(|arch| arch == "Apple Silicon")
.unwrap_or(false)
});
if has_gpu_data
&& (state.gpu_info.iter().any(|gpu| gpu.total_memory > 0) || is_apple_silicon)
{
let avg_utilization = crate::metrics::gpu_readings::mean_utilization(&state.gpu_info);
let avg_memory = state
.gpu_info
.iter()
.map(|gpu| {
if gpu.total_memory > 0 {
(gpu.used_memory as f64 / gpu.total_memory as f64) * 100.0
} else {
0.0
}
})
.sum::<f64>()
/ state.gpu_info.len() as f64;
let avg_temperature = crate::metrics::gpu_readings::mean_temperature(&state.gpu_info);
if let Some(util) = avg_utilization {
state.utilization_history.push_back(util);
}
state.memory_history.push_back(avg_memory);
if let Some(temp) = avg_temperature {
state.temperature_history.push_back(temp);
}
state
.package_power_history
.push_back(current_package_power_watts(state));
if detect_apple_silicon(state) {
state
.ane_power_history
.push_back(current_ane_power_watts(state));
}
if state.utilization_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.utilization_history.pop_front();
}
if state.memory_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.memory_history.pop_front();
}
if state.temperature_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.temperature_history.pop_front();
}
if state.package_power_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.package_power_history.pop_front();
}
if state.ane_power_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.ane_power_history.pop_front();
}
} else if !state.cpu_info.is_empty() {
self.update_fallback_history(state);
}
}
fn update_fallback_history(&self, state: &mut AppState) {
let avg_cpu_utilization = state
.cpu_info
.iter()
.map(|cpu| cpu.utilization)
.sum::<f64>()
/ state.cpu_info.len() as f64;
let avg_memory_usage = if !state.memory_info.is_empty() {
state
.memory_info
.iter()
.map(|mem| {
if mem.total_bytes > 0 {
(mem.used_bytes as f64 / mem.total_bytes as f64) * 100.0
} else {
0.0
}
})
.sum::<f64>()
/ state.memory_info.len() as f64
} else {
0.0
};
let cpu_temps: Vec<f64> = state
.cpu_info
.iter()
.filter_map(|cpu| cpu.temperature.map(|t| t as f64))
.collect();
let avg_temperature = if !cpu_temps.is_empty() {
cpu_temps.iter().sum::<f64>() / cpu_temps.len() as f64
} else {
0.0
};
state.utilization_history.push_back(avg_cpu_utilization);
state.memory_history.push_back(avg_memory_usage);
state.temperature_history.push_back(avg_temperature);
state.package_power_history.push_back(0.0);
if state.utilization_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.utilization_history.pop_front();
}
if state.memory_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.memory_history.pop_front();
}
if state.temperature_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.temperature_history.pop_front();
}
if state.package_power_history.len() > AppConfig::HISTORY_MAX_ENTRIES {
state.package_power_history.pop_front();
}
}
#[allow(dead_code)]
pub fn calculate_avg_gpu_utilization(state: &AppState) -> f64 {
if state.gpu_info.is_empty() {
return 0.0;
}
crate::metrics::gpu_readings::mean_utilization(&state.gpu_info).unwrap_or(0.0)
}
#[allow(dead_code)]
pub fn calculate_avg_gpu_memory(state: &AppState) -> f64 {
if state.gpu_info.is_empty() {
return 0.0;
}
state
.gpu_info
.iter()
.map(|gpu| {
if gpu.total_memory > 0 {
(gpu.used_memory as f64 / gpu.total_memory as f64) * 100.0
} else {
0.0
}
})
.sum::<f64>()
/ state.gpu_info.len() as f64
}
#[allow(dead_code)]
pub fn calculate_avg_cpu_utilization(state: &AppState) -> f64 {
if state.cpu_info.is_empty() {
return 0.0;
}
state
.cpu_info
.iter()
.map(|cpu| cpu.utilization)
.sum::<f64>()
/ state.cpu_info.len() as f64
}
#[allow(dead_code)]
pub fn calculate_avg_system_memory(state: &AppState) -> f64 {
if state.memory_info.is_empty() {
return 0.0;
}
state
.memory_info
.iter()
.map(|mem| {
if mem.total_bytes > 0 {
(mem.used_bytes as f64 / mem.total_bytes as f64) * 100.0
} else {
0.0
}
})
.sum::<f64>()
/ state.memory_info.len() as f64
}
}
fn detect_apple_silicon(state: &AppState) -> bool {
state.gpu_info.iter().any(|gpu| {
gpu.detail
.get("architecture")
.map(|arch| arch == "Apple Silicon")
.unwrap_or(false)
})
}
fn current_package_power_watts(state: &AppState) -> f64 {
if detect_apple_silicon(state) {
state
.gpu_info
.iter()
.find_map(|gpu| {
gpu.detail
.get("combined_power_mw")
.and_then(|value| value.parse::<f64>().ok())
.map(|mw| mw / 1000.0)
})
.unwrap_or_else(|| crate::metrics::gpu_readings::total_power_watts(&state.gpu_info))
} else {
crate::metrics::gpu_readings::total_power_watts(&state.gpu_info)
}
}
fn current_ane_power_watts(state: &AppState) -> f64 {
crate::metrics::gpu_readings::first_ane_power_watts(&state.gpu_info).unwrap_or(0.0)
}
impl Default for DataAggregator {
fn default() -> Self {
Self::new()
}
}