pub const DEFAULT_COLD_START_MARGIN: f64 = 15.0;
pub const MIN_SAMPLES_FOR_CONTEXT: usize = 5;
pub const DEFAULT_STALENESS_SEC: u64 = 3600;
#[derive(Debug, Clone)]
pub struct SystemContext {
pub timestamp: u64,
pub cpu_temp_c: f64,
pub gpu_temp_c: f64,
pub memory_percent: f64,
pub cpu_freq_mhz: f64,
pub cpu_freq_max_mhz: f64,
pub cache_warm: bool,
pub load_average: f64,
}
impl Default for SystemContext {
fn default() -> Self {
Self {
timestamp: 0,
cpu_temp_c: 50.0,
gpu_temp_c: 50.0,
memory_percent: 50.0,
cpu_freq_mhz: 3000.0,
cpu_freq_max_mhz: 4000.0,
cache_warm: false,
load_average: 1.0,
}
}
}
impl SystemContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_timestamp(mut self, ts: u64) -> Self {
self.timestamp = ts;
self
}
pub fn with_cpu_temp(mut self, temp_c: f64) -> Self {
self.cpu_temp_c = temp_c;
self
}
pub fn with_gpu_temp(mut self, temp_c: f64) -> Self {
self.gpu_temp_c = temp_c;
self
}
pub fn with_memory(mut self, percent: f64) -> Self {
self.memory_percent = percent.clamp(0.0, 100.0);
self
}
pub fn with_cpu_freq(mut self, freq_mhz: f64, max_mhz: f64) -> Self {
self.cpu_freq_mhz = freq_mhz;
self.cpu_freq_max_mhz = max_mhz;
self
}
pub fn with_cache_warm(mut self, warm: bool) -> Self {
self.cache_warm = warm;
self
}
pub fn with_load(mut self, load: f64) -> Self {
self.load_average = load;
self
}
pub fn capture() -> Self {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
Self {
timestamp,
cpu_temp_c: 60.0,
gpu_temp_c: 55.0,
memory_percent: 50.0,
cpu_freq_mhz: 3000.0,
cpu_freq_max_mhz: 4000.0,
cache_warm: false,
load_average: 1.0,
}
}
pub fn freq_utilization(&self) -> f64 {
if self.cpu_freq_max_mhz > 0.0 {
self.cpu_freq_mhz / self.cpu_freq_max_mhz
} else {
1.0
}
}
pub fn thermal_headroom(&self, throttle_temp: f64) -> f64 {
(throttle_temp - self.cpu_temp_c).max(0.0)
}
pub fn is_stale(&self, max_age_sec: u64) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
now.saturating_sub(self.timestamp) > max_age_sec
}
pub fn to_json(&self) -> String {
format!(
r#"{{"timestamp":{},"cpu_temp_c":{},"memory_percent":{},"cpu_freq_mhz":{},"cache_warm":{}}}"#,
self.timestamp,
self.cpu_temp_c,
self.memory_percent,
self.cpu_freq_mhz,
self.cache_warm
)
}
}
#[derive(Debug, Clone)]
pub struct BaselineEntry {
pub context: SystemContext,
pub value: f64,
pub metric: String,
}
impl BaselineEntry {
pub fn new(metric: &str, value: f64, context: SystemContext) -> Self {
Self {
metric: metric.to_string(),
value,
context,
}
}
}
#[derive(Debug, Clone)]
pub struct RegressionThreshold {
pub base_percent: f64,
pub temp_adjustment: f64,
pub memory_adjustment: f64,
pub freq_adjustment: f64,
pub cache_adjustment: f64,
pub final_percent: f64,
pub confidence: f64,
pub sample_count: usize,
}
impl RegressionThreshold {
pub fn is_regression(&self, percent_change: f64) -> bool {
percent_change.abs() > self.final_percent
}
}
#[derive(Debug, Clone)]
pub struct Trend {
pub slope_per_day: f64,
pub r_squared: f64,
pub direction: &'static str,
}
impl Trend {
pub fn is_significant(&self) -> bool {
self.r_squared > 0.5 && self.slope_per_day.abs() > 0.1
}
}
#[derive(Debug, Clone)]
pub struct RegressionCheck {
pub metric: String,
pub current_value: f64,
pub baseline_mean: f64,
pub percent_change: f64,
pub threshold: RegressionThreshold,
pub is_regression: bool,
pub trend: Option<Trend>,
}
impl RegressionCheck {
pub fn passed(&self) -> bool {
!self.is_regression
}
}