release/1.2.0 #2
@@ -38,7 +38,7 @@ impl OptimizerEngine {
|
|||||||
Self { window_size }
|
Self { window_size }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a simple moving average (SMA) filter to a stream of values.
|
/// Applies a simple moving average (SMA) filter with outlier rejection.
|
||||||
pub fn smooth(&self, data: &[f32]) -> Vec<f32> {
|
pub fn smooth(&self, data: &[f32]) -> Vec<f32> {
|
||||||
if data.is_empty() { return vec![]; }
|
if data.is_empty() { return vec![]; }
|
||||||
let mut smoothed = Vec::with_capacity(data.len());
|
let mut smoothed = Vec::with_capacity(data.len());
|
||||||
@@ -46,8 +46,19 @@ impl OptimizerEngine {
|
|||||||
for i in 0..data.len() {
|
for i in 0..data.len() {
|
||||||
let start = if i < self.window_size { 0 } else { i - self.window_size + 1 };
|
let start = if i < self.window_size { 0 } else { i - self.window_size + 1 };
|
||||||
let end = i + 1;
|
let end = i + 1;
|
||||||
let sum: f32 = data[start..end].iter().sum();
|
|
||||||
smoothed.push(sum / (end - start) as f32);
|
// Outlier rejection: only average values within a reasonable range
|
||||||
|
let window = &data[start..end];
|
||||||
|
let avg: f32 = window.iter().sum::<f32>() / window.len() as f32;
|
||||||
|
let filtered: Vec<f32> = window.iter()
|
||||||
|
.filter(|&&v| (v - avg).abs() < 20.0) // Reject spikes > 20 units
|
||||||
|
.cloned().collect();
|
||||||
|
|
||||||
|
if filtered.is_empty() {
|
||||||
|
smoothed.push(avg);
|
||||||
|
} else {
|
||||||
|
smoothed.push(filtered.iter().sum::<f32>() / filtered.len() as f32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
smoothed
|
smoothed
|
||||||
}
|
}
|
||||||
@@ -55,11 +66,9 @@ impl OptimizerEngine {
|
|||||||
/// Calculates Thermal Resistance: R_theta = (T_core - T_ambient) / P_package
|
/// Calculates Thermal Resistance: R_theta = (T_core - T_ambient) / P_package
|
||||||
pub fn calculate_thermal_resistance(&self, profile: &ThermalProfile) -> f32 {
|
pub fn calculate_thermal_resistance(&self, profile: &ThermalProfile) -> f32 {
|
||||||
profile.points.iter()
|
profile.points.iter()
|
||||||
|
.filter(|p| p.power_w > 1.0 && p.temp_c > 30.0) // Filter invalid data
|
||||||
.max_by(|a, b| a.power_w.partial_cmp(&b.power_w).unwrap_or(std::cmp::Ordering::Equal))
|
.max_by(|a, b| a.power_w.partial_cmp(&b.power_w).unwrap_or(std::cmp::Ordering::Equal))
|
||||||
.map(|p| {
|
.map(|p| (p.temp_c - profile.ambient_temp) / p.power_w)
|
||||||
if p.power_w < 1.0 { 0.0 }
|
|
||||||
else { (p.temp_c - profile.ambient_temp) / p.power_w }
|
|
||||||
})
|
|
||||||
.unwrap_or(0.0)
|
.unwrap_or(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +82,16 @@ impl OptimizerEngine {
|
|||||||
/// Finds the "Silicon Knee" - the point where performance per watt (efficiency)
|
/// Finds the "Silicon Knee" - the point where performance per watt (efficiency)
|
||||||
/// starts to diminish significantly and thermal density spikes.
|
/// starts to diminish significantly and thermal density spikes.
|
||||||
pub fn find_silicon_knee(&self, profile: &ThermalProfile) -> f32 {
|
pub fn find_silicon_knee(&self, profile: &ThermalProfile) -> f32 {
|
||||||
if profile.points.len() < 3 {
|
let valid_points: Vec<_> = profile.points.iter()
|
||||||
|
.filter(|p| p.power_w > 5.0 && p.temp_c > 40.0) // Filter idle/noise
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if valid_points.len() < 3 {
|
||||||
return profile.points.last().map(|p| p.power_w).unwrap_or(15.0);
|
return profile.points.last().map(|p| p.power_w).unwrap_or(15.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut points = profile.points.clone();
|
let mut points = valid_points;
|
||||||
points.sort_by(|a, b| a.power_w.partial_cmp(&b.power_w).unwrap_or(std::cmp::Ordering::Equal));
|
points.sort_by(|a, b| a.power_w.partial_cmp(&b.power_w).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
|
|
||||||
let mut best_pl = points[0].power_w;
|
let mut best_pl = points[0].power_w;
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
while let Ok(new_state) = telemetry_rx.try_recv() {
|
while let Ok(new_state) = telemetry_rx.try_recv() {
|
||||||
if let Some(log) = &new_state.log_event {
|
if let Some(log) = &new_state.log_event {
|
||||||
ui_state.logs.push(log.clone());
|
ui_state.add_log(log.clone());
|
||||||
debug!("Backend Log: {}", log);
|
debug!("Backend Log: {}", log);
|
||||||
} else {
|
} else {
|
||||||
ui_state.update(&new_state);
|
ui_state.update(&new_state);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use sysinfo::System;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::sal::traits::{PlatformSal, AuditStep, SafetyStatus};
|
use crate::sal::traits::{PlatformSal, AuditStep, SafetyStatus};
|
||||||
use crate::sal::heuristic::discovery::SystemFactSheet;
|
use crate::sal::heuristic::discovery::SystemFactSheet;
|
||||||
@@ -80,6 +79,21 @@ impl BenchmarkOrchestrator {
|
|||||||
// Start Watchdog Monitor
|
// Start Watchdog Monitor
|
||||||
let _watchdog_handle = self.spawn_watchdog_monitor();
|
let _watchdog_handle = self.spawn_watchdog_monitor();
|
||||||
|
|
||||||
|
// Use a closure to ensure cleanup always runs
|
||||||
|
let result = self.execute_benchmark();
|
||||||
|
|
||||||
|
// --- MANDATORY CLEANUP ---
|
||||||
|
self.log("Benchmark sequence finished. Restoring hardware defaults...")?;
|
||||||
|
let _ = self.workload.stop();
|
||||||
|
if let Err(e) = self.sal.restore() {
|
||||||
|
anyhow::bail!("CRITICAL: Failed to restore hardware state: {}", e);
|
||||||
|
}
|
||||||
|
self.log("✓ Hardware state restored.")?;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_benchmark(&mut self) -> Result<OptimizationResult> {
|
||||||
// Phase 1: Audit & Baseline
|
// Phase 1: Audit & Baseline
|
||||||
self.phase = BenchmarkPhase::Auditing;
|
self.phase = BenchmarkPhase::Auditing;
|
||||||
for step in self.sal.audit() {
|
for step in self.sal.audit() {
|
||||||
@@ -208,9 +222,6 @@ impl BenchmarkOrchestrator {
|
|||||||
res.config_paths.insert("i8kmon".to_string(), i8k_path.clone());
|
res.config_paths.insert("i8kmon".to_string(), i8k_path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sal.restore()?;
|
|
||||||
self.log("✓ Environment restored.")?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,9 +239,7 @@ impl BenchmarkOrchestrator {
|
|||||||
abort.store(true, Ordering::SeqCst);
|
abort.store(true, Ordering::SeqCst);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ok(SafetyStatus::Warning(_msg)) | Ok(SafetyStatus::Critical(_msg)) => {
|
Ok(SafetyStatus::Warning(_msg)) | Ok(SafetyStatus::Critical(_msg)) => {}
|
||||||
// Send warning log to UI
|
|
||||||
}
|
|
||||||
Ok(SafetyStatus::Nominal) => {}
|
Ok(SafetyStatus::Nominal) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
*reason_store.lock().unwrap() = Some(format!("Watchdog Sensor Failure: {}", e));
|
*reason_store.lock().unwrap() = Some(format!("Watchdog Sensor Failure: {}", e));
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ impl PreflightAuditor for DellXps9380Sal {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tool availability check
|
|
||||||
let tool_check = self.fact_sheet.paths.tools.contains_key("dell_fan_ctrl");
|
let tool_check = self.fact_sheet.paths.tools.contains_key("dell_fan_ctrl");
|
||||||
steps.push(AuditStep {
|
steps.push(AuditStep {
|
||||||
description: "Dell Fan Control Tool".to_string(),
|
description: "Dell Fan Control Tool".to_string(),
|
||||||
@@ -125,6 +124,7 @@ impl EnvironmentGuard for DellXps9380Sal {
|
|||||||
let mut suppressed = self.suppressed_services.lock().unwrap();
|
let mut suppressed = self.suppressed_services.lock().unwrap();
|
||||||
for s in services {
|
for s in services {
|
||||||
if Command::new("systemctl").args(["is-active", "--quiet", s]).status()?.success() {
|
if Command::new("systemctl").args(["is-active", "--quiet", s]).status()?.success() {
|
||||||
|
debug!("Suppressing service: {}", s);
|
||||||
Command::new("systemctl").args(["stop", s]).status()?;
|
Command::new("systemctl").args(["stop", s]).status()?;
|
||||||
suppressed.push(s.to_string());
|
suppressed.push(s.to_string());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,29 @@ use ratatui::{
|
|||||||
Frame,
|
Frame,
|
||||||
prelude::Stylize,
|
prelude::Stylize,
|
||||||
};
|
};
|
||||||
|
use std::collections::VecDeque;
|
||||||
use crate::mediator::TelemetryState;
|
use crate::mediator::TelemetryState;
|
||||||
use crate::ui::theme::*;
|
use crate::ui::theme::*;
|
||||||
|
|
||||||
/// DashboardState maintains UI-specific state that isn't part of the core telemetry,
|
/// DashboardState maintains UI-specific state that isn't part of the core telemetry,
|
||||||
/// such as the accumulated diagnostic logs.
|
/// such as the accumulated diagnostic logs.
|
||||||
pub struct DashboardState {
|
pub struct DashboardState {
|
||||||
pub logs: Vec<String>,
|
pub logs: VecDeque<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DashboardState {
|
impl DashboardState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
let mut logs = VecDeque::with_capacity(100);
|
||||||
logs: vec!["ember-tune Initialized.".to_string()],
|
logs.push_back("ember-tune Initialized.".to_string());
|
||||||
|
Self { logs }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a log message and ensures the buffer does not exceed capacity.
|
||||||
|
pub fn add_log(&mut self, msg: String) {
|
||||||
|
if self.logs.len() >= 100 {
|
||||||
|
self.logs.pop_front();
|
||||||
}
|
}
|
||||||
|
self.logs.push_back(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the UI state based on new telemetry.
|
/// Updates the UI state based on new telemetry.
|
||||||
|
|||||||
Reference in New Issue
Block a user