cbtop/context_regression/
types.rs1pub const DEFAULT_COLD_START_MARGIN: f64 = 15.0;
5
6pub const MIN_SAMPLES_FOR_CONTEXT: usize = 5;
8
9pub const DEFAULT_STALENESS_SEC: u64 = 3600;
11
12#[derive(Debug, Clone)]
14pub struct SystemContext {
15 pub timestamp: u64,
17 pub cpu_temp_c: f64,
19 pub gpu_temp_c: f64,
21 pub memory_percent: f64,
23 pub cpu_freq_mhz: f64,
25 pub cpu_freq_max_mhz: f64,
27 pub cache_warm: bool,
29 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 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn with_timestamp(mut self, ts: u64) -> Self {
56 self.timestamp = ts;
57 self
58 }
59
60 pub fn with_cpu_temp(mut self, temp_c: f64) -> Self {
62 self.cpu_temp_c = temp_c;
63 self
64 }
65
66 pub fn with_gpu_temp(mut self, temp_c: f64) -> Self {
68 self.gpu_temp_c = temp_c;
69 self
70 }
71
72 pub fn with_memory(mut self, percent: f64) -> Self {
74 self.memory_percent = percent.clamp(0.0, 100.0);
75 self
76 }
77
78 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 pub fn with_cache_warm(mut self, warm: bool) -> Self {
87 self.cache_warm = warm;
88 self
89 }
90
91 pub fn with_load(mut self, load: f64) -> Self {
93 self.load_average = load;
94 self
95 }
96
97 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 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 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 pub fn thermal_headroom(&self, throttle_temp: f64) -> f64 {
129 (throttle_temp - self.cpu_temp_c).max(0.0)
130 }
131
132 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 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#[derive(Debug, Clone)]
157pub struct BaselineEntry {
158 pub context: SystemContext,
160 pub value: f64,
162 pub metric: String,
164}
165
166impl BaselineEntry {
167 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#[derive(Debug, Clone)]
179pub struct RegressionThreshold {
180 pub base_percent: f64,
182 pub temp_adjustment: f64,
184 pub memory_adjustment: f64,
186 pub freq_adjustment: f64,
188 pub cache_adjustment: f64,
190 pub final_percent: f64,
192 pub confidence: f64,
194 pub sample_count: usize,
196}
197
198impl RegressionThreshold {
199 pub fn is_regression(&self, percent_change: f64) -> bool {
201 percent_change.abs() > self.final_percent
202 }
203}
204
205#[derive(Debug, Clone)]
207pub struct Trend {
208 pub slope_per_day: f64,
210 pub r_squared: f64,
212 pub direction: &'static str,
214}
215
216impl Trend {
217 pub fn is_significant(&self) -> bool {
219 self.r_squared > 0.5 && self.slope_per_day.abs() > 0.1
220 }
221}
222
223#[derive(Debug, Clone)]
225pub struct RegressionCheck {
226 pub metric: String,
228 pub current_value: f64,
230 pub baseline_mean: f64,
232 pub percent_change: f64,
234 pub threshold: RegressionThreshold,
236 pub is_regression: bool,
238 pub trend: Option<Trend>,
240}
241
242impl RegressionCheck {
243 pub fn passed(&self) -> bool {
245 !self.is_regression
246 }
247}