llm_optimizer_processor/aggregation/
statistics.rs1use crate::error::{AggregationError, AggregationResult};
7use serde::{Deserialize, Serialize};
8use statrs::distribution::{ContinuousCDF, Normal};
9use statrs::statistics::{Data, OrderStatistics, Statistics};
10use std::collections::VecDeque;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct OnlineVariance {
18 count: u64,
19 mean: f64,
20 m2: f64,
21}
22
23impl OnlineVariance {
24 pub fn new() -> Self {
26 Self {
27 count: 0,
28 mean: 0.0,
29 m2: 0.0,
30 }
31 }
32
33 pub fn update(&mut self, value: f64) {
35 self.count += 1;
36 let delta = value - self.mean;
37 self.mean += delta / self.count as f64;
38 let delta2 = value - self.mean;
39 self.m2 += delta * delta2;
40 }
41
42 pub fn mean(&self) -> f64 {
44 self.mean
45 }
46
47 pub fn variance(&self) -> Option<f64> {
49 if self.count < 2 {
50 None
51 } else {
52 Some(self.m2 / (self.count - 1) as f64)
53 }
54 }
55
56 pub fn std_dev(&self) -> Option<f64> {
58 self.variance().map(|v| v.sqrt())
59 }
60
61 pub fn population_variance(&self) -> Option<f64> {
63 if self.count == 0 {
64 None
65 } else {
66 Some(self.m2 / self.count as f64)
67 }
68 }
69
70 pub fn count(&self) -> u64 {
72 self.count
73 }
74
75 pub fn merge(&mut self, other: &OnlineVariance) {
77 if other.count == 0 {
78 return;
79 }
80
81 let combined_count = self.count + other.count;
82 let delta = other.mean - self.mean;
83 let combined_mean = (self.count as f64 * self.mean + other.count as f64 * other.mean)
84 / combined_count as f64;
85
86 self.m2 = self.m2
87 + other.m2
88 + delta * delta * (self.count as f64 * other.count as f64) / combined_count as f64;
89
90 self.count = combined_count;
91 self.mean = combined_mean;
92 }
93
94 pub fn reset(&mut self) {
96 self.count = 0;
97 self.mean = 0.0;
98 self.m2 = 0.0;
99 }
100}
101
102impl Default for OnlineVariance {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ExponentialMovingAverage {
111 alpha: f64,
112 current: Option<f64>,
113}
114
115impl ExponentialMovingAverage {
116 pub fn new(alpha: f64) -> AggregationResult<Self> {
118 if alpha <= 0.0 || alpha > 1.0 {
119 return Err(AggregationError::InvalidValue {
120 value: alpha,
121 reason: "Alpha must be between 0 and 1".to_string(),
122 });
123 }
124
125 Ok(Self {
126 alpha,
127 current: None,
128 })
129 }
130
131 pub fn with_span(span: f64) -> AggregationResult<Self> {
133 let alpha = 2.0 / (span + 1.0);
134 Self::new(alpha)
135 }
136
137 pub fn update(&mut self, value: f64) {
139 self.current = Some(match self.current {
140 Some(current) => self.alpha * value + (1.0 - self.alpha) * current,
141 None => value,
142 });
143 }
144
145 pub fn value(&self) -> Option<f64> {
147 self.current
148 }
149
150 pub fn reset(&mut self) {
152 self.current = None;
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct SlidingWindowStats {
162 window_size: usize,
163 values: VecDeque<f64>,
164}
165
166impl SlidingWindowStats {
167 pub fn new(window_size: usize) -> AggregationResult<Self> {
169 if window_size == 0 {
170 return Err(AggregationError::InvalidValue {
171 value: window_size as f64,
172 reason: "Window size must be greater than 0".to_string(),
173 });
174 }
175
176 Ok(Self {
177 window_size,
178 values: VecDeque::with_capacity(window_size),
179 })
180 }
181
182 pub fn update(&mut self, value: f64) {
184 if self.values.len() >= self.window_size {
185 self.values.pop_front();
186 }
187 self.values.push_back(value);
188 }
189
190 pub fn len(&self) -> usize {
192 self.values.len()
193 }
194
195 pub fn is_empty(&self) -> bool {
197 self.values.is_empty()
198 }
199
200 pub fn mean(&self) -> Option<f64> {
202 if self.values.is_empty() {
203 None
204 } else {
205 Some(self.values.iter().sum::<f64>() / self.values.len() as f64)
206 }
207 }
208
209 pub fn min(&self) -> Option<f64> {
211 self.values.iter().copied().min_by(|a, b| {
212 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
213 })
214 }
215
216 pub fn max(&self) -> Option<f64> {
218 self.values.iter().copied().max_by(|a, b| {
219 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
220 })
221 }
222
223 pub fn median(&self) -> Option<f64> {
225 if self.values.is_empty() {
226 return None;
227 }
228
229 let mut sorted: Vec<f64> = self.values.iter().copied().collect();
230 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
231
232 let len = sorted.len();
233 if len % 2 == 0 {
234 Some((sorted[len / 2 - 1] + sorted[len / 2]) / 2.0)
235 } else {
236 Some(sorted[len / 2])
237 }
238 }
239
240 pub fn percentile(&self, p: f64) -> AggregationResult<f64> {
242 if self.values.is_empty() {
243 return Err(AggregationError::InsufficientData {
244 aggregation_type: "percentile".to_string(),
245 required: 1,
246 actual: 0,
247 });
248 }
249
250 if p < 0.0 || p > 100.0 {
251 return Err(AggregationError::InvalidValue {
252 value: p,
253 reason: "Percentile must be between 0 and 100".to_string(),
254 });
255 }
256
257 let mut sorted: Vec<f64> = self.values.iter().copied().collect();
258 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
259
260 let index = (p / 100.0 * (sorted.len() - 1) as f64).round() as usize;
261 Ok(sorted[index])
262 }
263
264 pub fn std_dev(&self) -> Option<f64> {
266 if self.values.len() < 2 {
267 return None;
268 }
269
270 let mean = self.mean()?;
271 let variance = self
272 .values
273 .iter()
274 .map(|v| (v - mean).powi(2))
275 .sum::<f64>()
276 / (self.values.len() - 1) as f64;
277
278 Some(variance.sqrt())
279 }
280
281 pub fn reset(&mut self) {
283 self.values.clear();
284 }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct RateCalculator {
290 event_count: u64,
291 start_time: Option<chrono::DateTime<chrono::Utc>>,
292 last_time: Option<chrono::DateTime<chrono::Utc>>,
293}
294
295impl RateCalculator {
296 pub fn new() -> Self {
298 Self {
299 event_count: 0,
300 start_time: None,
301 last_time: None,
302 }
303 }
304
305 pub fn record_event(&mut self, timestamp: chrono::DateTime<chrono::Utc>) {
307 self.event_count += 1;
308
309 if self.start_time.is_none() {
310 self.start_time = Some(timestamp);
311 }
312
313 self.last_time = Some(timestamp);
314 }
315
316 pub fn overall_rate(&self) -> Option<f64> {
318 if self.event_count == 0 {
319 return Some(0.0);
320 }
321
322 let start = self.start_time?;
323 let end = self.last_time?;
324
325 let duration = end.signed_duration_since(start);
326 let seconds = duration.num_milliseconds() as f64 / 1000.0;
327
328 if seconds > 0.0 {
329 Some(self.event_count as f64 / seconds)
330 } else {
331 None
332 }
333 }
334
335 pub fn count(&self) -> u64 {
337 self.event_count
338 }
339
340 pub fn reset(&mut self) {
342 self.event_count = 0;
343 self.start_time = None;
344 self.last_time = None;
345 }
346}
347
348impl Default for RateCalculator {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct ZScoreAnomalyDetector {
357 variance: OnlineVariance,
358 threshold: f64,
359}
360
361impl ZScoreAnomalyDetector {
362 pub fn new(threshold: f64) -> Self {
364 Self {
365 variance: OnlineVariance::new(),
366 threshold,
367 }
368 }
369
370 pub fn check(&mut self, value: f64) -> bool {
372 let is_anomaly = if let Some(std_dev) = self.variance.std_dev() {
373 if std_dev > 0.0 {
374 let z_score = (value - self.variance.mean()).abs() / std_dev;
375 z_score > self.threshold
376 } else {
377 false
378 }
379 } else {
380 false
381 };
382
383 self.variance.update(value);
384 is_anomaly
385 }
386
387 pub fn mean(&self) -> f64 {
389 self.variance.mean()
390 }
391
392 pub fn std_dev(&self) -> Option<f64> {
394 self.variance.std_dev()
395 }
396
397 pub fn reset(&mut self) {
399 self.variance.reset();
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_online_variance() {
409 let mut ov = OnlineVariance::new();
410
411 let values = vec![10.0, 20.0, 30.0, 40.0, 50.0];
412 for &v in &values {
413 ov.update(v);
414 }
415
416 assert_eq!(ov.count(), 5);
417 assert_eq!(ov.mean(), 30.0);
418
419 let std_dev = ov.std_dev().unwrap();
420 assert!((std_dev - 15.811).abs() < 0.01);
421 }
422
423 #[test]
424 fn test_online_variance_merge() {
425 let mut ov1 = OnlineVariance::new();
426 ov1.update(10.0);
427 ov1.update(20.0);
428
429 let mut ov2 = OnlineVariance::new();
430 ov2.update(30.0);
431 ov2.update(40.0);
432
433 ov1.merge(&ov2);
434
435 assert_eq!(ov1.count(), 4);
436 assert_eq!(ov1.mean(), 25.0);
437 }
438
439 #[test]
440 fn test_ema() {
441 let mut ema = ExponentialMovingAverage::new(0.5).unwrap();
442
443 ema.update(10.0);
444 assert_eq!(ema.value(), Some(10.0));
445
446 ema.update(20.0);
447 assert_eq!(ema.value(), Some(15.0));
448
449 ema.update(30.0);
450 assert_eq!(ema.value(), Some(22.5));
451 }
452
453 #[test]
454 fn test_ema_with_span() {
455 let mut ema = ExponentialMovingAverage::with_span(9.0).unwrap();
456
457 for i in 1..=10 {
458 ema.update(i as f64);
459 }
460
461 assert!(ema.value().is_some());
462 let value = ema.value().unwrap();
463 assert!(value > 5.0 && value < 10.0);
464 }
465
466 #[test]
467 fn test_sliding_window_stats() {
468 let mut sw = SlidingWindowStats::new(3).unwrap();
469
470 sw.update(10.0);
471 sw.update(20.0);
472 sw.update(30.0);
473
474 assert_eq!(sw.len(), 3);
475 assert_eq!(sw.mean(), Some(20.0));
476 assert_eq!(sw.min(), Some(10.0));
477 assert_eq!(sw.max(), Some(30.0));
478
479 sw.update(40.0);
481 assert_eq!(sw.len(), 3);
482 assert_eq!(sw.mean(), Some(30.0));
483 assert_eq!(sw.min(), Some(20.0));
484 }
485
486 #[test]
487 fn test_sliding_window_median() {
488 let mut sw = SlidingWindowStats::new(5).unwrap();
489
490 sw.update(10.0);
491 sw.update(20.0);
492 sw.update(30.0);
493 sw.update(40.0);
494 sw.update(50.0);
495
496 assert_eq!(sw.median(), Some(30.0));
497 }
498
499 #[test]
500 fn test_sliding_window_percentile() {
501 let mut sw = SlidingWindowStats::new(5).unwrap();
502
503 for i in 1..=5 {
504 sw.update(i as f64 * 10.0);
505 }
506
507 let p50 = sw.percentile(50.0).unwrap();
508 assert_eq!(p50, 30.0);
509
510 let p95 = sw.percentile(95.0).unwrap();
511 assert_eq!(p95, 50.0);
512 }
513
514 #[test]
515 fn test_rate_calculator() {
516 let mut rate = RateCalculator::new();
517
518 let base_time = chrono::Utc::now();
519
520 rate.record_event(base_time);
521 rate.record_event(base_time + chrono::Duration::milliseconds(500));
522 rate.record_event(base_time + chrono::Duration::seconds(1));
523
524 assert_eq!(rate.count(), 3);
525
526 let overall_rate = rate.overall_rate().unwrap();
527 assert!(overall_rate > 0.0);
528 assert!(overall_rate <= 3.0); }
530
531 #[test]
532 fn test_zscore_anomaly_detector() {
533 let mut detector = ZScoreAnomalyDetector::new(2.0);
534
535 for i in 1..=10 {
537 let is_anomaly = detector.check(i as f64);
538 }
540
541 let is_anomaly = detector.check(100.0);
543 assert!(is_anomaly);
544 }
545
546 #[test]
547 fn test_online_variance_reset() {
548 let mut ov = OnlineVariance::new();
549 ov.update(10.0);
550 ov.update(20.0);
551
552 assert_eq!(ov.count(), 2);
553
554 ov.reset();
555 assert_eq!(ov.count(), 0);
556 assert_eq!(ov.mean(), 0.0);
557 }
558
559 #[test]
560 fn test_sliding_window_empty() {
561 let sw = SlidingWindowStats::new(5).unwrap();
562
563 assert!(sw.is_empty());
564 assert_eq!(sw.len(), 0);
565 assert_eq!(sw.mean(), None);
566 assert_eq!(sw.median(), None);
567 }
568}