Skip to main content

cbtop/context_regression/
types.rs

1//! Context regression types: system context, baselines, thresholds, and trends.
2
3/// Default cold start margin (15%)
4pub const DEFAULT_COLD_START_MARGIN: f64 = 15.0;
5
6/// Default minimum samples for learning
7pub const MIN_SAMPLES_FOR_CONTEXT: usize = 5;
8
9/// Default context staleness (seconds)
10pub const DEFAULT_STALENESS_SEC: u64 = 3600;
11
12/// System context snapshot
13#[derive(Debug, Clone)]
14pub struct SystemContext {
15    /// Timestamp (Unix seconds)
16    pub timestamp: u64,
17    /// CPU temperature (Celsius)
18    pub cpu_temp_c: f64,
19    /// GPU temperature (Celsius)
20    pub gpu_temp_c: f64,
21    /// Memory utilization (0-100)
22    pub memory_percent: f64,
23    /// CPU frequency (MHz)
24    pub cpu_freq_mhz: f64,
25    /// Maximum CPU frequency (MHz)
26    pub cpu_freq_max_mhz: f64,
27    /// Is cache warm
28    pub cache_warm: bool,
29    /// Load average (1 min)
30    pub load_average: f64,
31}
32
33impl Default for SystemContext {
34    fn default() -> Self {
35        Self {
36            timestamp: 0,
37            cpu_temp_c: 50.0,
38            gpu_temp_c: 50.0,
39            memory_percent: 50.0,
40            cpu_freq_mhz: 3000.0,
41            cpu_freq_max_mhz: 4000.0,
42            cache_warm: false,
43            load_average: 1.0,
44        }
45    }
46}
47
48impl SystemContext {
49    /// Create new context
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Set timestamp
55    pub fn with_timestamp(mut self, ts: u64) -> Self {
56        self.timestamp = ts;
57        self
58    }
59
60    /// Set CPU temperature
61    pub fn with_cpu_temp(mut self, temp_c: f64) -> Self {
62        self.cpu_temp_c = temp_c;
63        self
64    }
65
66    /// Set GPU temperature
67    pub fn with_gpu_temp(mut self, temp_c: f64) -> Self {
68        self.gpu_temp_c = temp_c;
69        self
70    }
71
72    /// Set memory utilization
73    pub fn with_memory(mut self, percent: f64) -> Self {
74        self.memory_percent = percent.clamp(0.0, 100.0);
75        self
76    }
77
78    /// Set CPU frequency
79    pub fn with_cpu_freq(mut self, freq_mhz: f64, max_mhz: f64) -> Self {
80        self.cpu_freq_mhz = freq_mhz;
81        self.cpu_freq_max_mhz = max_mhz;
82        self
83    }
84
85    /// Set cache state
86    pub fn with_cache_warm(mut self, warm: bool) -> Self {
87        self.cache_warm = warm;
88        self
89    }
90
91    /// Set load average
92    pub fn with_load(mut self, load: f64) -> Self {
93        self.load_average = load;
94        self
95    }
96
97    /// Capture current system context
98    pub fn capture() -> Self {
99        let timestamp = std::time::SystemTime::now()
100            .duration_since(std::time::UNIX_EPOCH)
101            .map(|d| d.as_secs())
102            .unwrap_or(0);
103
104        // In a real implementation, these would read from sysfs/procfs
105        // For now, return reasonable defaults
106        Self {
107            timestamp,
108            cpu_temp_c: 60.0,
109            gpu_temp_c: 55.0,
110            memory_percent: 50.0,
111            cpu_freq_mhz: 3000.0,
112            cpu_freq_max_mhz: 4000.0,
113            cache_warm: false,
114            load_average: 1.0,
115        }
116    }
117
118    /// Get frequency utilization (0-1)
119    pub fn freq_utilization(&self) -> f64 {
120        if self.cpu_freq_max_mhz > 0.0 {
121            self.cpu_freq_mhz / self.cpu_freq_max_mhz
122        } else {
123            1.0
124        }
125    }
126
127    /// Get thermal headroom (degrees below throttle threshold)
128    pub fn thermal_headroom(&self, throttle_temp: f64) -> f64 {
129        (throttle_temp - self.cpu_temp_c).max(0.0)
130    }
131
132    /// Check if context is stale
133    pub fn is_stale(&self, max_age_sec: u64) -> bool {
134        let now = std::time::SystemTime::now()
135            .duration_since(std::time::UNIX_EPOCH)
136            .map(|d| d.as_secs())
137            .unwrap_or(0);
138
139        now.saturating_sub(self.timestamp) > max_age_sec
140    }
141
142    /// Export to JSON
143    pub fn to_json(&self) -> String {
144        format!(
145            r#"{{"timestamp":{},"cpu_temp_c":{},"memory_percent":{},"cpu_freq_mhz":{},"cache_warm":{}}}"#,
146            self.timestamp,
147            self.cpu_temp_c,
148            self.memory_percent,
149            self.cpu_freq_mhz,
150            self.cache_warm
151        )
152    }
153}
154
155/// Historical baseline entry
156#[derive(Debug, Clone)]
157pub struct BaselineEntry {
158    /// Context at measurement time
159    pub context: SystemContext,
160    /// Measured value
161    pub value: f64,
162    /// Metric name
163    pub metric: String,
164}
165
166impl BaselineEntry {
167    /// Create new entry
168    pub fn new(metric: &str, value: f64, context: SystemContext) -> Self {
169        Self {
170            metric: metric.to_string(),
171            value,
172            context,
173        }
174    }
175}
176
177/// Computed regression threshold
178#[derive(Debug, Clone)]
179pub struct RegressionThreshold {
180    /// Base threshold (from historical mean)
181    pub base_percent: f64,
182    /// Temperature adjustment
183    pub temp_adjustment: f64,
184    /// Memory adjustment
185    pub memory_adjustment: f64,
186    /// Frequency adjustment
187    pub freq_adjustment: f64,
188    /// Cache adjustment
189    pub cache_adjustment: f64,
190    /// Final threshold
191    pub final_percent: f64,
192    /// Confidence in threshold
193    pub confidence: f64,
194    /// Number of samples used
195    pub sample_count: usize,
196}
197
198impl RegressionThreshold {
199    /// Check if regression detected
200    pub fn is_regression(&self, percent_change: f64) -> bool {
201        percent_change.abs() > self.final_percent
202    }
203}
204
205/// Detected trend
206#[derive(Debug, Clone)]
207pub struct Trend {
208    /// Slope (change per day)
209    pub slope_per_day: f64,
210    /// R² of linear fit
211    pub r_squared: f64,
212    /// Direction description
213    pub direction: &'static str,
214}
215
216impl Trend {
217    /// Check if trend is significant
218    pub fn is_significant(&self) -> bool {
219        self.r_squared > 0.5 && self.slope_per_day.abs() > 0.1
220    }
221}
222
223/// Result of regression check
224#[derive(Debug, Clone)]
225pub struct RegressionCheck {
226    /// Metric name
227    pub metric: String,
228    /// Current value
229    pub current_value: f64,
230    /// Baseline mean
231    pub baseline_mean: f64,
232    /// Percent change from baseline
233    pub percent_change: f64,
234    /// Computed threshold
235    pub threshold: RegressionThreshold,
236    /// Is regression detected
237    pub is_regression: bool,
238    /// Detected trend
239    pub trend: Option<Trend>,
240}
241
242impl RegressionCheck {
243    /// Check if passed (no regression)
244    pub fn passed(&self) -> bool {
245        !self.is_regression
246    }
247}