1#[derive(Debug, Clone)]
10pub struct EmaTracker {
11 value: f64,
13 alpha: f64,
15 initialized: bool,
17}
18
19impl Default for EmaTracker {
20 fn default() -> Self {
21 Self::new(0.1) }
23}
24
25impl EmaTracker {
26 #[must_use]
32 pub fn new(alpha: f64) -> Self {
33 Self {
34 value: 0.0,
35 alpha: alpha.clamp(0.0, 1.0),
36 initialized: false,
37 }
38 }
39
40 #[must_use]
42 pub fn for_fps() -> Self {
43 Self::new(0.3)
44 }
45
46 #[must_use]
48 pub fn for_load() -> Self {
49 Self::new(0.05)
50 }
51
52 pub fn update(&mut self, sample: f64) {
54 if self.initialized {
55 self.value = self.alpha * sample + (1.0 - self.alpha) * self.value;
56 } else {
57 self.value = sample;
58 self.initialized = true;
59 }
60 }
61
62 #[must_use]
64 pub fn value(&self) -> f64 {
65 self.value
66 }
67
68 #[must_use]
70 pub fn is_initialized(&self) -> bool {
71 self.initialized
72 }
73
74 #[must_use]
76 pub fn alpha(&self) -> f64 {
77 self.alpha
78 }
79
80 pub fn reset(&mut self) {
82 self.value = 0.0;
83 self.initialized = false;
84 }
85
86 pub fn set_alpha(&mut self, alpha: f64) {
88 self.alpha = alpha.clamp(0.0, 1.0);
89 }
90}
91
92#[derive(Debug, Clone)]
100pub struct RateLimiter {
101 last_allowed_us: u64,
103 interval_us: u64,
105}
106
107impl Default for RateLimiter {
108 fn default() -> Self {
109 Self::new_hz(60) }
111}
112
113impl RateLimiter {
114 #[must_use]
116 pub fn new(interval_us: u64) -> Self {
117 Self {
118 last_allowed_us: 0,
119 interval_us,
120 }
121 }
122
123 #[must_use]
125 pub fn new_hz(hz: u32) -> Self {
126 let interval_us = if hz == 0 {
127 1_000_000
128 } else {
129 1_000_000 / hz as u64
130 };
131 Self::new(interval_us)
132 }
133
134 #[must_use]
136 pub fn new_ms(ms: u64) -> Self {
137 Self::new(ms * 1000)
138 }
139
140 pub fn check(&mut self) -> bool {
144 let now = std::time::SystemTime::now()
145 .duration_since(std::time::UNIX_EPOCH)
146 .unwrap_or_default()
147 .as_micros() as u64;
148
149 if now >= self.last_allowed_us + self.interval_us {
150 self.last_allowed_us = now;
151 true
152 } else {
153 false
154 }
155 }
156
157 #[must_use]
159 pub fn would_allow(&self) -> bool {
160 let now = std::time::SystemTime::now()
161 .duration_since(std::time::UNIX_EPOCH)
162 .unwrap_or_default()
163 .as_micros() as u64;
164
165 now >= self.last_allowed_us + self.interval_us
166 }
167
168 #[must_use]
170 pub fn interval_us(&self) -> u64 {
171 self.interval_us
172 }
173
174 #[must_use]
176 pub fn hz(&self) -> f64 {
177 if self.interval_us == 0 {
178 0.0
179 } else {
180 1_000_000.0 / self.interval_us as f64
181 }
182 }
183
184 pub fn reset(&mut self) {
186 self.last_allowed_us = 0;
187 }
188}
189
190#[derive(Debug, Clone)]
199pub struct ThresholdDetector {
200 low: f64,
202 high: f64,
204 is_high: bool,
206}
207
208impl ThresholdDetector {
209 #[must_use]
217 pub fn new(low: f64, high: f64) -> Self {
218 Self {
219 low,
220 high: high.max(low), is_high: false,
222 }
223 }
224
225 #[must_use]
227 pub fn percent(low: f64, high: f64) -> Self {
228 Self::new(low.clamp(0.0, 100.0), high.clamp(0.0, 100.0))
229 }
230
231 #[must_use]
233 pub fn for_resource() -> Self {
234 Self::new(70.0, 90.0)
235 }
236
237 #[must_use]
239 pub fn for_temperature() -> Self {
240 Self::new(60.0, 80.0)
241 }
242
243 pub fn update(&mut self, value: f64) -> bool {
245 let was_high = self.is_high;
246
247 if self.is_high && value < self.low {
248 self.is_high = false;
249 } else if !self.is_high && value > self.high {
250 self.is_high = true;
251 }
252
253 was_high != self.is_high
254 }
255
256 #[must_use]
258 pub fn is_high(&self) -> bool {
259 self.is_high
260 }
261
262 #[must_use]
264 pub fn is_low(&self) -> bool {
265 !self.is_high
266 }
267
268 #[must_use]
270 pub fn low_threshold(&self) -> f64 {
271 self.low
272 }
273
274 #[must_use]
276 pub fn high_threshold(&self) -> f64 {
277 self.high
278 }
279
280 pub fn reset(&mut self) {
282 self.is_high = false;
283 }
284
285 pub fn set_high(&mut self) {
287 self.is_high = true;
288 }
289}
290
291#[derive(Debug, Clone)]
299pub struct SampleCounter {
300 count: u64,
302 last_count: u64,
304 last_time_us: u64,
306 rate: f64,
308}
309
310impl Default for SampleCounter {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316impl SampleCounter {
317 #[must_use]
319 pub fn new() -> Self {
320 Self {
321 count: 0,
322 last_count: 0,
323 last_time_us: 0,
324 rate: 0.0,
325 }
326 }
327
328 pub fn increment(&mut self) {
330 self.count += 1;
331 }
332
333 pub fn add(&mut self, n: u64) {
335 self.count += n;
336 }
337
338 #[must_use]
340 pub fn count(&self) -> u64 {
341 self.count
342 }
343
344 pub fn calculate_rate(&mut self) -> f64 {
348 let now = std::time::SystemTime::now()
349 .duration_since(std::time::UNIX_EPOCH)
350 .unwrap_or_default()
351 .as_micros() as u64;
352
353 if self.last_time_us > 0 {
354 let elapsed_us = now.saturating_sub(self.last_time_us);
355 if elapsed_us > 0 {
356 let delta = self.count.saturating_sub(self.last_count);
357 self.rate = (delta as f64 * 1_000_000.0) / elapsed_us as f64;
358 }
359 }
360
361 self.last_count = self.count;
362 self.last_time_us = now;
363 self.rate
364 }
365
366 #[must_use]
368 pub fn rate(&self) -> f64 {
369 self.rate
370 }
371
372 pub fn reset(&mut self) {
374 self.count = 0;
375 self.last_count = 0;
376 self.last_time_us = 0;
377 self.rate = 0.0;
378 }
379}
380
381#[derive(Debug, Clone)]
389pub struct BudgetTracker {
390 budget: f64,
392 usage: f64,
394 peak: f64,
396}
397
398impl BudgetTracker {
399 #[must_use]
401 pub fn new(budget: f64) -> Self {
402 Self {
403 budget: budget.max(0.0),
404 usage: 0.0,
405 peak: 0.0,
406 }
407 }
408
409 #[must_use]
411 pub fn for_render() -> Self {
412 contract_pre_render!();
413 Self::new(16_000.0) }
415
416 #[must_use]
418 pub fn for_compute() -> Self {
419 Self::new(1_000.0) }
421
422 pub fn record(&mut self, usage: f64) {
424 self.usage = usage;
425 self.peak = self.peak.max(usage);
426 }
427
428 #[must_use]
430 pub fn usage(&self) -> f64 {
431 self.usage
432 }
433
434 #[must_use]
436 pub fn peak(&self) -> f64 {
437 self.peak
438 }
439
440 #[must_use]
442 pub fn budget(&self) -> f64 {
443 self.budget
444 }
445
446 #[must_use]
448 pub fn utilization(&self) -> f64 {
449 if self.budget <= 0.0 {
450 0.0
451 } else {
452 (self.usage / self.budget) * 100.0
453 }
454 }
455
456 #[must_use]
458 pub fn peak_utilization(&self) -> f64 {
459 if self.budget <= 0.0 {
460 0.0
461 } else {
462 (self.peak / self.budget) * 100.0
463 }
464 }
465
466 #[must_use]
468 pub fn is_over_budget(&self) -> bool {
469 self.usage > self.budget
470 }
471
472 #[must_use]
474 pub fn remaining(&self) -> f64 {
475 (self.budget - self.usage).max(0.0)
476 }
477
478 pub fn reset(&mut self) {
480 self.usage = 0.0;
481 self.peak = 0.0;
482 }
483
484 pub fn set_budget(&mut self, budget: f64) {
486 self.budget = budget.max(0.0);
487 }
488}
489
490#[derive(Debug, Clone)]
499pub struct MinMaxTracker {
500 min: f64,
502 max: f64,
504 min_time_us: u64,
506 max_time_us: u64,
508 count: u64,
510}
511
512impl Default for MinMaxTracker {
513 fn default() -> Self {
514 Self::new()
515 }
516}
517
518impl MinMaxTracker {
519 #[must_use]
521 pub fn new() -> Self {
522 Self {
523 min: f64::MAX,
524 max: f64::MIN,
525 min_time_us: 0,
526 max_time_us: 0,
527 count: 0,
528 }
529 }
530
531 pub fn record(&mut self, value: f64) {
533 let now = std::time::SystemTime::now()
534 .duration_since(std::time::UNIX_EPOCH)
535 .unwrap_or_default()
536 .as_micros() as u64;
537
538 if value < self.min {
539 self.min = value;
540 self.min_time_us = now;
541 }
542 if value > self.max {
543 self.max = value;
544 self.max_time_us = now;
545 }
546 self.count += 1;
547 }
548
549 #[must_use]
551 pub fn min(&self) -> Option<f64> {
552 if self.count > 0 {
553 Some(self.min)
554 } else {
555 None
556 }
557 }
558
559 #[must_use]
561 pub fn max(&self) -> Option<f64> {
562 if self.count > 0 {
563 Some(self.max)
564 } else {
565 None
566 }
567 }
568
569 #[must_use]
571 pub fn range(&self) -> Option<f64> {
572 if self.count > 0 {
573 Some(self.max - self.min)
574 } else {
575 None
576 }
577 }
578
579 #[must_use]
581 pub fn count(&self) -> u64 {
582 self.count
583 }
584
585 #[must_use]
587 pub fn time_since_min_us(&self) -> u64 {
588 if self.min_time_us == 0 {
589 return 0;
590 }
591 let now = std::time::SystemTime::now()
592 .duration_since(std::time::UNIX_EPOCH)
593 .unwrap_or_default()
594 .as_micros() as u64;
595 now.saturating_sub(self.min_time_us)
596 }
597
598 #[must_use]
600 pub fn time_since_max_us(&self) -> u64 {
601 if self.max_time_us == 0 {
602 return 0;
603 }
604 let now = std::time::SystemTime::now()
605 .duration_since(std::time::UNIX_EPOCH)
606 .unwrap_or_default()
607 .as_micros() as u64;
608 now.saturating_sub(self.max_time_us)
609 }
610
611 pub fn reset(&mut self) {
613 self.min = f64::MAX;
614 self.max = f64::MIN;
615 self.min_time_us = 0;
616 self.max_time_us = 0;
617 self.count = 0;
618 }
619}
620
621#[derive(Debug, Clone)]
630pub struct MovingWindow {
631 current_sum: f64,
633 current_count: u64,
635 prev_sum: f64,
637 prev_count: u64,
639 window_us: u64,
641 bucket_start_us: u64,
643}
644
645impl MovingWindow {
646 #[must_use]
648 pub fn new(window_ms: u64) -> Self {
649 let now = std::time::SystemTime::now()
650 .duration_since(std::time::UNIX_EPOCH)
651 .unwrap_or_default()
652 .as_micros() as u64;
653
654 Self {
655 current_sum: 0.0,
656 current_count: 0,
657 prev_sum: 0.0,
658 prev_count: 0,
659 window_us: window_ms * 1000,
660 bucket_start_us: now,
661 }
662 }
663
664 #[must_use]
666 pub fn one_second() -> Self {
667 Self::new(1000)
668 }
669
670 #[must_use]
672 pub fn one_minute() -> Self {
673 Self::new(60_000)
674 }
675
676 pub fn record(&mut self, value: f64) {
678 self.maybe_rotate();
679 self.current_sum += value;
680 self.current_count += 1;
681 }
682
683 pub fn increment(&mut self) {
685 self.record(1.0);
686 }
687
688 fn maybe_rotate(&mut self) {
690 let now = std::time::SystemTime::now()
691 .duration_since(std::time::UNIX_EPOCH)
692 .unwrap_or_default()
693 .as_micros() as u64;
694
695 let elapsed = now.saturating_sub(self.bucket_start_us);
696
697 if elapsed >= self.window_us {
698 self.prev_sum = self.current_sum;
700 self.prev_count = self.current_count;
701 self.current_sum = 0.0;
702 self.current_count = 0;
703 self.bucket_start_us = now;
704 }
705 }
706
707 #[must_use]
709 pub fn sum(&mut self) -> f64 {
710 self.maybe_rotate();
711 self.current_sum + self.prev_sum
712 }
713
714 #[must_use]
716 pub fn count(&mut self) -> u64 {
717 self.maybe_rotate();
718 self.current_count + self.prev_count
719 }
720
721 #[must_use]
723 pub fn rate_per_second(&mut self) -> f64 {
724 self.maybe_rotate();
725 let total = self.current_sum + self.prev_sum;
726 let window_secs = (self.window_us as f64) / 1_000_000.0;
727 if window_secs > 0.0 {
728 total / window_secs
729 } else {
730 0.0
731 }
732 }
733
734 #[must_use]
736 pub fn count_rate(&mut self) -> f64 {
737 self.maybe_rotate();
738 let total = self.current_count + self.prev_count;
739 let window_secs = (self.window_us as f64) / 1_000_000.0;
740 if window_secs > 0.0 {
741 total as f64 / window_secs
742 } else {
743 0.0
744 }
745 }
746
747 pub fn reset(&mut self) {
749 let now = std::time::SystemTime::now()
750 .duration_since(std::time::UNIX_EPOCH)
751 .unwrap_or_default()
752 .as_micros() as u64;
753
754 self.current_sum = 0.0;
755 self.current_count = 0;
756 self.prev_sum = 0.0;
757 self.prev_count = 0;
758 self.bucket_start_us = now;
759 }
760}
761
762#[derive(Debug, Clone)]
771pub struct PercentileTracker {
772 buckets: [u64; 10],
774 count: u64,
776 boundaries: [u64; 10],
778}
779
780impl Default for PercentileTracker {
781 fn default() -> Self {
782 Self::new()
783 }
784}
785
786impl PercentileTracker {
787 #[must_use]
789 pub fn new() -> Self {
790 Self {
791 buckets: [0; 10],
792 count: 0,
793 boundaries: [
795 1_000, 5_000, 10_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, u64::MAX, ],
806 }
807 }
808
809 #[must_use]
811 pub fn with_boundaries(boundaries: [u64; 10]) -> Self {
812 Self {
813 buckets: [0; 10],
814 count: 0,
815 boundaries,
816 }
817 }
818
819 pub fn record_us(&mut self, value_us: u64) {
821 for (i, &boundary) in self.boundaries.iter().enumerate() {
822 if value_us < boundary {
823 self.buckets[i] += 1;
824 self.count += 1;
825 return;
826 }
827 }
828 self.buckets[9] += 1;
830 self.count += 1;
831 }
832
833 pub fn record_ms(&mut self, value_ms: f64) {
835 self.record_us((value_ms * 1000.0) as u64);
836 }
837
838 #[must_use]
840 pub fn percentile_us(&self, pct: f64) -> u64 {
841 if self.count == 0 {
842 return 0;
843 }
844
845 let target = ((pct / 100.0) * self.count as f64) as u64;
846 let mut cumulative = 0u64;
847
848 for (i, &bucket_count) in self.buckets.iter().enumerate() {
849 cumulative += bucket_count;
850 if cumulative >= target {
851 let lower = if i == 0 { 0 } else { self.boundaries[i - 1] };
853 let upper = self.boundaries[i];
854 if upper == u64::MAX {
855 return lower + 500_000; }
857 return (lower + upper) / 2;
858 }
859 }
860
861 self.boundaries[8] }
863
864 #[must_use]
866 pub fn percentile_ms(&self, pct: f64) -> f64 {
867 self.percentile_us(pct) as f64 / 1000.0
868 }
869
870 #[must_use]
872 pub fn p50_ms(&self) -> f64 {
873 self.percentile_ms(50.0)
874 }
875
876 #[must_use]
878 pub fn p90_ms(&self) -> f64 {
879 self.percentile_ms(90.0)
880 }
881
882 #[must_use]
884 pub fn p99_ms(&self) -> f64 {
885 self.percentile_ms(99.0)
886 }
887
888 #[must_use]
890 pub fn count(&self) -> u64 {
891 self.count
892 }
893
894 pub fn reset(&mut self) {
896 self.buckets = [0; 10];
897 self.count = 0;
898 }
899}
900
901#[derive(Debug, Clone)]
910pub struct StateTracker<const N: usize> {
911 current: usize,
913 entered_us: u64,
915 durations: [u64; N],
917 transitions: [u64; N],
919}
920
921impl<const N: usize> Default for StateTracker<N> {
922 fn default() -> Self {
923 Self::new()
924 }
925}
926
927impl<const N: usize> StateTracker<N> {
928 #[must_use]
930 pub fn new() -> Self {
931 let now = std::time::SystemTime::now()
932 .duration_since(std::time::UNIX_EPOCH)
933 .unwrap_or_default()
934 .as_micros() as u64;
935
936 let mut transitions = [0u64; N];
937 if N > 0 {
938 transitions[0] = 1; }
940
941 Self {
942 current: 0,
943 entered_us: now,
944 durations: [0u64; N],
945 transitions,
946 }
947 }
948
949 pub fn transition(&mut self, new_state: usize) {
951 if new_state >= N {
952 return; }
954
955 let now = std::time::SystemTime::now()
956 .duration_since(std::time::UNIX_EPOCH)
957 .unwrap_or_default()
958 .as_micros() as u64;
959
960 let elapsed = now.saturating_sub(self.entered_us);
962 self.durations[self.current] += elapsed;
963
964 self.current = new_state;
966 self.entered_us = now;
967 self.transitions[new_state] += 1;
968 }
969
970 #[must_use]
972 pub fn current(&self) -> usize {
973 self.current
974 }
975
976 #[must_use]
978 pub fn time_in_current_us(&self) -> u64 {
979 let now = std::time::SystemTime::now()
980 .duration_since(std::time::UNIX_EPOCH)
981 .unwrap_or_default()
982 .as_micros() as u64;
983 now.saturating_sub(self.entered_us)
984 }
985
986 #[must_use]
988 pub fn total_time_in_state_us(&self, state: usize) -> u64 {
989 if state >= N {
990 return 0;
991 }
992 if state == self.current {
993 self.durations[state] + self.time_in_current_us()
994 } else {
995 self.durations[state]
996 }
997 }
998
999 #[must_use]
1001 pub fn transition_count(&self, state: usize) -> u64 {
1002 if state >= N {
1003 0
1004 } else {
1005 self.transitions[state]
1006 }
1007 }
1008
1009 #[must_use]
1011 pub fn total_transitions(&self) -> u64 {
1012 self.transitions.iter().sum()
1013 }
1014
1015 pub fn reset(&mut self) {
1017 let now = std::time::SystemTime::now()
1018 .duration_since(std::time::UNIX_EPOCH)
1019 .unwrap_or_default()
1020 .as_micros() as u64;
1021
1022 self.current = 0;
1023 self.entered_us = now;
1024 self.durations = [0u64; N];
1025 self.transitions = [0u64; N];
1026 if N > 0 {
1027 self.transitions[0] = 1;
1028 }
1029 }
1030}
1031
1032#[derive(Debug, Clone)]
1041pub struct ChangeDetector {
1042 baseline: f64,
1044 abs_threshold: f64,
1046 rel_threshold: f64,
1048 last_value: f64,
1050 change_count: u64,
1052}
1053
1054impl Default for ChangeDetector {
1055 fn default() -> Self {
1056 Self::new(0.0, 1.0, 5.0)
1057 }
1058}
1059
1060impl ChangeDetector {
1061 #[must_use]
1068 pub fn new(baseline: f64, abs_threshold: f64, rel_threshold: f64) -> Self {
1069 Self {
1070 baseline,
1071 abs_threshold: abs_threshold.abs(),
1072 rel_threshold: rel_threshold.abs(),
1073 last_value: baseline,
1074 change_count: 0,
1075 }
1076 }
1077
1078 #[must_use]
1080 pub fn for_percentage() -> Self {
1081 Self::new(0.0, 1.0, 5.0)
1082 }
1083
1084 #[must_use]
1086 pub fn for_latency() -> Self {
1087 Self::new(0.0, 1000.0, 10.0)
1088 }
1089
1090 #[must_use]
1092 pub fn has_changed(&self, value: f64) -> bool {
1093 let abs_diff = (value - self.last_value).abs();
1094
1095 if abs_diff >= self.abs_threshold {
1097 return true;
1098 }
1099
1100 if self.last_value.abs() > f64::EPSILON {
1102 let rel_diff = (abs_diff / self.last_value.abs()) * 100.0;
1103 if rel_diff >= self.rel_threshold {
1104 return true;
1105 }
1106 }
1107
1108 false
1109 }
1110
1111 pub fn update(&mut self, value: f64) -> bool {
1113 let changed = self.has_changed(value);
1114 if changed {
1115 self.change_count += 1;
1116 }
1117 self.last_value = value;
1118 changed
1119 }
1120
1121 pub fn update_baseline(&mut self) {
1123 self.baseline = self.last_value;
1124 }
1125
1126 pub fn set_baseline(&mut self, baseline: f64) {
1128 self.baseline = baseline;
1129 }
1130
1131 #[must_use]
1133 pub fn baseline(&self) -> f64 {
1134 self.baseline
1135 }
1136
1137 #[must_use]
1139 pub fn last_value(&self) -> f64 {
1140 self.last_value
1141 }
1142
1143 #[must_use]
1145 pub fn change_count(&self) -> u64 {
1146 self.change_count
1147 }
1148
1149 #[must_use]
1151 pub fn change_from_baseline(&self) -> f64 {
1152 self.last_value - self.baseline
1153 }
1154
1155 #[must_use]
1157 pub fn relative_change(&self) -> f64 {
1158 if self.baseline.abs() > f64::EPSILON {
1159 ((self.last_value - self.baseline) / self.baseline.abs()) * 100.0
1160 } else {
1161 0.0
1162 }
1163 }
1164
1165 pub fn reset(&mut self) {
1167 self.last_value = self.baseline;
1168 self.change_count = 0;
1169 }
1170}
1171
1172#[derive(Debug, Clone)]
1181pub struct Accumulator {
1182 value: u64,
1184 prev_raw: u64,
1186 initialized: bool,
1188 overflows: u64,
1190}
1191
1192impl Default for Accumulator {
1193 fn default() -> Self {
1194 Self::new()
1195 }
1196}
1197
1198impl Accumulator {
1199 #[must_use]
1201 pub fn new() -> Self {
1202 Self {
1203 value: 0,
1204 prev_raw: 0,
1205 initialized: false,
1206 overflows: 0,
1207 }
1208 }
1209
1210 pub fn update(&mut self, raw: u64) {
1214 if !self.initialized {
1215 self.prev_raw = raw;
1216 self.initialized = true;
1217 return;
1218 }
1219
1220 let delta = if raw >= self.prev_raw {
1222 raw - self.prev_raw
1223 } else {
1224 self.overflows += 1;
1226 (u64::MAX - self.prev_raw) + raw + 1
1228 };
1229
1230 self.value += delta;
1231 self.prev_raw = raw;
1232 }
1233
1234 pub fn add(&mut self, delta: u64) {
1236 self.value += delta;
1237 self.initialized = true;
1238 }
1239
1240 #[must_use]
1242 pub fn value(&self) -> u64 {
1243 self.value
1244 }
1245
1246 #[must_use]
1248 pub fn overflows(&self) -> u64 {
1249 self.overflows
1250 }
1251
1252 #[must_use]
1254 pub fn is_initialized(&self) -> bool {
1255 self.initialized
1256 }
1257
1258 #[must_use]
1260 pub fn last_raw(&self) -> u64 {
1261 self.prev_raw
1262 }
1263
1264 pub fn reset(&mut self) {
1266 self.value = 0;
1267 self.prev_raw = 0;
1268 self.initialized = false;
1269 self.overflows = 0;
1270 }
1271}
1272
1273#[derive(Debug, Clone)]
1282pub struct EventCounter<const N: usize> {
1283 counts: [u64; N],
1285 total: u64,
1287}
1288
1289impl<const N: usize> Default for EventCounter<N> {
1290 fn default() -> Self {
1291 Self::new()
1292 }
1293}
1294
1295impl<const N: usize> EventCounter<N> {
1296 #[must_use]
1298 pub fn new() -> Self {
1299 Self {
1300 counts: [0u64; N],
1301 total: 0,
1302 }
1303 }
1304
1305 pub fn increment(&mut self, category: usize) {
1307 if category < N {
1308 self.counts[category] += 1;
1309 self.total += 1;
1310 }
1311 }
1312
1313 pub fn add(&mut self, category: usize, count: u64) {
1315 if category < N {
1316 self.counts[category] += count;
1317 self.total += count;
1318 }
1319 }
1320
1321 #[must_use]
1323 pub fn count(&self, category: usize) -> u64 {
1324 if category < N {
1325 self.counts[category]
1326 } else {
1327 0
1328 }
1329 }
1330
1331 #[must_use]
1333 pub fn total(&self) -> u64 {
1334 self.total
1335 }
1336
1337 #[must_use]
1339 pub fn percentage(&self, category: usize) -> f64 {
1340 if self.total == 0 || category >= N {
1341 0.0
1342 } else {
1343 (self.counts[category] as f64 / self.total as f64) * 100.0
1344 }
1345 }
1346
1347 #[must_use]
1349 pub fn dominant(&self) -> Option<usize> {
1350 if self.total == 0 {
1351 return None;
1352 }
1353 self.counts
1354 .iter()
1355 .enumerate()
1356 .max_by_key(|(_, &count)| count)
1357 .map(|(idx, _)| idx)
1358 }
1359
1360 pub fn reset(&mut self) {
1362 self.counts = [0u64; N];
1363 self.total = 0;
1364 }
1365}
1366
1367#[derive(Debug, Clone)]
1376pub struct TrendDetector {
1377 sum: f64,
1379 sum_xy: f64,
1381 index: u64,
1383 count: u64,
1385 threshold: f64,
1387}
1388
1389impl Default for TrendDetector {
1390 fn default() -> Self {
1391 Self::new(0.1)
1392 }
1393}
1394
1395#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1397pub enum Trend {
1398 Up,
1400 Down,
1402 Flat,
1404 Unknown,
1406}
1407
1408impl TrendDetector {
1409 #[must_use]
1414 pub fn new(threshold: f64) -> Self {
1415 Self {
1416 sum: 0.0,
1417 sum_xy: 0.0,
1418 index: 0,
1419 count: 0,
1420 threshold: threshold.abs(),
1421 }
1422 }
1423
1424 #[must_use]
1426 pub fn for_percentage() -> Self {
1427 Self::new(0.5)
1428 }
1429
1430 #[must_use]
1432 pub fn for_latency() -> Self {
1433 Self::new(1.0)
1434 }
1435
1436 pub fn update(&mut self, value: f64) {
1438 self.sum += value;
1439 self.sum_xy += (self.index as f64) * value;
1440 self.index += 1;
1441 self.count += 1;
1442 }
1443
1444 #[must_use]
1449 pub fn slope(&self) -> f64 {
1450 if self.count < 2 {
1451 return 0.0;
1452 }
1453
1454 let n = self.count as f64;
1455 let sum_x = (self.count * (self.count - 1)) as f64 / 2.0; let sum_x2 = (self.count * (self.count - 1) * (2 * self.count - 1)) as f64 / 6.0;
1457
1458 let sum_x_squared = sum_x.powi(2);
1460 let denominator = n * sum_x2 - sum_x_squared;
1461 if denominator.abs() < f64::EPSILON {
1462 return 0.0;
1463 }
1464
1465 (n * self.sum_xy - sum_x * self.sum) / denominator
1466 }
1467
1468 #[must_use]
1470 pub fn trend(&self) -> Trend {
1471 if self.count < 3 {
1472 return Trend::Unknown;
1473 }
1474
1475 let slope = self.slope();
1476 if slope > self.threshold {
1477 Trend::Up
1478 } else if slope < -self.threshold {
1479 Trend::Down
1480 } else {
1481 Trend::Flat
1482 }
1483 }
1484
1485 #[must_use]
1487 pub fn is_trending_up(&self) -> bool {
1488 self.trend() == Trend::Up
1489 }
1490
1491 #[must_use]
1493 pub fn is_trending_down(&self) -> bool {
1494 self.trend() == Trend::Down
1495 }
1496
1497 #[must_use]
1499 pub fn count(&self) -> u64 {
1500 self.count
1501 }
1502
1503 pub fn reset(&mut self) {
1505 self.sum = 0.0;
1506 self.sum_xy = 0.0;
1507 self.index = 0;
1508 self.count = 0;
1509 }
1510}
1511
1512#[derive(Debug, Clone)]
1521pub struct AnomalyDetector {
1522 mean: f64,
1524 m2: f64,
1526 count: u64,
1528 threshold: f64,
1530 last_value: f64,
1532 anomaly_count: u64,
1534}
1535
1536impl Default for AnomalyDetector {
1537 fn default() -> Self {
1538 Self::new(3.0)
1539 }
1540}
1541
1542impl AnomalyDetector {
1543 #[must_use]
1548 pub fn new(threshold: f64) -> Self {
1549 Self {
1550 mean: 0.0,
1551 m2: 0.0,
1552 count: 0,
1553 threshold: threshold.abs(),
1554 last_value: 0.0,
1555 anomaly_count: 0,
1556 }
1557 }
1558
1559 #[must_use]
1561 pub fn two_sigma() -> Self {
1562 Self::new(2.0)
1563 }
1564
1565 #[must_use]
1567 pub fn three_sigma() -> Self {
1568 Self::new(3.0)
1569 }
1570
1571 pub fn update(&mut self, value: f64) -> bool {
1575 self.last_value = value;
1576 self.count += 1;
1577
1578 if self.count == 1 {
1580 self.mean = value;
1581 return false;
1582 }
1583
1584 let is_anomaly = self.is_anomaly(value);
1586 if is_anomaly {
1587 self.anomaly_count += 1;
1588 }
1589
1590 let delta = value - self.mean;
1592 self.mean += delta / self.count as f64;
1593 let delta2 = value - self.mean;
1594 self.m2 += delta * delta2;
1595
1596 is_anomaly
1597 }
1598
1599 #[must_use]
1601 pub fn is_anomaly(&self, value: f64) -> bool {
1602 if self.count < 10 {
1603 return false; }
1605
1606 let z = self.z_score(value);
1607 z.abs() > self.threshold
1608 }
1609
1610 #[must_use]
1612 pub fn z_score(&self, value: f64) -> f64 {
1613 let std_dev = self.std_dev();
1614 if std_dev < f64::EPSILON {
1615 return 0.0;
1616 }
1617 (value - self.mean) / std_dev
1618 }
1619
1620 #[must_use]
1622 pub fn mean(&self) -> f64 {
1623 self.mean
1624 }
1625
1626 #[must_use]
1628 pub fn variance(&self) -> f64 {
1629 if self.count < 2 {
1630 0.0
1631 } else {
1632 self.m2 / (self.count - 1) as f64
1633 }
1634 }
1635
1636 #[must_use]
1638 pub fn std_dev(&self) -> f64 {
1639 self.variance().sqrt()
1640 }
1641
1642 #[must_use]
1644 pub fn count(&self) -> u64 {
1645 self.count
1646 }
1647
1648 #[must_use]
1650 pub fn anomaly_count(&self) -> u64 {
1651 self.anomaly_count
1652 }
1653
1654 #[must_use]
1656 pub fn anomaly_rate(&self) -> f64 {
1657 if self.count == 0 {
1658 0.0
1659 } else {
1660 (self.anomaly_count as f64 / self.count as f64) * 100.0
1661 }
1662 }
1663
1664 #[must_use]
1666 pub fn threshold(&self) -> f64 {
1667 self.threshold
1668 }
1669
1670 pub fn reset(&mut self) {
1672 self.mean = 0.0;
1673 self.m2 = 0.0;
1674 self.count = 0;
1675 self.last_value = 0.0;
1676 self.anomaly_count = 0;
1677 }
1678}