Skip to main content

presentar_terminal/perf_trace/
analysis.rs

1// =============================================================================
2// THROUGHPUT TRACKER (trueno-viz O(1) rate calculation)
3// =============================================================================
4
5/// Throughput tracker for bytes/ops per second (trueno-viz pattern)
6///
7/// Tracks cumulative totals and calculates rates over time intervals.
8#[derive(Debug, Clone)]
9pub struct ThroughputTracker {
10    /// Total bytes/ops
11    total: u64,
12    /// Previous total (for delta)
13    prev_total: u64,
14    /// Last calculation time (microseconds)
15    last_time_us: u64,
16    /// Calculated rate (units per second)
17    rate: f64,
18    /// Peak rate
19    peak_rate: f64,
20}
21
22impl Default for ThroughputTracker {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl ThroughputTracker {
29    /// Create a new throughput tracker
30    #[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    /// Add bytes/ops (O(1))
42    pub fn add(&mut self, count: u64) {
43        self.total += count;
44    }
45
46    /// Calculate rate (should be called periodically) (O(1))
47    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    /// Get current rate (O(1))
68    #[must_use]
69    pub fn rate(&self) -> f64 {
70        self.rate
71    }
72
73    /// Get peak rate (O(1))
74    #[must_use]
75    pub fn peak_rate(&self) -> f64 {
76        self.peak_rate
77    }
78
79    /// Get total (O(1))
80    #[must_use]
81    pub fn total(&self) -> u64 {
82        self.total
83    }
84
85    /// Format rate as human-readable string (O(1))
86    #[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    /// Format rate as bytes/second (O(1))
101    #[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    /// Reset tracker
116    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// =============================================================================
126// JITTER TRACKER (trueno-viz O(1) latency jitter analysis)
127// =============================================================================
128
129/// Jitter tracker for latency variation (trueno-viz pattern)
130///
131/// Tracks inter-arrival time variation (jitter) common in network/audio.
132#[derive(Debug, Clone)]
133pub struct JitterTracker {
134    /// Previous value
135    prev: f64,
136    /// Running jitter (smoothed)
137    jitter: f64,
138    /// Peak jitter
139    peak_jitter: f64,
140    /// Sample count
141    count: u64,
142    /// Smoothing factor (like RFC 3550)
143    alpha: f64,
144}
145
146impl Default for JitterTracker {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152impl JitterTracker {
153    /// Create a new jitter tracker with RFC 3550 smoothing
154    #[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, // RFC 3550 default
162        }
163    }
164
165    /// Create with custom smoothing factor
166    #[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    /// Update with new inter-arrival time (O(1))
178    ///
179    /// Uses RFC 3550 jitter calculation: J = J + (|D| - J) / 16
180    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        // Calculate difference from previous
189        let diff = (value - self.prev).abs();
190        self.prev = value;
191
192        // Exponential smoothing (RFC 3550 style)
193        self.jitter += self.alpha * (diff - self.jitter);
194        self.peak_jitter = self.peak_jitter.max(self.jitter);
195    }
196
197    /// Get current jitter (O(1))
198    #[must_use]
199    pub fn jitter(&self) -> f64 {
200        self.jitter
201    }
202
203    /// Get peak jitter (O(1))
204    #[must_use]
205    pub fn peak_jitter(&self) -> f64 {
206        self.peak_jitter
207    }
208
209    /// Get sample count
210    #[must_use]
211    pub fn count(&self) -> u64 {
212        self.count
213    }
214
215    /// Check if jitter exceeds threshold
216    #[must_use]
217    pub fn exceeds(&self, threshold: f64) -> bool {
218        self.jitter > threshold
219    }
220
221    /// Reset tracker
222    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// =============================================================================
231// DERIVATIVE TRACKER (trueno-viz O(1) rate-of-change pattern)
232// =============================================================================
233
234/// First derivative (rate of change) tracker (trueno-viz pattern)
235///
236/// Tracks instantaneous and smoothed rate of change for metrics.
237/// Useful for detecting acceleration/deceleration in CPU, memory, etc.
238#[derive(Debug, Clone)]
239pub struct DerivativeTracker {
240    /// Previous value
241    prev: f64,
242    /// Previous time (microseconds)
243    prev_time_us: u64,
244    /// Instantaneous derivative
245    derivative: f64,
246    /// Smoothed derivative (EMA)
247    smoothed: f64,
248    /// Smoothing factor
249    alpha: f64,
250    /// Sample count
251    count: u64,
252}
253
254impl Default for DerivativeTracker {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260impl DerivativeTracker {
261    /// Create a new derivative tracker with default smoothing (0.3)
262    #[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    /// Create with custom smoothing factor
275    #[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    /// Update with new value (O(1))
288    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; // seconds
303        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    /// Update with explicit delta time (for testing)
313    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    /// Get instantaneous derivative (O(1))
330    #[must_use]
331    pub fn derivative(&self) -> f64 {
332        self.derivative
333    }
334
335    /// Get smoothed derivative (O(1))
336    #[must_use]
337    pub fn smoothed(&self) -> f64 {
338        self.smoothed
339    }
340
341    /// Check if accelerating (positive derivative)
342    #[must_use]
343    pub fn is_accelerating(&self) -> bool {
344        self.smoothed > 0.0
345    }
346
347    /// Check if decelerating (negative derivative)
348    #[must_use]
349    pub fn is_decelerating(&self) -> bool {
350        self.smoothed < 0.0
351    }
352
353    /// Get sample count
354    #[must_use]
355    pub fn count(&self) -> u64 {
356        self.count
357    }
358
359    /// Reset tracker
360    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// =============================================================================
370// INTEGRAL TRACKER (trueno-viz O(1) cumulative area pattern)
371// =============================================================================
372
373/// Integral (cumulative area) tracker (trueno-viz pattern)
374///
375/// Tracks cumulative area under the curve using trapezoidal rule.
376/// Useful for energy consumption, work done, cumulative load.
377#[derive(Debug, Clone)]
378pub struct IntegralTracker {
379    /// Previous value
380    prev: f64,
381    /// Previous time (microseconds)
382    prev_time_us: u64,
383    /// Cumulative integral
384    integral: f64,
385    /// Sample count
386    count: u64,
387}
388
389impl Default for IntegralTracker {
390    fn default() -> Self {
391        Self::new()
392    }
393}
394
395impl IntegralTracker {
396    /// Create a new integral tracker
397    #[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    /// Update with new value (O(1))
408    ///
409    /// Uses trapezoidal rule: area = (v1 + v2) / 2 * dt
410    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; // seconds
425                                                                               // Trapezoidal rule
426        self.integral += (self.prev + value) / 2.0 * dt;
427
428        self.prev = value;
429        self.prev_time_us = now;
430    }
431
432    /// Update with explicit delta time (for testing)
433    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        // Trapezoidal rule
442        self.integral += (self.prev + value) / 2.0 * dt_secs;
443        self.prev = value;
444    }
445
446    /// Get cumulative integral (O(1))
447    #[must_use]
448    pub fn integral(&self) -> f64 {
449        self.integral
450    }
451
452    /// Get average value (integral / time) (O(1))
453    #[must_use]
454    pub fn average(&self) -> f64 {
455        if self.count < 2 {
456            return self.prev;
457        }
458        // Would need total time tracking for true average
459        self.prev
460    }
461
462    /// Get sample count
463    #[must_use]
464    pub fn count(&self) -> u64 {
465        self.count
466    }
467
468    /// Reset tracker
469    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// =============================================================================
478// CORRELATION TRACKER (trueno-viz O(1) running correlation)
479// =============================================================================
480
481/// Running correlation coefficient tracker (trueno-viz pattern)
482///
483/// Tracks Pearson correlation between two variables using online algorithm.
484/// Useful for finding related metrics (CPU vs memory, network vs disk).
485#[derive(Debug, Clone)]
486pub struct CorrelationTracker {
487    /// Mean of X
488    mean_x: f64,
489    /// Mean of Y
490    mean_y: f64,
491    /// Sum of (xi - mean_x) * (yi - mean_y)
492    cov_sum: f64,
493    /// Sum of (xi - mean_x)^2
494    var_x_sum: f64,
495    /// Sum of (yi - mean_y)^2
496    var_y_sum: f64,
497    /// Sample count
498    count: u64,
499}
500
501impl Default for CorrelationTracker {
502    fn default() -> Self {
503        Self::new()
504    }
505}
506
507impl CorrelationTracker {
508    /// Create a new correlation tracker
509    #[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    /// Update with new (x, y) pair (O(1))
522    ///
523    /// Uses Welford's online algorithm for covariance.
524    pub fn update(&mut self, x: f64, y: f64) {
525        self.count += 1;
526        let n = self.count as f64;
527
528        // Update means
529        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        // Update covariance and variance sums
536        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    /// Get correlation coefficient (O(1))
545    ///
546    /// Returns value in [-1, 1] or 0 if insufficient data.
547    #[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    /// Check if positively correlated (r > 0.5)
562    #[must_use]
563    pub fn is_positive(&self) -> bool {
564        self.correlation() > 0.5
565    }
566
567    /// Check if negatively correlated (r < -0.5)
568    #[must_use]
569    pub fn is_negative(&self) -> bool {
570        self.correlation() < -0.5
571    }
572
573    /// Check if strongly correlated (|r| > 0.7)
574    #[must_use]
575    pub fn is_strong(&self) -> bool {
576        self.correlation().abs() > 0.7
577    }
578
579    /// Get covariance (O(1))
580    #[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    /// Get sample count
589    #[must_use]
590    pub fn count(&self) -> u64 {
591        self.count
592    }
593
594    /// Reset tracker
595    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// =============================================================================
606// CIRCUIT BREAKER (trueno-viz O(1) resilience pattern)
607// =============================================================================
608
609/// Circuit breaker state
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
611pub enum CircuitState {
612    /// Normal operation, requests allowed
613    Closed,
614    /// Too many failures, requests blocked
615    Open,
616    /// Testing if service recovered
617    HalfOpen,
618}
619
620/// Circuit breaker for failure handling (trueno-viz pattern)
621///
622/// Prevents cascading failures by temporarily blocking requests
623/// after repeated failures.
624#[derive(Debug, Clone)]
625pub struct CircuitBreaker {
626    /// Current state
627    state: CircuitState,
628    /// Failure count
629    failures: u64,
630    /// Success count (in half-open state)
631    successes: u64,
632    /// Failure threshold to open circuit
633    failure_threshold: u64,
634    /// Success threshold to close circuit
635    success_threshold: u64,
636    /// Time circuit was opened (microseconds)
637    opened_at: u64,
638    /// Timeout before trying half-open (microseconds)
639    timeout_us: u64,
640}
641
642impl Default for CircuitBreaker {
643    fn default() -> Self {
644        Self::new(5, 3, 30_000_000) // 5 failures, 3 successes, 30s timeout
645    }
646}
647
648impl CircuitBreaker {
649    /// Create a new circuit breaker
650    ///
651    /// # Arguments
652    /// * `failure_threshold` - Failures before opening
653    /// * `success_threshold` - Successes in half-open before closing
654    /// * `timeout_us` - Microseconds before trying half-open
655    #[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    /// Create for network operations (5 failures, 30s timeout)
669    #[must_use]
670    pub fn for_network() -> Self {
671        Self::new(5, 3, 30_000_000)
672    }
673
674    /// Create for fast-fail (3 failures, 5s timeout)
675    #[must_use]
676    pub fn for_fast_fail() -> Self {
677        Self::new(3, 2, 5_000_000)
678    }
679
680    /// Check if request is allowed (O(1))
681    #[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    /// Record a success (O(1))
704    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    /// Record a failure (O(1))
721    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    /// Get current state (O(1))
745    #[must_use]
746    pub fn state(&self) -> CircuitState {
747        self.state
748    }
749
750    /// Get failure count
751    #[must_use]
752    pub fn failures(&self) -> u64 {
753        self.failures
754    }
755
756    /// Check if circuit is open
757    #[must_use]
758    pub fn is_open(&self) -> bool {
759        self.state == CircuitState::Open
760    }
761
762    /// Check if circuit is closed
763    #[must_use]
764    pub fn is_closed(&self) -> bool {
765        self.state == CircuitState::Closed
766    }
767
768    /// Force reset to closed state
769    pub fn reset(&mut self) {
770        self.state = CircuitState::Closed;
771        self.failures = 0;
772        self.successes = 0;
773    }
774}
775
776// =============================================================================
777// EXPONENTIAL BACKOFF (trueno-viz O(1) retry timing pattern)
778// =============================================================================
779
780/// Exponential backoff calculator (trueno-viz pattern)
781///
782/// Calculates retry delays with exponential growth and optional jitter.
783/// Useful for retry logic in network operations.
784#[derive(Debug, Clone)]
785pub struct ExponentialBackoff {
786    /// Base delay (microseconds)
787    base_us: u64,
788    /// Maximum delay (microseconds)
789    max_us: u64,
790    /// Current attempt
791    attempt: u64,
792    /// Multiplier for each attempt
793    multiplier: f64,
794    /// Add jitter (randomness)
795    jitter: bool,
796}
797
798impl Default for ExponentialBackoff {
799    fn default() -> Self {
800        Self::new(100_000, 30_000_000) // 100ms base, 30s max
801    }
802}
803
804impl ExponentialBackoff {
805    /// Create a new exponential backoff
806    ///
807    /// # Arguments
808    /// * `base_us` - Base delay in microseconds
809    /// * `max_us` - Maximum delay in microseconds
810    #[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    /// Create with jitter enabled
822    #[must_use]
823    pub fn with_jitter(mut self) -> Self {
824        self.jitter = true;
825        self
826    }
827
828    /// Create with custom multiplier
829    #[must_use]
830    pub fn with_multiplier(mut self, multiplier: f64) -> Self {
831        self.multiplier = multiplier.max(1.0);
832        self
833    }
834
835    /// Create for network retries (100ms base, 30s max, with jitter)
836    #[must_use]
837    pub fn for_network() -> Self {
838        Self::new(100_000, 30_000_000).with_jitter()
839    }
840
841    /// Create for fast retries (10ms base, 1s max)
842    #[must_use]
843    pub fn for_fast() -> Self {
844        Self::new(10_000, 1_000_000)
845    }
846
847    /// Get next delay and increment attempt (O(1))
848    pub fn next_delay(&mut self) -> u64 {
849        let delay = self.current_delay();
850        self.attempt += 1;
851        delay
852    }
853
854    /// Get current delay without incrementing (O(1))
855    #[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            // Simple deterministic jitter based on attempt
862            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    /// Get current delay in milliseconds
870    #[must_use]
871    pub fn current_delay_ms(&self) -> u64 {
872        self.current_delay() / 1000
873    }
874
875    /// Get attempt count
876    #[must_use]
877    pub fn attempt(&self) -> u64 {
878        self.attempt
879    }
880
881    /// Check if at max delay
882    #[must_use]
883    pub fn is_at_max(&self) -> bool {
884        self.current_delay() >= self.max_us
885    }
886
887    /// Reset to first attempt
888    pub fn reset(&mut self) {
889        self.attempt = 0;
890    }
891}
892
893// =============================================================================
894// SLIDING MEDIAN (trueno-viz O(1) approximate median pattern)
895// =============================================================================
896
897/// Approximate sliding median using histogram buckets (trueno-viz pattern)
898///
899/// Uses fixed-size histogram for O(1) median approximation.
900/// Good for latency percentiles where exact values aren't critical.
901#[derive(Debug, Clone)]
902pub struct SlidingMedian {
903    /// Histogram buckets
904    buckets: [u64; 10],
905    /// Bucket boundaries (upper bounds)
906    boundaries: [f64; 10],
907    /// Total count
908    count: u64,
909    /// Min value seen
910    min: f64,
911    /// Max value seen
912    max: f64,
913}
914
915impl Default for SlidingMedian {
916    fn default() -> Self {
917        Self::new()
918    }
919}
920
921impl SlidingMedian {
922    /// Create with default boundaries (0-1000 linear)
923    #[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    /// Create for latency (0-100ms, exponential)
937    #[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    /// Create for percentage (0-100%)
949    #[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    /// Update with new value (O(1))
961    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        // Find bucket
967        for (i, &boundary) in self.boundaries.iter().enumerate() {
968            if value <= boundary {
969                self.buckets[i] += 1;
970                return;
971            }
972        }
973        // Above all boundaries, put in last bucket
974        self.buckets[9] += 1;
975    }
976
977    /// Get approximate median (O(1))
978    #[must_use]
979    pub fn median(&self) -> f64 {
980        self.percentile(50)
981    }
982
983    /// Get approximate percentile (O(1))
984    #[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                // Return bucket midpoint
997                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    /// Get count
1006    #[must_use]
1007    pub fn count(&self) -> u64 {
1008        self.count
1009    }
1010
1011    /// Get min value
1012    #[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    /// Get max value
1022    #[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    /// Reset tracker
1032    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// =============================================================================
1041// HYSTERESIS FILTER (trueno-viz O(1) noise filtering pattern)
1042// =============================================================================
1043
1044/// Hysteresis filter for noise reduction (trueno-viz pattern)
1045///
1046/// Only changes output when input crosses threshold by dead band amount.
1047/// Prevents rapid toggling from noisy inputs.
1048#[derive(Debug, Clone)]
1049pub struct HysteresisFilter {
1050    /// Current output value
1051    output: f64,
1052    /// Dead band (minimum change to update)
1053    dead_band: f64,
1054    /// Sample count
1055    count: u64,
1056}
1057
1058impl Default for HysteresisFilter {
1059    fn default() -> Self {
1060        Self::new(1.0)
1061    }
1062}
1063
1064impl HysteresisFilter {
1065    /// Create with specified dead band
1066    #[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    /// Create for percentage values (1% dead band)
1076    #[must_use]
1077    pub fn for_percentage() -> Self {
1078        Self::new(1.0)
1079    }
1080
1081    /// Create for latency values (0.5ms dead band)
1082    #[must_use]
1083    pub fn for_latency() -> Self {
1084        Self::new(0.5)
1085    }
1086
1087    /// Create for temperature (0.5 degree dead band)
1088    #[must_use]
1089    pub fn for_temperature() -> Self {
1090        Self::new(0.5)
1091    }
1092
1093    /// Update with new value (O(1))
1094    ///
1095    /// Returns true if output changed.
1096    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    /// Get filtered output (O(1))
1113    #[must_use]
1114    pub fn output(&self) -> f64 {
1115        self.output
1116    }
1117
1118    /// Get dead band
1119    #[must_use]
1120    pub fn dead_band(&self) -> f64 {
1121        self.dead_band
1122    }
1123
1124    /// Set dead band
1125    pub fn set_dead_band(&mut self, dead_band: f64) {
1126        self.dead_band = dead_band.abs();
1127    }
1128
1129    /// Get sample count
1130    #[must_use]
1131    pub fn count(&self) -> u64 {
1132        self.count
1133    }
1134
1135    /// Reset filter
1136    pub fn reset(&mut self) {
1137        self.output = 0.0;
1138        self.count = 0;
1139    }
1140}
1141
1142// =============================================================================
1143// SPIKE FILTER (trueno-viz O(1) outlier rejection pattern)
1144// =============================================================================
1145
1146/// Spike filter for outlier rejection (trueno-viz pattern)
1147///
1148/// Rejects values that differ too much from recent average.
1149/// Good for sensor readings with occasional bad values.
1150#[derive(Debug, Clone)]
1151pub struct SpikeFilter {
1152    /// Running average
1153    avg: f64,
1154    /// Spike threshold (max deviation from avg)
1155    threshold: f64,
1156    /// Smoothing factor for avg
1157    alpha: f64,
1158    /// Spike count
1159    spikes: u64,
1160    /// Sample count
1161    count: u64,
1162    /// Last accepted value
1163    last_accepted: f64,
1164}
1165
1166impl Default for SpikeFilter {
1167    fn default() -> Self {
1168        Self::new(3.0)
1169    }
1170}
1171
1172impl SpikeFilter {
1173    /// Create with specified threshold (multiples of running avg)
1174    #[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    /// Create for percentage values
1187    #[must_use]
1188    pub fn for_percentage() -> Self {
1189        Self::new(50.0) // 50% deviation threshold
1190    }
1191
1192    /// Create for latency values
1193    #[must_use]
1194    pub fn for_latency() -> Self {
1195        Self::new(100.0) // 100ms deviation threshold
1196    }
1197
1198    /// Update with new value (O(1))
1199    ///
1200    /// Returns the filtered value (original if accepted, last accepted if spike).
1201    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        // Check if spike
1211        let deviation = (value - self.avg).abs();
1212        if deviation > self.threshold {
1213            self.spikes += 1;
1214            return self.last_accepted;
1215        }
1216
1217        // Accept and update average
1218        self.avg = self.alpha * value + (1.0 - self.alpha) * self.avg;
1219        self.last_accepted = value;
1220        value
1221    }
1222
1223    /// Get running average (O(1))
1224    #[must_use]
1225    pub fn average(&self) -> f64 {
1226        self.avg
1227    }
1228
1229    /// Get spike count
1230    #[must_use]
1231    pub fn spikes(&self) -> u64 {
1232        self.spikes
1233    }
1234
1235    /// Get spike rate (percentage)
1236    #[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    /// Get sample count
1246    #[must_use]
1247    pub fn count(&self) -> u64 {
1248        self.count
1249    }
1250
1251    /// Get last accepted value
1252    #[must_use]
1253    pub fn last_accepted(&self) -> f64 {
1254        self.last_accepted
1255    }
1256
1257    /// Reset filter
1258    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// =============================================================================
1267// GAUGE TRACKER (trueno-viz O(1) current value tracking pattern)
1268// =============================================================================
1269
1270/// Gauge tracker for current values (trueno-viz pattern)
1271///
1272/// Tracks current value with min/max/avg statistics.
1273/// Useful for memory, connections, queue depth.
1274#[derive(Debug, Clone)]
1275pub struct GaugeTracker {
1276    /// Current value
1277    current: f64,
1278    /// Minimum value
1279    min: f64,
1280    /// Maximum value
1281    max: f64,
1282    /// Running sum for average
1283    sum: f64,
1284    /// Sample count
1285    count: u64,
1286}
1287
1288impl Default for GaugeTracker {
1289    fn default() -> Self {
1290        Self::new()
1291    }
1292}
1293
1294impl GaugeTracker {
1295    /// Create a new gauge tracker
1296    #[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    /// Set current value (O(1))
1308    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    /// Increment current value
1317    pub fn inc(&mut self) {
1318        self.set(self.current + 1.0);
1319    }
1320
1321    /// Decrement current value
1322    pub fn dec(&mut self) {
1323        self.set(self.current - 1.0);
1324    }
1325
1326    /// Add to current value
1327    pub fn add(&mut self, delta: f64) {
1328        self.set(self.current + delta);
1329    }
1330
1331    /// Get current value (O(1))
1332    #[must_use]
1333    pub fn current(&self) -> f64 {
1334        self.current
1335    }
1336
1337    /// Get minimum value (O(1))
1338    #[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    /// Get maximum value (O(1))
1348    #[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    /// Get average value (O(1))
1358    #[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    /// Get range (max - min) (O(1))
1368    #[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    /// Get sample count
1378    #[must_use]
1379    pub fn count(&self) -> u64 {
1380        self.count
1381    }
1382
1383    /// Reset tracker
1384    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// =============================================================================
1394// COUNTER PAIR (trueno-viz O(1) success/failure tracking pattern)
1395// =============================================================================
1396
1397/// Counter pair for success/failure tracking (trueno-viz pattern)
1398///
1399/// Tracks success and failure counts with ratio calculation.
1400/// Useful for request success rates, error rates.
1401#[derive(Debug, Clone)]
1402pub struct CounterPair {
1403    /// Success count
1404    successes: u64,
1405    /// Failure count
1406    failures: u64,
1407}
1408
1409impl Default for CounterPair {
1410    fn default() -> Self {
1411        Self::new()
1412    }
1413}
1414
1415impl CounterPair {
1416    /// Create a new counter pair
1417    #[must_use]
1418    pub fn new() -> Self {
1419        Self {
1420            successes: 0,
1421            failures: 0,
1422        }
1423    }
1424
1425    /// Record a success (O(1))
1426    pub fn success(&mut self) {
1427        self.successes += 1;
1428    }
1429
1430    /// Record a failure (O(1))
1431    pub fn failure(&mut self) {
1432        self.failures += 1;
1433    }
1434
1435    /// Record multiple successes
1436    pub fn add_successes(&mut self, count: u64) {
1437        self.successes += count;
1438    }
1439
1440    /// Record multiple failures
1441    pub fn add_failures(&mut self, count: u64) {
1442        self.failures += count;
1443    }
1444
1445    /// Get success count (O(1))
1446    #[must_use]
1447    pub fn successes(&self) -> u64 {
1448        self.successes
1449    }
1450
1451    /// Get failure count (O(1))
1452    #[must_use]
1453    pub fn failures(&self) -> u64 {
1454        self.failures
1455    }
1456
1457    /// Get total count (O(1))
1458    #[must_use]
1459    pub fn total(&self) -> u64 {
1460        self.successes + self.failures
1461    }
1462
1463    /// Get success rate (percentage) (O(1))
1464    #[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    /// Get failure rate (percentage) (O(1))
1475    #[must_use]
1476    pub fn failure_rate(&self) -> f64 {
1477        100.0 - self.success_rate()
1478    }
1479
1480    /// Check if healthy (success rate > threshold)
1481    #[must_use]
1482    pub fn is_healthy(&self, threshold: f64) -> bool {
1483        self.success_rate() >= threshold
1484    }
1485
1486    /// Reset counters
1487    pub fn reset(&mut self) {
1488        self.successes = 0;
1489        self.failures = 0;
1490    }
1491}
1492
1493// =============================================================================
1494// HEALTH SCORE (trueno-viz O(1) composite health pattern)
1495// =============================================================================
1496
1497/// Health score calculator (trueno-viz pattern)
1498///
1499/// Combines multiple metrics into a single health score (0-100).
1500/// Useful for system health dashboards.
1501#[derive(Debug, Clone)]
1502pub struct HealthScore {
1503    /// Component scores (up to 8)
1504    scores: [f64; 8],
1505    /// Component weights
1506    weights: [f64; 8],
1507    /// Number of active components
1508    active: usize,
1509}
1510
1511impl Default for HealthScore {
1512    fn default() -> Self {
1513        Self::new()
1514    }
1515}
1516
1517impl HealthScore {
1518    /// Create a new health score calculator
1519    #[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    /// Set a component score (O(1))
1529    ///
1530    /// Index 0-7, score 0-100.
1531    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    /// Set a component weight (O(1))
1541    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    /// Get overall health score (O(1))
1548    #[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    /// Get health status (O(1))
1570    #[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    /// Check if healthy (score >= 90)
1585    #[must_use]
1586    pub fn is_healthy(&self) -> bool {
1587        self.score() >= 90.0
1588    }
1589
1590    /// Get minimum component score (O(1))
1591    #[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    /// Get number of active components
1602    #[must_use]
1603    pub fn active_components(&self) -> usize {
1604        self.active
1605    }
1606
1607    /// Reset all scores to 100
1608    pub fn reset(&mut self) {
1609        self.scores = [100.0; 8];
1610        self.active = 0;
1611    }
1612}
1613
1614/// Health status levels
1615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1616pub enum HealthStatus {
1617    /// Score >= 90
1618    Healthy,
1619    /// Score >= 70
1620    Degraded,
1621    /// Score >= 50
1622    Warning,
1623    /// Score < 50
1624    Critical,
1625}