llm_optimizer_processor/analyzer/
stats.rs1use std::collections::VecDeque;
7
8#[derive(Debug, Clone)]
10pub struct CircularBuffer<T> {
11 buffer: VecDeque<T>,
12 capacity: usize,
13}
14
15impl<T> CircularBuffer<T> {
16 pub fn new(capacity: usize) -> Self {
18 Self {
19 buffer: VecDeque::with_capacity(capacity),
20 capacity,
21 }
22 }
23
24 pub fn push(&mut self, value: T) {
26 if self.buffer.len() >= self.capacity {
27 self.buffer.pop_front();
28 }
29 self.buffer.push_back(value);
30 }
31
32 pub fn len(&self) -> usize {
34 self.buffer.len()
35 }
36
37 pub fn is_empty(&self) -> bool {
39 self.buffer.is_empty()
40 }
41
42 pub fn as_slice(&self) -> &VecDeque<T> {
44 &self.buffer
45 }
46
47 pub fn clear(&mut self) {
49 self.buffer.clear();
50 }
51
52 pub fn capacity(&self) -> usize {
54 self.capacity
55 }
56
57 pub fn is_full(&self) -> bool {
59 self.buffer.len() >= self.capacity
60 }
61}
62
63impl<T: Clone> CircularBuffer<T> {
64 pub fn to_vec(&self) -> Vec<T> {
66 self.buffer.iter().cloned().collect()
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct ExponentialMovingAverage {
73 alpha: f64,
74 current: Option<f64>,
75}
76
77impl ExponentialMovingAverage {
78 pub fn new(alpha: f64) -> Self {
83 Self {
84 alpha: alpha.clamp(0.0, 1.0),
85 current: None,
86 }
87 }
88
89 pub fn update(&mut self, value: f64) {
91 self.current = Some(match self.current {
92 None => value,
93 Some(prev) => self.alpha * value + (1.0 - self.alpha) * prev,
94 });
95 }
96
97 pub fn value(&self) -> Option<f64> {
99 self.current
100 }
101
102 pub fn reset(&mut self) {
104 self.current = None;
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct SimpleMovingAverage {
111 window: CircularBuffer<f64>,
112 sum: f64,
113}
114
115impl SimpleMovingAverage {
116 pub fn new(window_size: usize) -> Self {
118 Self {
119 window: CircularBuffer::new(window_size),
120 sum: 0.0,
121 }
122 }
123
124 pub fn update(&mut self, value: f64) {
126 if self.window.is_full() {
127 if let Some(&oldest) = self.window.as_slice().front() {
129 self.sum -= oldest;
130 }
131 }
132
133 self.window.push(value);
134 self.sum += value;
135 }
136
137 pub fn value(&self) -> Option<f64> {
139 if self.window.is_empty() {
140 None
141 } else {
142 Some(self.sum / self.window.len() as f64)
143 }
144 }
145
146 pub fn count(&self) -> usize {
148 self.window.len()
149 }
150
151 pub fn reset(&mut self) {
153 self.window.clear();
154 self.sum = 0.0;
155 }
156}
157
158pub fn calculate_percentiles(mut values: Vec<f64>, percentiles: &[f64]) -> Vec<(f64, f64)> {
160 if values.is_empty() {
161 return percentiles.iter().map(|&p| (p, 0.0)).collect();
162 }
163
164 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
165
166 percentiles
167 .iter()
168 .map(|&p| {
169 let idx = ((p / 100.0) * (values.len() - 1) as f64).round() as usize;
170 let value = values[idx.min(values.len() - 1)];
171 (p, value)
172 })
173 .collect()
174}
175
176pub fn mean(values: &[f64]) -> f64 {
178 if values.is_empty() {
179 return 0.0;
180 }
181 values.iter().sum::<f64>() / values.len() as f64
182}
183
184pub fn std_dev(values: &[f64]) -> f64 {
186 if values.len() < 2 {
187 return 0.0;
188 }
189
190 let m = mean(values);
191 let variance = values.iter().map(|x| (x - m).powi(2)).sum::<f64>() / (values.len() - 1) as f64;
192 variance.sqrt()
193}
194
195pub fn z_score(value: f64, mean: f64, std_dev: f64) -> f64 {
197 if std_dev == 0.0 {
198 return 0.0;
199 }
200 (value - mean) / std_dev
201}
202
203pub fn is_outlier_zscore(value: f64, mean: f64, std_dev: f64, threshold: f64) -> bool {
208 z_score(value, mean, std_dev).abs() > threshold
209}
210
211pub fn calculate_iqr(mut values: Vec<f64>) -> (f64, f64, f64) {
213 if values.is_empty() {
214 return (0.0, 0.0, 0.0);
215 }
216
217 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
218
219 let q1_idx = (values.len() / 4).max(0);
220 let q3_idx = (3 * values.len() / 4).min(values.len() - 1);
221
222 let q1 = values[q1_idx];
223 let q3 = values[q3_idx];
224 let iqr = q3 - q1;
225
226 (q1, q3, iqr)
227}
228
229pub fn is_outlier_iqr(value: f64, values: &[f64], multiplier: f64) -> bool {
234 if values.len() < 4 {
235 return false;
236 }
237
238 let (q1, q3, iqr) = calculate_iqr(values.to_vec());
239 let lower_bound = q1 - multiplier * iqr;
240 let upper_bound = q3 + multiplier * iqr;
241
242 value < lower_bound || value > upper_bound
243}
244
245pub fn calculate_rate(count: u64, duration_secs: f64) -> f64 {
247 if duration_secs == 0.0 {
248 return 0.0;
249 }
250 count as f64 / duration_secs
251}
252
253pub fn percentage_change(old: f64, new: f64) -> f64 {
255 if old == 0.0 {
256 if new == 0.0 {
257 return 0.0;
258 }
259 return 100.0; }
261 ((new - old) / old) * 100.0
262}
263
264#[derive(Debug, Clone)]
266pub struct LinearTrend {
267 pub slope: f64,
269 pub intercept: f64,
271 pub r_squared: f64,
273}
274
275pub fn calculate_linear_trend(values: &[(f64, f64)]) -> Option<LinearTrend> {
277 if values.len() < 2 {
278 return None;
279 }
280
281 let n = values.len() as f64;
282 let sum_x: f64 = values.iter().map(|(x, _)| x).sum();
283 let sum_y: f64 = values.iter().map(|(_, y)| y).sum();
284 let sum_xy: f64 = values.iter().map(|(x, y)| x * y).sum();
285 let sum_x2: f64 = values.iter().map(|(x, _)| x * x).sum();
286 let sum_y2: f64 = values.iter().map(|(_, y)| y * y).sum();
287
288 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
289 let intercept = (sum_y - slope * sum_x) / n;
290
291 let mean_y = sum_y / n;
293 let ss_tot: f64 = values.iter().map(|(_, y)| (y - mean_y).powi(2)).sum();
294 let ss_res: f64 = values
295 .iter()
296 .map(|(x, y)| {
297 let predicted = slope * x + intercept;
298 (y - predicted).powi(2)
299 })
300 .sum();
301
302 let r_squared = if ss_tot == 0.0 {
303 1.0
304 } else {
305 1.0 - (ss_res / ss_tot)
306 };
307
308 Some(LinearTrend {
309 slope,
310 intercept,
311 r_squared,
312 })
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_circular_buffer() {
321 let mut buffer = CircularBuffer::new(3);
322 assert!(buffer.is_empty());
323
324 buffer.push(1);
325 buffer.push(2);
326 buffer.push(3);
327 assert!(buffer.is_full());
328 assert_eq!(buffer.len(), 3);
329
330 buffer.push(4);
331 assert_eq!(buffer.len(), 3);
332 assert_eq!(buffer.to_vec(), vec![2, 3, 4]);
333 }
334
335 #[test]
336 fn test_exponential_moving_average() {
337 let mut ema = ExponentialMovingAverage::new(0.5);
338 assert_eq!(ema.value(), None);
339
340 ema.update(10.0);
341 assert_eq!(ema.value(), Some(10.0));
342
343 ema.update(20.0);
344 assert_eq!(ema.value(), Some(15.0)); ema.reset();
347 assert_eq!(ema.value(), None);
348 }
349
350 #[test]
351 fn test_simple_moving_average() {
352 let mut sma = SimpleMovingAverage::new(3);
353 assert_eq!(sma.value(), None);
354
355 sma.update(10.0);
356 assert_eq!(sma.value(), Some(10.0));
357
358 sma.update(20.0);
359 assert_eq!(sma.value(), Some(15.0));
360
361 sma.update(30.0);
362 assert_eq!(sma.value(), Some(20.0));
363
364 sma.update(40.0); assert_eq!(sma.value(), Some(30.0)); }
367
368 #[test]
369 fn test_calculate_percentiles() {
370 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
371 let percentiles = calculate_percentiles(values, &[50.0, 95.0, 99.0]);
372
373 assert_eq!(percentiles.len(), 3);
374 assert!((percentiles[0].1 - 5.5).abs() < 0.1); assert!((percentiles[1].1 - 10.0).abs() < 0.1); }
377
378 #[test]
379 fn test_mean_and_std_dev() {
380 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
381 assert_eq!(mean(&values), 3.0);
382
383 let std = std_dev(&values);
384 assert!((std - 1.58).abs() < 0.01); }
386
387 #[test]
388 fn test_z_score() {
389 let score = z_score(100.0, 50.0, 10.0);
390 assert_eq!(score, 5.0);
391
392 let score = z_score(40.0, 50.0, 10.0);
393 assert_eq!(score, -1.0);
394 }
395
396 #[test]
397 fn test_is_outlier_zscore() {
398 assert!(is_outlier_zscore(100.0, 50.0, 10.0, 3.0)); assert!(!is_outlier_zscore(65.0, 50.0, 10.0, 3.0)); }
401
402 #[test]
403 fn test_calculate_iqr() {
404 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
405 let (q1, q3, iqr) = calculate_iqr(values);
406
407 assert_eq!(q1, 2.0);
408 assert_eq!(q3, 6.0);
409 assert_eq!(iqr, 4.0);
410 }
411
412 #[test]
413 fn test_is_outlier_iqr() {
414 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
415
416 assert!(!is_outlier_iqr(10.0, &values, 1.5));
418 assert!(is_outlier_iqr(20.0, &values, 1.5));
419 }
420
421 #[test]
422 fn test_calculate_rate() {
423 assert_eq!(calculate_rate(100, 10.0), 10.0);
424 assert_eq!(calculate_rate(0, 10.0), 0.0);
425 assert_eq!(calculate_rate(100, 0.0), 0.0);
426 }
427
428 #[test]
429 fn test_percentage_change() {
430 assert_eq!(percentage_change(100.0, 150.0), 50.0);
431 assert_eq!(percentage_change(100.0, 50.0), -50.0);
432 assert_eq!(percentage_change(0.0, 100.0), 100.0);
433 }
434
435 #[test]
436 fn test_calculate_linear_trend() {
437 let data = vec![(1.0, 2.0), (2.0, 4.0), (3.0, 6.0), (4.0, 8.0)];
438 let trend = calculate_linear_trend(&data).unwrap();
439
440 assert!((trend.slope - 2.0).abs() < 0.01);
441 assert!((trend.intercept - 0.0).abs() < 0.01);
442 assert!((trend.r_squared - 1.0).abs() < 0.01); }
444}