1#[derive(Debug, Clone)]
9pub struct ThroughputTracker {
10 total: u64,
12 prev_total: u64,
14 last_time_us: u64,
16 rate: f64,
18 peak_rate: f64,
20}
21
22impl Default for ThroughputTracker {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl ThroughputTracker {
29 #[must_use]
31 pub fn new() -> Self {
32 Self {
33 total: 0,
34 prev_total: 0,
35 last_time_us: 0,
36 rate: 0.0,
37 peak_rate: 0.0,
38 }
39 }
40
41 pub fn add(&mut self, count: u64) {
43 self.total += count;
44 }
45
46 pub fn calculate_rate(&mut self) -> f64 {
48 let now = std::time::SystemTime::now()
49 .duration_since(std::time::UNIX_EPOCH)
50 .unwrap_or_default()
51 .as_micros() as u64;
52
53 if self.last_time_us > 0 {
54 let elapsed_us = now.saturating_sub(self.last_time_us);
55 if elapsed_us > 0 {
56 let delta = self.total.saturating_sub(self.prev_total);
57 self.rate = (delta as f64 * 1_000_000.0) / elapsed_us as f64;
58 self.peak_rate = self.peak_rate.max(self.rate);
59 }
60 }
61
62 self.prev_total = self.total;
63 self.last_time_us = now;
64 self.rate
65 }
66
67 #[must_use]
69 pub fn rate(&self) -> f64 {
70 self.rate
71 }
72
73 #[must_use]
75 pub fn peak_rate(&self) -> f64 {
76 self.peak_rate
77 }
78
79 #[must_use]
81 pub fn total(&self) -> u64 {
82 self.total
83 }
84
85 #[must_use]
87 pub fn format_rate(&self) -> String {
88 let rate = self.rate;
89 if rate >= 1_000_000_000.0 {
90 format!("{:.1}G/s", rate / 1_000_000_000.0)
91 } else if rate >= 1_000_000.0 {
92 format!("{:.1}M/s", rate / 1_000_000.0)
93 } else if rate >= 1_000.0 {
94 format!("{:.1}K/s", rate / 1_000.0)
95 } else {
96 format!("{:.0}/s", rate)
97 }
98 }
99
100 #[must_use]
102 pub fn format_bytes_rate(&self) -> String {
103 let rate = self.rate;
104 if rate >= 1_073_741_824.0 {
105 format!("{:.1}GB/s", rate / 1_073_741_824.0)
106 } else if rate >= 1_048_576.0 {
107 format!("{:.1}MB/s", rate / 1_048_576.0)
108 } else if rate >= 1_024.0 {
109 format!("{:.1}KB/s", rate / 1_024.0)
110 } else {
111 format!("{:.0}B/s", rate)
112 }
113 }
114
115 pub fn reset(&mut self) {
117 self.total = 0;
118 self.prev_total = 0;
119 self.last_time_us = 0;
120 self.rate = 0.0;
121 self.peak_rate = 0.0;
122 }
123}
124
125#[derive(Debug, Clone)]
133pub struct JitterTracker {
134 prev: f64,
136 jitter: f64,
138 peak_jitter: f64,
140 count: u64,
142 alpha: f64,
144}
145
146impl Default for JitterTracker {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl JitterTracker {
153 #[must_use]
155 pub fn new() -> Self {
156 Self {
157 prev: 0.0,
158 jitter: 0.0,
159 peak_jitter: 0.0,
160 count: 0,
161 alpha: 1.0 / 16.0, }
163 }
164
165 #[must_use]
167 pub fn with_alpha(alpha: f64) -> Self {
168 Self {
169 prev: 0.0,
170 jitter: 0.0,
171 peak_jitter: 0.0,
172 count: 0,
173 alpha: alpha.clamp(0.0, 1.0),
174 }
175 }
176
177 pub fn update(&mut self, value: f64) {
181 self.count += 1;
182
183 if self.count == 1 {
184 self.prev = value;
185 return;
186 }
187
188 let diff = (value - self.prev).abs();
190 self.prev = value;
191
192 self.jitter += self.alpha * (diff - self.jitter);
194 self.peak_jitter = self.peak_jitter.max(self.jitter);
195 }
196
197 #[must_use]
199 pub fn jitter(&self) -> f64 {
200 self.jitter
201 }
202
203 #[must_use]
205 pub fn peak_jitter(&self) -> f64 {
206 self.peak_jitter
207 }
208
209 #[must_use]
211 pub fn count(&self) -> u64 {
212 self.count
213 }
214
215 #[must_use]
217 pub fn exceeds(&self, threshold: f64) -> bool {
218 self.jitter > threshold
219 }
220
221 pub fn reset(&mut self) {
223 self.prev = 0.0;
224 self.jitter = 0.0;
225 self.peak_jitter = 0.0;
226 self.count = 0;
227 }
228}
229
230#[derive(Debug, Clone)]
239pub struct DerivativeTracker {
240 prev: f64,
242 prev_time_us: u64,
244 derivative: f64,
246 smoothed: f64,
248 alpha: f64,
250 count: u64,
252}
253
254impl Default for DerivativeTracker {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl DerivativeTracker {
261 #[must_use]
263 pub fn new() -> Self {
264 Self {
265 prev: 0.0,
266 prev_time_us: 0,
267 derivative: 0.0,
268 smoothed: 0.0,
269 alpha: 0.3,
270 count: 0,
271 }
272 }
273
274 #[must_use]
276 pub fn with_alpha(alpha: f64) -> Self {
277 Self {
278 prev: 0.0,
279 prev_time_us: 0,
280 derivative: 0.0,
281 smoothed: 0.0,
282 alpha: alpha.clamp(0.0, 1.0),
283 count: 0,
284 }
285 }
286
287 pub fn update(&mut self, value: f64) {
289 let now = std::time::SystemTime::now()
290 .duration_since(std::time::UNIX_EPOCH)
291 .unwrap_or_default()
292 .as_micros() as u64;
293
294 self.count += 1;
295
296 if self.count == 1 {
297 self.prev = value;
298 self.prev_time_us = now;
299 return;
300 }
301
302 let dt = (now.saturating_sub(self.prev_time_us)) as f64 / 1_000_000.0; if dt > 0.0 {
304 self.derivative = (value - self.prev) / dt;
305 self.smoothed = self.alpha * self.derivative + (1.0 - self.alpha) * self.smoothed;
306 }
307
308 self.prev = value;
309 self.prev_time_us = now;
310 }
311
312 pub fn update_with_dt(&mut self, value: f64, dt_secs: f64) {
314 self.count += 1;
315
316 if self.count == 1 {
317 self.prev = value;
318 return;
319 }
320
321 if dt_secs > 0.0 {
322 self.derivative = (value - self.prev) / dt_secs;
323 self.smoothed = self.alpha * self.derivative + (1.0 - self.alpha) * self.smoothed;
324 }
325
326 self.prev = value;
327 }
328
329 #[must_use]
331 pub fn derivative(&self) -> f64 {
332 self.derivative
333 }
334
335 #[must_use]
337 pub fn smoothed(&self) -> f64 {
338 self.smoothed
339 }
340
341 #[must_use]
343 pub fn is_accelerating(&self) -> bool {
344 self.smoothed > 0.0
345 }
346
347 #[must_use]
349 pub fn is_decelerating(&self) -> bool {
350 self.smoothed < 0.0
351 }
352
353 #[must_use]
355 pub fn count(&self) -> u64 {
356 self.count
357 }
358
359 pub fn reset(&mut self) {
361 self.prev = 0.0;
362 self.prev_time_us = 0;
363 self.derivative = 0.0;
364 self.smoothed = 0.0;
365 self.count = 0;
366 }
367}
368
369#[derive(Debug, Clone)]
378pub struct IntegralTracker {
379 prev: f64,
381 prev_time_us: u64,
383 integral: f64,
385 count: u64,
387}
388
389impl Default for IntegralTracker {
390 fn default() -> Self {
391 Self::new()
392 }
393}
394
395impl IntegralTracker {
396 #[must_use]
398 pub fn new() -> Self {
399 Self {
400 prev: 0.0,
401 prev_time_us: 0,
402 integral: 0.0,
403 count: 0,
404 }
405 }
406
407 pub fn update(&mut self, value: f64) {
411 let now = std::time::SystemTime::now()
412 .duration_since(std::time::UNIX_EPOCH)
413 .unwrap_or_default()
414 .as_micros() as u64;
415
416 self.count += 1;
417
418 if self.count == 1 {
419 self.prev = value;
420 self.prev_time_us = now;
421 return;
422 }
423
424 let dt = (now.saturating_sub(self.prev_time_us)) as f64 / 1_000_000.0; self.integral += (self.prev + value) / 2.0 * dt;
427
428 self.prev = value;
429 self.prev_time_us = now;
430 }
431
432 pub fn update_with_dt(&mut self, value: f64, dt_secs: f64) {
434 self.count += 1;
435
436 if self.count == 1 {
437 self.prev = value;
438 return;
439 }
440
441 self.integral += (self.prev + value) / 2.0 * dt_secs;
443 self.prev = value;
444 }
445
446 #[must_use]
448 pub fn integral(&self) -> f64 {
449 self.integral
450 }
451
452 #[must_use]
454 pub fn average(&self) -> f64 {
455 if self.count < 2 {
456 return self.prev;
457 }
458 self.prev
460 }
461
462 #[must_use]
464 pub fn count(&self) -> u64 {
465 self.count
466 }
467
468 pub fn reset(&mut self) {
470 self.prev = 0.0;
471 self.prev_time_us = 0;
472 self.integral = 0.0;
473 self.count = 0;
474 }
475}
476
477#[derive(Debug, Clone)]
486pub struct CorrelationTracker {
487 mean_x: f64,
489 mean_y: f64,
491 cov_sum: f64,
493 var_x_sum: f64,
495 var_y_sum: f64,
497 count: u64,
499}
500
501impl Default for CorrelationTracker {
502 fn default() -> Self {
503 Self::new()
504 }
505}
506
507impl CorrelationTracker {
508 #[must_use]
510 pub fn new() -> Self {
511 Self {
512 mean_x: 0.0,
513 mean_y: 0.0,
514 cov_sum: 0.0,
515 var_x_sum: 0.0,
516 var_y_sum: 0.0,
517 count: 0,
518 }
519 }
520
521 pub fn update(&mut self, x: f64, y: f64) {
525 self.count += 1;
526 let n = self.count as f64;
527
528 let delta_x = x - self.mean_x;
530 let delta_y = y - self.mean_y;
531
532 self.mean_x += delta_x / n;
533 self.mean_y += delta_y / n;
534
535 let delta_x2 = x - self.mean_x;
537 let delta_y2 = y - self.mean_y;
538
539 self.cov_sum += delta_x * delta_y2;
540 self.var_x_sum += delta_x * delta_x2;
541 self.var_y_sum += delta_y * delta_y2;
542 }
543
544 #[must_use]
548 pub fn correlation(&self) -> f64 {
549 if self.count < 2 {
550 return 0.0;
551 }
552
553 let denominator = (self.var_x_sum * self.var_y_sum).sqrt();
554 if denominator < f64::EPSILON {
555 return 0.0;
556 }
557
558 (self.cov_sum / denominator).clamp(-1.0, 1.0)
559 }
560
561 #[must_use]
563 pub fn is_positive(&self) -> bool {
564 self.correlation() > 0.5
565 }
566
567 #[must_use]
569 pub fn is_negative(&self) -> bool {
570 self.correlation() < -0.5
571 }
572
573 #[must_use]
575 pub fn is_strong(&self) -> bool {
576 self.correlation().abs() > 0.7
577 }
578
579 #[must_use]
581 pub fn covariance(&self) -> f64 {
582 if self.count < 2 {
583 return 0.0;
584 }
585 self.cov_sum / (self.count - 1) as f64
586 }
587
588 #[must_use]
590 pub fn count(&self) -> u64 {
591 self.count
592 }
593
594 pub fn reset(&mut self) {
596 self.mean_x = 0.0;
597 self.mean_y = 0.0;
598 self.cov_sum = 0.0;
599 self.var_x_sum = 0.0;
600 self.var_y_sum = 0.0;
601 self.count = 0;
602 }
603}
604
605#[derive(Debug, Clone, Copy, PartialEq, Eq)]
611pub enum CircuitState {
612 Closed,
614 Open,
616 HalfOpen,
618}
619
620#[derive(Debug, Clone)]
625pub struct CircuitBreaker {
626 state: CircuitState,
628 failures: u64,
630 successes: u64,
632 failure_threshold: u64,
634 success_threshold: u64,
636 opened_at: u64,
638 timeout_us: u64,
640}
641
642impl Default for CircuitBreaker {
643 fn default() -> Self {
644 Self::new(5, 3, 30_000_000) }
646}
647
648impl CircuitBreaker {
649 #[must_use]
656 pub fn new(failure_threshold: u64, success_threshold: u64, timeout_us: u64) -> Self {
657 Self {
658 state: CircuitState::Closed,
659 failures: 0,
660 successes: 0,
661 failure_threshold,
662 success_threshold,
663 opened_at: 0,
664 timeout_us,
665 }
666 }
667
668 #[must_use]
670 pub fn for_network() -> Self {
671 Self::new(5, 3, 30_000_000)
672 }
673
674 #[must_use]
676 pub fn for_fast_fail() -> Self {
677 Self::new(3, 2, 5_000_000)
678 }
679
680 #[must_use]
682 pub fn is_allowed(&mut self) -> bool {
683 match self.state {
684 CircuitState::Closed => true,
685 CircuitState::Open => {
686 let now = std::time::SystemTime::now()
687 .duration_since(std::time::UNIX_EPOCH)
688 .unwrap_or_default()
689 .as_micros() as u64;
690
691 if now.saturating_sub(self.opened_at) >= self.timeout_us {
692 self.state = CircuitState::HalfOpen;
693 self.successes = 0;
694 true
695 } else {
696 false
697 }
698 }
699 CircuitState::HalfOpen => true,
700 }
701 }
702
703 pub fn record_success(&mut self) {
705 match self.state {
706 CircuitState::Closed => {
707 self.failures = 0;
708 }
709 CircuitState::HalfOpen => {
710 self.successes += 1;
711 if self.successes >= self.success_threshold {
712 self.state = CircuitState::Closed;
713 self.failures = 0;
714 }
715 }
716 CircuitState::Open => {}
717 }
718 }
719
720 pub fn record_failure(&mut self) {
722 match self.state {
723 CircuitState::Closed => {
724 self.failures += 1;
725 if self.failures >= self.failure_threshold {
726 self.state = CircuitState::Open;
727 self.opened_at = std::time::SystemTime::now()
728 .duration_since(std::time::UNIX_EPOCH)
729 .unwrap_or_default()
730 .as_micros() as u64;
731 }
732 }
733 CircuitState::HalfOpen => {
734 self.state = CircuitState::Open;
735 self.opened_at = std::time::SystemTime::now()
736 .duration_since(std::time::UNIX_EPOCH)
737 .unwrap_or_default()
738 .as_micros() as u64;
739 }
740 CircuitState::Open => {}
741 }
742 }
743
744 #[must_use]
746 pub fn state(&self) -> CircuitState {
747 self.state
748 }
749
750 #[must_use]
752 pub fn failures(&self) -> u64 {
753 self.failures
754 }
755
756 #[must_use]
758 pub fn is_open(&self) -> bool {
759 self.state == CircuitState::Open
760 }
761
762 #[must_use]
764 pub fn is_closed(&self) -> bool {
765 self.state == CircuitState::Closed
766 }
767
768 pub fn reset(&mut self) {
770 self.state = CircuitState::Closed;
771 self.failures = 0;
772 self.successes = 0;
773 }
774}
775
776#[derive(Debug, Clone)]
785pub struct ExponentialBackoff {
786 base_us: u64,
788 max_us: u64,
790 attempt: u64,
792 multiplier: f64,
794 jitter: bool,
796}
797
798impl Default for ExponentialBackoff {
799 fn default() -> Self {
800 Self::new(100_000, 30_000_000) }
802}
803
804impl ExponentialBackoff {
805 #[must_use]
811 pub fn new(base_us: u64, max_us: u64) -> Self {
812 Self {
813 base_us,
814 max_us,
815 attempt: 0,
816 multiplier: 2.0,
817 jitter: false,
818 }
819 }
820
821 #[must_use]
823 pub fn with_jitter(mut self) -> Self {
824 self.jitter = true;
825 self
826 }
827
828 #[must_use]
830 pub fn with_multiplier(mut self, multiplier: f64) -> Self {
831 self.multiplier = multiplier.max(1.0);
832 self
833 }
834
835 #[must_use]
837 pub fn for_network() -> Self {
838 Self::new(100_000, 30_000_000).with_jitter()
839 }
840
841 #[must_use]
843 pub fn for_fast() -> Self {
844 Self::new(10_000, 1_000_000)
845 }
846
847 pub fn next_delay(&mut self) -> u64 {
849 let delay = self.current_delay();
850 self.attempt += 1;
851 delay
852 }
853
854 #[must_use]
856 pub fn current_delay(&self) -> u64 {
857 let delay = (self.base_us as f64 * self.multiplier.powi(self.attempt as i32)) as u64;
858 let capped = delay.min(self.max_us);
859
860 if self.jitter {
861 let jitter_factor = 0.5 + (self.attempt % 10) as f64 * 0.05;
863 ((capped as f64) * jitter_factor) as u64
864 } else {
865 capped
866 }
867 }
868
869 #[must_use]
871 pub fn current_delay_ms(&self) -> u64 {
872 self.current_delay() / 1000
873 }
874
875 #[must_use]
877 pub fn attempt(&self) -> u64 {
878 self.attempt
879 }
880
881 #[must_use]
883 pub fn is_at_max(&self) -> bool {
884 self.current_delay() >= self.max_us
885 }
886
887 pub fn reset(&mut self) {
889 self.attempt = 0;
890 }
891}
892
893#[derive(Debug, Clone)]
902pub struct SlidingMedian {
903 buckets: [u64; 10],
905 boundaries: [f64; 10],
907 count: u64,
909 min: f64,
911 max: f64,
913}
914
915impl Default for SlidingMedian {
916 fn default() -> Self {
917 Self::new()
918 }
919}
920
921impl SlidingMedian {
922 #[must_use]
924 pub fn new() -> Self {
925 Self {
926 buckets: [0; 10],
927 boundaries: [
928 100.0, 200.0, 300.0, 400.0, 500.0, 600.0, 700.0, 800.0, 900.0, 1000.0,
929 ],
930 count: 0,
931 min: f64::MAX,
932 max: f64::MIN,
933 }
934 }
935
936 #[must_use]
938 pub fn for_latency() -> Self {
939 Self {
940 buckets: [0; 10],
941 boundaries: [1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0],
942 count: 0,
943 min: f64::MAX,
944 max: f64::MIN,
945 }
946 }
947
948 #[must_use]
950 pub fn for_percentage() -> Self {
951 Self {
952 buckets: [0; 10],
953 boundaries: [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0],
954 count: 0,
955 min: f64::MAX,
956 max: f64::MIN,
957 }
958 }
959
960 pub fn update(&mut self, value: f64) {
962 self.count += 1;
963 self.min = self.min.min(value);
964 self.max = self.max.max(value);
965
966 for (i, &boundary) in self.boundaries.iter().enumerate() {
968 if value <= boundary {
969 self.buckets[i] += 1;
970 return;
971 }
972 }
973 self.buckets[9] += 1;
975 }
976
977 #[must_use]
979 pub fn median(&self) -> f64 {
980 self.percentile(50)
981 }
982
983 #[must_use]
985 pub fn percentile(&self, p: u8) -> f64 {
986 if self.count == 0 {
987 return 0.0;
988 }
989
990 let target = (self.count as f64 * p as f64 / 100.0) as u64;
991 let mut cumulative = 0u64;
992
993 for (i, &count) in self.buckets.iter().enumerate() {
994 cumulative += count;
995 if cumulative >= target {
996 let lower = if i == 0 { 0.0 } else { self.boundaries[i - 1] };
998 return (lower + self.boundaries[i]) / 2.0;
999 }
1000 }
1001
1002 self.boundaries[9]
1003 }
1004
1005 #[must_use]
1007 pub fn count(&self) -> u64 {
1008 self.count
1009 }
1010
1011 #[must_use]
1013 pub fn min(&self) -> f64 {
1014 if self.count == 0 {
1015 0.0
1016 } else {
1017 self.min
1018 }
1019 }
1020
1021 #[must_use]
1023 pub fn max(&self) -> f64 {
1024 if self.count == 0 {
1025 0.0
1026 } else {
1027 self.max
1028 }
1029 }
1030
1031 pub fn reset(&mut self) {
1033 self.buckets = [0; 10];
1034 self.count = 0;
1035 self.min = f64::MAX;
1036 self.max = f64::MIN;
1037 }
1038}
1039
1040#[derive(Debug, Clone)]
1049pub struct HysteresisFilter {
1050 output: f64,
1052 dead_band: f64,
1054 count: u64,
1056}
1057
1058impl Default for HysteresisFilter {
1059 fn default() -> Self {
1060 Self::new(1.0)
1061 }
1062}
1063
1064impl HysteresisFilter {
1065 #[must_use]
1067 pub fn new(dead_band: f64) -> Self {
1068 Self {
1069 output: 0.0,
1070 dead_band: dead_band.abs(),
1071 count: 0,
1072 }
1073 }
1074
1075 #[must_use]
1077 pub fn for_percentage() -> Self {
1078 Self::new(1.0)
1079 }
1080
1081 #[must_use]
1083 pub fn for_latency() -> Self {
1084 Self::new(0.5)
1085 }
1086
1087 #[must_use]
1089 pub fn for_temperature() -> Self {
1090 Self::new(0.5)
1091 }
1092
1093 pub fn update(&mut self, value: f64) -> bool {
1097 self.count += 1;
1098
1099 if self.count == 1 {
1100 self.output = value;
1101 return true;
1102 }
1103
1104 if (value - self.output).abs() >= self.dead_band {
1105 self.output = value;
1106 return true;
1107 }
1108
1109 false
1110 }
1111
1112 #[must_use]
1114 pub fn output(&self) -> f64 {
1115 self.output
1116 }
1117
1118 #[must_use]
1120 pub fn dead_band(&self) -> f64 {
1121 self.dead_band
1122 }
1123
1124 pub fn set_dead_band(&mut self, dead_band: f64) {
1126 self.dead_band = dead_band.abs();
1127 }
1128
1129 #[must_use]
1131 pub fn count(&self) -> u64 {
1132 self.count
1133 }
1134
1135 pub fn reset(&mut self) {
1137 self.output = 0.0;
1138 self.count = 0;
1139 }
1140}
1141
1142#[derive(Debug, Clone)]
1151pub struct SpikeFilter {
1152 avg: f64,
1154 threshold: f64,
1156 alpha: f64,
1158 spikes: u64,
1160 count: u64,
1162 last_accepted: f64,
1164}
1165
1166impl Default for SpikeFilter {
1167 fn default() -> Self {
1168 Self::new(3.0)
1169 }
1170}
1171
1172impl SpikeFilter {
1173 #[must_use]
1175 pub fn new(threshold: f64) -> Self {
1176 Self {
1177 avg: 0.0,
1178 threshold: threshold.abs(),
1179 alpha: 0.1,
1180 spikes: 0,
1181 count: 0,
1182 last_accepted: 0.0,
1183 }
1184 }
1185
1186 #[must_use]
1188 pub fn for_percentage() -> Self {
1189 Self::new(50.0) }
1191
1192 #[must_use]
1194 pub fn for_latency() -> Self {
1195 Self::new(100.0) }
1197
1198 pub fn update(&mut self, value: f64) -> f64 {
1202 self.count += 1;
1203
1204 if self.count == 1 {
1205 self.avg = value;
1206 self.last_accepted = value;
1207 return value;
1208 }
1209
1210 let deviation = (value - self.avg).abs();
1212 if deviation > self.threshold {
1213 self.spikes += 1;
1214 return self.last_accepted;
1215 }
1216
1217 self.avg = self.alpha * value + (1.0 - self.alpha) * self.avg;
1219 self.last_accepted = value;
1220 value
1221 }
1222
1223 #[must_use]
1225 pub fn average(&self) -> f64 {
1226 self.avg
1227 }
1228
1229 #[must_use]
1231 pub fn spikes(&self) -> u64 {
1232 self.spikes
1233 }
1234
1235 #[must_use]
1237 pub fn spike_rate(&self) -> f64 {
1238 if self.count == 0 {
1239 0.0
1240 } else {
1241 (self.spikes as f64 / self.count as f64) * 100.0
1242 }
1243 }
1244
1245 #[must_use]
1247 pub fn count(&self) -> u64 {
1248 self.count
1249 }
1250
1251 #[must_use]
1253 pub fn last_accepted(&self) -> f64 {
1254 self.last_accepted
1255 }
1256
1257 pub fn reset(&mut self) {
1259 self.avg = 0.0;
1260 self.spikes = 0;
1261 self.count = 0;
1262 self.last_accepted = 0.0;
1263 }
1264}
1265
1266#[derive(Debug, Clone)]
1275pub struct GaugeTracker {
1276 current: f64,
1278 min: f64,
1280 max: f64,
1282 sum: f64,
1284 count: u64,
1286}
1287
1288impl Default for GaugeTracker {
1289 fn default() -> Self {
1290 Self::new()
1291 }
1292}
1293
1294impl GaugeTracker {
1295 #[must_use]
1297 pub fn new() -> Self {
1298 Self {
1299 current: 0.0,
1300 min: f64::MAX,
1301 max: f64::MIN,
1302 sum: 0.0,
1303 count: 0,
1304 }
1305 }
1306
1307 pub fn set(&mut self, value: f64) {
1309 self.current = value;
1310 self.min = self.min.min(value);
1311 self.max = self.max.max(value);
1312 self.sum += value;
1313 self.count += 1;
1314 }
1315
1316 pub fn inc(&mut self) {
1318 self.set(self.current + 1.0);
1319 }
1320
1321 pub fn dec(&mut self) {
1323 self.set(self.current - 1.0);
1324 }
1325
1326 pub fn add(&mut self, delta: f64) {
1328 self.set(self.current + delta);
1329 }
1330
1331 #[must_use]
1333 pub fn current(&self) -> f64 {
1334 self.current
1335 }
1336
1337 #[must_use]
1339 pub fn min(&self) -> f64 {
1340 if self.count == 0 {
1341 0.0
1342 } else {
1343 self.min
1344 }
1345 }
1346
1347 #[must_use]
1349 pub fn max(&self) -> f64 {
1350 if self.count == 0 {
1351 0.0
1352 } else {
1353 self.max
1354 }
1355 }
1356
1357 #[must_use]
1359 pub fn average(&self) -> f64 {
1360 if self.count == 0 {
1361 0.0
1362 } else {
1363 self.sum / self.count as f64
1364 }
1365 }
1366
1367 #[must_use]
1369 pub fn range(&self) -> f64 {
1370 if self.count == 0 {
1371 0.0
1372 } else {
1373 self.max - self.min
1374 }
1375 }
1376
1377 #[must_use]
1379 pub fn count(&self) -> u64 {
1380 self.count
1381 }
1382
1383 pub fn reset(&mut self) {
1385 self.current = 0.0;
1386 self.min = f64::MAX;
1387 self.max = f64::MIN;
1388 self.sum = 0.0;
1389 self.count = 0;
1390 }
1391}
1392
1393#[derive(Debug, Clone)]
1402pub struct CounterPair {
1403 successes: u64,
1405 failures: u64,
1407}
1408
1409impl Default for CounterPair {
1410 fn default() -> Self {
1411 Self::new()
1412 }
1413}
1414
1415impl CounterPair {
1416 #[must_use]
1418 pub fn new() -> Self {
1419 Self {
1420 successes: 0,
1421 failures: 0,
1422 }
1423 }
1424
1425 pub fn success(&mut self) {
1427 self.successes += 1;
1428 }
1429
1430 pub fn failure(&mut self) {
1432 self.failures += 1;
1433 }
1434
1435 pub fn add_successes(&mut self, count: u64) {
1437 self.successes += count;
1438 }
1439
1440 pub fn add_failures(&mut self, count: u64) {
1442 self.failures += count;
1443 }
1444
1445 #[must_use]
1447 pub fn successes(&self) -> u64 {
1448 self.successes
1449 }
1450
1451 #[must_use]
1453 pub fn failures(&self) -> u64 {
1454 self.failures
1455 }
1456
1457 #[must_use]
1459 pub fn total(&self) -> u64 {
1460 self.successes + self.failures
1461 }
1462
1463 #[must_use]
1465 pub fn success_rate(&self) -> f64 {
1466 let total = self.total();
1467 if total == 0 {
1468 100.0
1469 } else {
1470 (self.successes as f64 / total as f64) * 100.0
1471 }
1472 }
1473
1474 #[must_use]
1476 pub fn failure_rate(&self) -> f64 {
1477 100.0 - self.success_rate()
1478 }
1479
1480 #[must_use]
1482 pub fn is_healthy(&self, threshold: f64) -> bool {
1483 self.success_rate() >= threshold
1484 }
1485
1486 pub fn reset(&mut self) {
1488 self.successes = 0;
1489 self.failures = 0;
1490 }
1491}
1492
1493#[derive(Debug, Clone)]
1502pub struct HealthScore {
1503 scores: [f64; 8],
1505 weights: [f64; 8],
1507 active: usize,
1509}
1510
1511impl Default for HealthScore {
1512 fn default() -> Self {
1513 Self::new()
1514 }
1515}
1516
1517impl HealthScore {
1518 #[must_use]
1520 pub fn new() -> Self {
1521 Self {
1522 scores: [100.0; 8],
1523 weights: [1.0; 8],
1524 active: 0,
1525 }
1526 }
1527
1528 pub fn set(&mut self, index: usize, score: f64) {
1532 if index < 8 {
1533 self.scores[index] = score.clamp(0.0, 100.0);
1534 if index >= self.active {
1535 self.active = index + 1;
1536 }
1537 }
1538 }
1539
1540 pub fn set_weight(&mut self, index: usize, weight: f64) {
1542 if index < 8 {
1543 self.weights[index] = weight.max(0.0);
1544 }
1545 }
1546
1547 #[must_use]
1549 pub fn score(&self) -> f64 {
1550 if self.active == 0 {
1551 return 100.0;
1552 }
1553
1554 let mut weighted_sum = 0.0;
1555 let mut weight_sum = 0.0;
1556
1557 for i in 0..self.active {
1558 weighted_sum += self.scores[i] * self.weights[i];
1559 weight_sum += self.weights[i];
1560 }
1561
1562 if weight_sum < f64::EPSILON {
1563 100.0
1564 } else {
1565 (weighted_sum / weight_sum).clamp(0.0, 100.0)
1566 }
1567 }
1568
1569 #[must_use]
1571 pub fn status(&self) -> HealthStatus {
1572 let score = self.score();
1573 if score >= 90.0 {
1574 HealthStatus::Healthy
1575 } else if score >= 70.0 {
1576 HealthStatus::Degraded
1577 } else if score >= 50.0 {
1578 HealthStatus::Warning
1579 } else {
1580 HealthStatus::Critical
1581 }
1582 }
1583
1584 #[must_use]
1586 pub fn is_healthy(&self) -> bool {
1587 self.score() >= 90.0
1588 }
1589
1590 #[must_use]
1592 pub fn min_score(&self) -> f64 {
1593 if self.active == 0 {
1594 return 100.0;
1595 }
1596 self.scores[..self.active]
1597 .iter()
1598 .fold(f64::MAX, |a, &b| a.min(b))
1599 }
1600
1601 #[must_use]
1603 pub fn active_components(&self) -> usize {
1604 self.active
1605 }
1606
1607 pub fn reset(&mut self) {
1609 self.scores = [100.0; 8];
1610 self.active = 0;
1611 }
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1616pub enum HealthStatus {
1617 Healthy,
1619 Degraded,
1621 Warning,
1623 Critical,
1625}