Skip to main content

presentar_terminal/perf_trace/
trackers.rs

1// =============================================================================
2// EMA TRACKER (trueno-viz O(1) smoothing pattern)
3// =============================================================================
4
5/// Exponential Moving Average tracker for O(1) smoothing (trueno-viz pattern)
6///
7/// Provides real-time smoothed values without storing history.
8/// Commonly used for FPS counters, load averages, and trend detection.
9#[derive(Debug, Clone)]
10pub struct EmaTracker {
11    /// Current smoothed value
12    value: f64,
13    /// Smoothing factor (0.0-1.0, higher = more responsive)
14    alpha: f64,
15    /// Whether we've received the first sample
16    initialized: bool,
17}
18
19impl Default for EmaTracker {
20    fn default() -> Self {
21        Self::new(0.1) // Default 10% weight for new samples
22    }
23}
24
25impl EmaTracker {
26    /// Create a new EMA tracker with given smoothing factor
27    ///
28    /// Alpha should be between 0.0 and 1.0:
29    /// - Higher alpha (e.g., 0.5) = more responsive, less smooth
30    /// - Lower alpha (e.g., 0.05) = less responsive, more smooth
31    #[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    /// Create tracker optimized for FPS counting (fast response)
41    #[must_use]
42    pub fn for_fps() -> Self {
43        Self::new(0.3)
44    }
45
46    /// Create tracker optimized for load average (slow response)
47    #[must_use]
48    pub fn for_load() -> Self {
49        Self::new(0.05)
50    }
51
52    /// Update with a new sample (O(1))
53    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    /// Get current smoothed value (O(1))
63    #[must_use]
64    pub fn value(&self) -> f64 {
65        self.value
66    }
67
68    /// Check if tracker has been initialized
69    #[must_use]
70    pub fn is_initialized(&self) -> bool {
71        self.initialized
72    }
73
74    /// Get the alpha (smoothing factor)
75    #[must_use]
76    pub fn alpha(&self) -> f64 {
77        self.alpha
78    }
79
80    /// Reset tracker to uninitialized state
81    pub fn reset(&mut self) {
82        self.value = 0.0;
83        self.initialized = false;
84    }
85
86    /// Set a new alpha value
87    pub fn set_alpha(&mut self, alpha: f64) {
88        self.alpha = alpha.clamp(0.0, 1.0);
89    }
90}
91
92// =============================================================================
93// RATE LIMITER (trueno-viz O(1) throttling pattern)
94// =============================================================================
95
96/// Token bucket rate limiter for O(1) throttling (trueno-viz pattern)
97///
98/// Used to limit update frequency without blocking.
99#[derive(Debug, Clone)]
100pub struct RateLimiter {
101    /// Last allowed time in microseconds
102    last_allowed_us: u64,
103    /// Minimum interval between allows in microseconds
104    interval_us: u64,
105}
106
107impl Default for RateLimiter {
108    fn default() -> Self {
109        Self::new_hz(60) // Default 60 Hz
110    }
111}
112
113impl RateLimiter {
114    /// Create rate limiter with interval in microseconds
115    #[must_use]
116    pub fn new(interval_us: u64) -> Self {
117        Self {
118            last_allowed_us: 0,
119            interval_us,
120        }
121    }
122
123    /// Create rate limiter for given frequency (Hz)
124    #[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    /// Create rate limiter for given millisecond interval
135    #[must_use]
136    pub fn new_ms(ms: u64) -> Self {
137        Self::new(ms * 1000)
138    }
139
140    /// Check if action is allowed now (O(1))
141    ///
142    /// Returns true and updates timestamp if allowed, false otherwise.
143    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    /// Check without updating (peek)
158    #[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    /// Get interval in microseconds
169    #[must_use]
170    pub fn interval_us(&self) -> u64 {
171        self.interval_us
172    }
173
174    /// Get interval in Hz
175    #[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    /// Reset the limiter
185    pub fn reset(&mut self) {
186        self.last_allowed_us = 0;
187    }
188}
189
190// =============================================================================
191// THRESHOLD DETECTOR (trueno-viz O(1) level detection pattern)
192// =============================================================================
193
194/// Threshold-based level detector for O(1) state transitions (trueno-viz pattern)
195///
196/// Provides hysteresis to prevent rapid toggling at threshold boundaries.
197/// Common use: CPU/memory warning levels, alert thresholds.
198#[derive(Debug, Clone)]
199pub struct ThresholdDetector {
200    /// Low threshold (transition from High to Low when below this)
201    low: f64,
202    /// High threshold (transition from Low to High when above this)
203    high: f64,
204    /// Current state (true = high/alert, false = low/normal)
205    is_high: bool,
206}
207
208impl ThresholdDetector {
209    /// Create a new threshold detector with hysteresis
210    ///
211    /// # Arguments
212    /// * `low` - Threshold to transition to normal state
213    /// * `high` - Threshold to transition to alert state
214    ///
215    /// Hysteresis: low < high prevents rapid toggling
216    #[must_use]
217    pub fn new(low: f64, high: f64) -> Self {
218        Self {
219            low,
220            high: high.max(low), // Ensure high >= low
221            is_high: false,
222        }
223    }
224
225    /// Create detector for percentage thresholds (0-100)
226    #[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    /// Create detector for CPU/memory warnings (70/90)
232    #[must_use]
233    pub fn for_resource() -> Self {
234        Self::new(70.0, 90.0)
235    }
236
237    /// Create detector for temperature warnings (60/80)
238    #[must_use]
239    pub fn for_temperature() -> Self {
240        Self::new(60.0, 80.0)
241    }
242
243    /// Update with new value and return whether state changed (O(1))
244    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    /// Check if currently in high/alert state
257    #[must_use]
258    pub fn is_high(&self) -> bool {
259        self.is_high
260    }
261
262    /// Check if currently in low/normal state
263    #[must_use]
264    pub fn is_low(&self) -> bool {
265        !self.is_high
266    }
267
268    /// Get the low threshold
269    #[must_use]
270    pub fn low_threshold(&self) -> f64 {
271        self.low
272    }
273
274    /// Get the high threshold
275    #[must_use]
276    pub fn high_threshold(&self) -> f64 {
277        self.high
278    }
279
280    /// Reset to low/normal state
281    pub fn reset(&mut self) {
282        self.is_high = false;
283    }
284
285    /// Force to high/alert state
286    pub fn set_high(&mut self) {
287        self.is_high = true;
288    }
289}
290
291// =============================================================================
292// SAMPLE COUNTER (trueno-viz O(1) counting pattern)
293// =============================================================================
294
295/// Sample counter with windowed rate calculation (trueno-viz pattern)
296///
297/// Tracks count and provides rate per second calculation.
298#[derive(Debug, Clone)]
299pub struct SampleCounter {
300    /// Total count
301    count: u64,
302    /// Count at last rate calculation
303    last_count: u64,
304    /// Time of last rate calculation in microseconds
305    last_time_us: u64,
306    /// Calculated rate (samples per second)
307    rate: f64,
308}
309
310impl Default for SampleCounter {
311    fn default() -> Self {
312        Self::new()
313    }
314}
315
316impl SampleCounter {
317    /// Create a new counter
318    #[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    /// Increment count by 1 (O(1))
329    pub fn increment(&mut self) {
330        self.count += 1;
331    }
332
333    /// Increment count by n (O(1))
334    pub fn add(&mut self, n: u64) {
335        self.count += n;
336    }
337
338    /// Get current count
339    #[must_use]
340    pub fn count(&self) -> u64 {
341        self.count
342    }
343
344    /// Calculate and get rate (samples per second)
345    ///
346    /// Should be called periodically (e.g., once per second)
347    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    /// Get last calculated rate without recalculating
367    #[must_use]
368    pub fn rate(&self) -> f64 {
369        self.rate
370    }
371
372    /// Reset counter
373    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// =============================================================================
382// BUDGET TRACKER (trueno-viz O(1) budget monitoring pattern)
383// =============================================================================
384
385/// Budget tracker for monitoring time/resource consumption (trueno-viz pattern)
386///
387/// Tracks usage against a budget and calculates utilization percentage.
388#[derive(Debug, Clone)]
389pub struct BudgetTracker {
390    /// Budget limit
391    budget: f64,
392    /// Current usage
393    usage: f64,
394    /// Peak usage
395    peak: f64,
396}
397
398impl BudgetTracker {
399    /// Create a new budget tracker
400    #[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    /// Create for 16ms render budget (60fps)
410    #[must_use]
411    pub fn for_render() -> Self {
412        contract_pre_render!();
413        Self::new(16_000.0) // 16ms in microseconds
414    }
415
416    /// Create for 1ms compute budget
417    #[must_use]
418    pub fn for_compute() -> Self {
419        Self::new(1_000.0) // 1ms in microseconds
420    }
421
422    /// Record usage (O(1))
423    pub fn record(&mut self, usage: f64) {
424        self.usage = usage;
425        self.peak = self.peak.max(usage);
426    }
427
428    /// Get current usage
429    #[must_use]
430    pub fn usage(&self) -> f64 {
431        self.usage
432    }
433
434    /// Get peak usage
435    #[must_use]
436    pub fn peak(&self) -> f64 {
437        self.peak
438    }
439
440    /// Get budget
441    #[must_use]
442    pub fn budget(&self) -> f64 {
443        self.budget
444    }
445
446    /// Get utilization percentage (O(1))
447    #[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    /// Get peak utilization percentage (O(1))
457    #[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    /// Check if over budget
467    #[must_use]
468    pub fn is_over_budget(&self) -> bool {
469        self.usage > self.budget
470    }
471
472    /// Get remaining budget
473    #[must_use]
474    pub fn remaining(&self) -> f64 {
475        (self.budget - self.usage).max(0.0)
476    }
477
478    /// Reset usage and peak
479    pub fn reset(&mut self) {
480        self.usage = 0.0;
481        self.peak = 0.0;
482    }
483
484    /// Set new budget
485    pub fn set_budget(&mut self, budget: f64) {
486        self.budget = budget.max(0.0);
487    }
488}
489
490// =============================================================================
491// MIN/MAX TRACKER (trueno-viz O(1) extrema tracking pattern)
492// =============================================================================
493
494/// Min/Max value tracker with timestamps (trueno-viz pattern)
495///
496/// Tracks minimum and maximum values along with when they occurred.
497/// Useful for monitoring extreme values in metrics over time.
498#[derive(Debug, Clone)]
499pub struct MinMaxTracker {
500    /// Minimum value seen
501    min: f64,
502    /// Maximum value seen
503    max: f64,
504    /// Time of minimum (microseconds since epoch)
505    min_time_us: u64,
506    /// Time of maximum (microseconds since epoch)
507    max_time_us: u64,
508    /// Sample count
509    count: u64,
510}
511
512impl Default for MinMaxTracker {
513    fn default() -> Self {
514        Self::new()
515    }
516}
517
518impl MinMaxTracker {
519    /// Create a new min/max tracker
520    #[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    /// Record a value (O(1))
532    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    /// Get minimum value (O(1))
550    #[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    /// Get maximum value (O(1))
560    #[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    /// Get range (max - min) (O(1))
570    #[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    /// Get sample count
580    #[must_use]
581    pub fn count(&self) -> u64 {
582        self.count
583    }
584
585    /// Get time since minimum in microseconds
586    #[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    /// Get time since maximum in microseconds
599    #[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    /// Reset tracker
612    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// =============================================================================
622// MOVING WINDOW (trueno-viz O(1) time-windowed aggregation pattern)
623// =============================================================================
624
625/// Time-windowed aggregation tracker (trueno-viz pattern)
626///
627/// Tracks sum/count over a sliding time window for rate calculations.
628/// Window expiration is O(1) using bucket rotation.
629#[derive(Debug, Clone)]
630pub struct MovingWindow {
631    /// Current bucket sum
632    current_sum: f64,
633    /// Current bucket count
634    current_count: u64,
635    /// Previous bucket sum
636    prev_sum: f64,
637    /// Previous bucket count
638    prev_count: u64,
639    /// Window duration in microseconds
640    window_us: u64,
641    /// Current bucket start time
642    bucket_start_us: u64,
643}
644
645impl MovingWindow {
646    /// Create a new moving window
647    #[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    /// Create 1-second window
665    #[must_use]
666    pub fn one_second() -> Self {
667        Self::new(1000)
668    }
669
670    /// Create 1-minute window
671    #[must_use]
672    pub fn one_minute() -> Self {
673        Self::new(60_000)
674    }
675
676    /// Record a value (O(1) with potential bucket rotation)
677    pub fn record(&mut self, value: f64) {
678        self.maybe_rotate();
679        self.current_sum += value;
680        self.current_count += 1;
681    }
682
683    /// Increment count by 1 (O(1))
684    pub fn increment(&mut self) {
685        self.record(1.0);
686    }
687
688    /// Check and rotate buckets if needed (O(1))
689    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            // Rotate: current becomes previous
699            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    /// Get sum over window (O(1))
708    #[must_use]
709    pub fn sum(&mut self) -> f64 {
710        self.maybe_rotate();
711        self.current_sum + self.prev_sum
712    }
713
714    /// Get count over window (O(1))
715    #[must_use]
716    pub fn count(&mut self) -> u64 {
717        self.maybe_rotate();
718        self.current_count + self.prev_count
719    }
720
721    /// Get rate per second (O(1))
722    #[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    /// Get count rate per second (O(1))
735    #[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    /// Reset window
748    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// =============================================================================
763// PERCENTILE TRACKER (trueno-viz O(1) approximate percentile pattern)
764// =============================================================================
765
766/// Approximate percentile tracker using fixed buckets (trueno-viz pattern)
767///
768/// Provides O(1) approximate percentiles using histogram-based estimation.
769/// Buckets: [0-1, 1-5, 5-10, 10-25, 25-50, 50-100, 100-250, 250-500, 500-1000, 1000+] ms
770#[derive(Debug, Clone)]
771pub struct PercentileTracker {
772    /// Bucket counts
773    buckets: [u64; 10],
774    /// Total count
775    count: u64,
776    /// Bucket boundaries in microseconds
777    boundaries: [u64; 10],
778}
779
780impl Default for PercentileTracker {
781    fn default() -> Self {
782        Self::new()
783    }
784}
785
786impl PercentileTracker {
787    /// Create a new percentile tracker with default latency buckets
788    #[must_use]
789    pub fn new() -> Self {
790        Self {
791            buckets: [0; 10],
792            count: 0,
793            // Boundaries in microseconds: 1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1000ms, infinity
794            boundaries: [
795                1_000,     // 0-1ms
796                5_000,     // 1-5ms
797                10_000,    // 5-10ms
798                25_000,    // 10-25ms
799                50_000,    // 25-50ms
800                100_000,   // 50-100ms
801                250_000,   // 100-250ms
802                500_000,   // 250-500ms
803                1_000_000, // 500-1000ms
804                u64::MAX,  // 1000ms+
805            ],
806        }
807    }
808
809    /// Create with custom bucket boundaries (in microseconds)
810    #[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    /// Record a value in microseconds (O(1))
820    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        // Shouldn't reach here due to u64::MAX boundary
829        self.buckets[9] += 1;
830        self.count += 1;
831    }
832
833    /// Record a value in milliseconds (O(1))
834    pub fn record_ms(&mut self, value_ms: f64) {
835        self.record_us((value_ms * 1000.0) as u64);
836    }
837
838    /// Get approximate percentile value in microseconds (O(1))
839    #[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                // Return midpoint of bucket
852                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; // Estimate for last bucket
856                }
857                return (lower + upper) / 2;
858            }
859        }
860
861        self.boundaries[8] // Return 1s as fallback
862    }
863
864    /// Get approximate percentile value in milliseconds (O(1))
865    #[must_use]
866    pub fn percentile_ms(&self, pct: f64) -> f64 {
867        self.percentile_us(pct) as f64 / 1000.0
868    }
869
870    /// Get p50 (median) in milliseconds
871    #[must_use]
872    pub fn p50_ms(&self) -> f64 {
873        self.percentile_ms(50.0)
874    }
875
876    /// Get p90 in milliseconds
877    #[must_use]
878    pub fn p90_ms(&self) -> f64 {
879        self.percentile_ms(90.0)
880    }
881
882    /// Get p99 in milliseconds
883    #[must_use]
884    pub fn p99_ms(&self) -> f64 {
885        self.percentile_ms(99.0)
886    }
887
888    /// Get total count
889    #[must_use]
890    pub fn count(&self) -> u64 {
891        self.count
892    }
893
894    /// Reset tracker
895    pub fn reset(&mut self) {
896        self.buckets = [0; 10];
897        self.count = 0;
898    }
899}
900
901// =============================================================================
902// STATE TRACKER (trueno-viz O(1) state machine pattern)
903// =============================================================================
904
905/// State transition tracker with history (trueno-viz pattern)
906///
907/// Tracks state transitions and time spent in each state.
908/// Useful for monitoring component lifecycle or connection states.
909#[derive(Debug, Clone)]
910pub struct StateTracker<const N: usize> {
911    /// Current state index (0..N-1)
912    current: usize,
913    /// Time entered current state (microseconds since epoch)
914    entered_us: u64,
915    /// Total time spent in each state (microseconds)
916    durations: [u64; N],
917    /// Transition count for each state
918    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    /// Create a new state tracker starting in state 0
929    #[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; // Initial transition to state 0
939        }
940
941        Self {
942            current: 0,
943            entered_us: now,
944            durations: [0u64; N],
945            transitions,
946        }
947    }
948
949    /// Transition to a new state (O(1))
950    pub fn transition(&mut self, new_state: usize) {
951        if new_state >= N {
952            return; // Invalid state
953        }
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        // Record time in previous state
961        let elapsed = now.saturating_sub(self.entered_us);
962        self.durations[self.current] += elapsed;
963
964        // Transition to new state
965        self.current = new_state;
966        self.entered_us = now;
967        self.transitions[new_state] += 1;
968    }
969
970    /// Get current state (O(1))
971    #[must_use]
972    pub fn current(&self) -> usize {
973        self.current
974    }
975
976    /// Get time in current state in microseconds (O(1))
977    #[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    /// Get total time in state in microseconds (O(1))
987    #[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    /// Get transition count for state (O(1))
1000    #[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    /// Get total transitions (O(N))
1010    #[must_use]
1011    pub fn total_transitions(&self) -> u64 {
1012        self.transitions.iter().sum()
1013    }
1014
1015    /// Reset tracker
1016    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// =============================================================================
1033// CHANGE DETECTOR (trueno-viz O(1) significant change detection)
1034// =============================================================================
1035
1036/// Significant change detector (trueno-viz pattern)
1037///
1038/// Detects when a value has changed significantly from a baseline.
1039/// Uses both absolute and relative thresholds.
1040#[derive(Debug, Clone)]
1041pub struct ChangeDetector {
1042    /// Baseline value
1043    baseline: f64,
1044    /// Absolute change threshold
1045    abs_threshold: f64,
1046    /// Relative change threshold (percentage)
1047    rel_threshold: f64,
1048    /// Last reported value
1049    last_value: f64,
1050    /// Number of changes detected
1051    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    /// Create a new change detector
1062    ///
1063    /// # Arguments
1064    /// * `baseline` - Initial baseline value
1065    /// * `abs_threshold` - Absolute change required to trigger
1066    /// * `rel_threshold` - Relative change (%) required to trigger
1067    #[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    /// Create for percentage monitoring (1% absolute, 5% relative)
1079    #[must_use]
1080    pub fn for_percentage() -> Self {
1081        Self::new(0.0, 1.0, 5.0)
1082    }
1083
1084    /// Create for latency monitoring (1ms absolute, 10% relative)
1085    #[must_use]
1086    pub fn for_latency() -> Self {
1087        Self::new(0.0, 1000.0, 10.0)
1088    }
1089
1090    /// Check if value has changed significantly (O(1))
1091    #[must_use]
1092    pub fn has_changed(&self, value: f64) -> bool {
1093        let abs_diff = (value - self.last_value).abs();
1094
1095        // Check absolute threshold
1096        if abs_diff >= self.abs_threshold {
1097            return true;
1098        }
1099
1100        // Check relative threshold
1101        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    /// Update with new value and return whether it changed significantly (O(1))
1112    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    /// Update baseline to current value (O(1))
1122    pub fn update_baseline(&mut self) {
1123        self.baseline = self.last_value;
1124    }
1125
1126    /// Set baseline to specific value (O(1))
1127    pub fn set_baseline(&mut self, baseline: f64) {
1128        self.baseline = baseline;
1129    }
1130
1131    /// Get current baseline
1132    #[must_use]
1133    pub fn baseline(&self) -> f64 {
1134        self.baseline
1135    }
1136
1137    /// Get last value
1138    #[must_use]
1139    pub fn last_value(&self) -> f64 {
1140        self.last_value
1141    }
1142
1143    /// Get change count
1144    #[must_use]
1145    pub fn change_count(&self) -> u64 {
1146        self.change_count
1147    }
1148
1149    /// Get change from baseline
1150    #[must_use]
1151    pub fn change_from_baseline(&self) -> f64 {
1152        self.last_value - self.baseline
1153    }
1154
1155    /// Get relative change from baseline (percentage)
1156    #[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    /// Reset detector
1166    pub fn reset(&mut self) {
1167        self.last_value = self.baseline;
1168        self.change_count = 0;
1169    }
1170}
1171
1172// =============================================================================
1173// ACCUMULATOR (trueno-viz O(1) overflow-safe accumulation)
1174// =============================================================================
1175
1176/// Overflow-safe accumulator (trueno-viz pattern)
1177///
1178/// Accumulates values with automatic overflow detection and handling.
1179/// Useful for counters that may wrap (network bytes, disk I/O).
1180#[derive(Debug, Clone)]
1181pub struct Accumulator {
1182    /// Current accumulated value
1183    value: u64,
1184    /// Previous raw value (for delta calculation)
1185    prev_raw: u64,
1186    /// Whether we've seen the first value
1187    initialized: bool,
1188    /// Overflow count
1189    overflows: u64,
1190}
1191
1192impl Default for Accumulator {
1193    fn default() -> Self {
1194        Self::new()
1195    }
1196}
1197
1198impl Accumulator {
1199    /// Create a new accumulator
1200    #[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    /// Update with a raw counter value (O(1))
1211    ///
1212    /// Handles counter wraps/overflows automatically.
1213    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        // Calculate delta, handling overflow
1221        let delta = if raw >= self.prev_raw {
1222            raw - self.prev_raw
1223        } else {
1224            // Counter wrapped
1225            self.overflows += 1;
1226            // Assume wrap to 0 from max
1227            (u64::MAX - self.prev_raw) + raw + 1
1228        };
1229
1230        self.value += delta;
1231        self.prev_raw = raw;
1232    }
1233
1234    /// Add a delta value directly (O(1))
1235    pub fn add(&mut self, delta: u64) {
1236        self.value += delta;
1237        self.initialized = true;
1238    }
1239
1240    /// Get accumulated value (O(1))
1241    #[must_use]
1242    pub fn value(&self) -> u64 {
1243        self.value
1244    }
1245
1246    /// Get overflow count (O(1))
1247    #[must_use]
1248    pub fn overflows(&self) -> u64 {
1249        self.overflows
1250    }
1251
1252    /// Check if initialized
1253    #[must_use]
1254    pub fn is_initialized(&self) -> bool {
1255        self.initialized
1256    }
1257
1258    /// Get last raw value
1259    #[must_use]
1260    pub fn last_raw(&self) -> u64 {
1261        self.prev_raw
1262    }
1263
1264    /// Reset accumulator
1265    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// =============================================================================
1274// EVENT COUNTER (trueno-viz O(1) categorized event counting)
1275// =============================================================================
1276
1277/// Categorized event counter (trueno-viz pattern)
1278///
1279/// Counts events by category with O(1) increment and lookup.
1280/// Useful for error categorization, request types, etc.
1281#[derive(Debug, Clone)]
1282pub struct EventCounter<const N: usize> {
1283    /// Counts per category
1284    counts: [u64; N],
1285    /// Total count
1286    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    /// Create a new event counter
1297    #[must_use]
1298    pub fn new() -> Self {
1299        Self {
1300            counts: [0u64; N],
1301            total: 0,
1302        }
1303    }
1304
1305    /// Increment category count (O(1))
1306    pub fn increment(&mut self, category: usize) {
1307        if category < N {
1308            self.counts[category] += 1;
1309            self.total += 1;
1310        }
1311    }
1312
1313    /// Add to category count (O(1))
1314    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    /// Get category count (O(1))
1322    #[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    /// Get total count (O(1))
1332    #[must_use]
1333    pub fn total(&self) -> u64 {
1334        self.total
1335    }
1336
1337    /// Get category percentage (O(1))
1338    #[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    /// Get dominant category (O(N))
1348    #[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    /// Reset all counts
1361    pub fn reset(&mut self) {
1362        self.counts = [0u64; N];
1363        self.total = 0;
1364    }
1365}
1366
1367// =============================================================================
1368// TREND DETECTOR (trueno-viz O(1) trend analysis pattern)
1369// =============================================================================
1370
1371/// Trend detection using linear regression slope (trueno-viz pattern)
1372///
1373/// Detects upward/downward/flat trends using a sliding window approach.
1374/// Uses simplified slope calculation for O(1) updates.
1375#[derive(Debug, Clone)]
1376pub struct TrendDetector {
1377    /// Sum of values
1378    sum: f64,
1379    /// Sum of index * value (for slope)
1380    sum_xy: f64,
1381    /// Current index
1382    index: u64,
1383    /// Number of samples
1384    count: u64,
1385    /// Threshold for trend detection (slope magnitude)
1386    threshold: f64,
1387}
1388
1389impl Default for TrendDetector {
1390    fn default() -> Self {
1391        Self::new(0.1)
1392    }
1393}
1394
1395/// Trend direction
1396#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1397pub enum Trend {
1398    /// Values increasing
1399    Up,
1400    /// Values decreasing
1401    Down,
1402    /// Values stable
1403    Flat,
1404    /// Not enough data
1405    Unknown,
1406}
1407
1408impl TrendDetector {
1409    /// Create a new trend detector
1410    ///
1411    /// # Arguments
1412    /// * `threshold` - Minimum slope magnitude to detect trend
1413    #[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    /// Create for percentage changes (0.5% threshold)
1425    #[must_use]
1426    pub fn for_percentage() -> Self {
1427        Self::new(0.5)
1428    }
1429
1430    /// Create for latency changes (1ms threshold)
1431    #[must_use]
1432    pub fn for_latency() -> Self {
1433        Self::new(1.0)
1434    }
1435
1436    /// Update with a new value (O(1))
1437    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    /// Calculate slope (O(1))
1445    ///
1446    /// Uses least squares linear regression formula:
1447    /// slope = (n * Σxy - Σx * Σy) / (n * Σx² - (Σx)²)
1448    #[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; // Sum of 0..n-1
1456        let sum_x2 = (self.count * (self.count - 1) * (2 * self.count - 1)) as f64 / 6.0;
1457
1458        // Linear regression denominator: n * Σx² - (Σx)²
1459        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    /// Get current trend (O(1))
1469    #[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    /// Check if trending up
1486    #[must_use]
1487    pub fn is_trending_up(&self) -> bool {
1488        self.trend() == Trend::Up
1489    }
1490
1491    /// Check if trending down
1492    #[must_use]
1493    pub fn is_trending_down(&self) -> bool {
1494        self.trend() == Trend::Down
1495    }
1496
1497    /// Get sample count
1498    #[must_use]
1499    pub fn count(&self) -> u64 {
1500        self.count
1501    }
1502
1503    /// Reset detector
1504    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// =============================================================================
1513// ANOMALY DETECTOR (trueno-viz O(1) z-score anomaly detection)
1514// =============================================================================
1515
1516/// Z-score based anomaly detector (trueno-viz pattern)
1517///
1518/// Detects anomalies using running mean/variance and z-score threshold.
1519/// Useful for alerting on unusual values.
1520#[derive(Debug, Clone)]
1521pub struct AnomalyDetector {
1522    /// Running mean
1523    mean: f64,
1524    /// Running M2 (sum of squared differences)
1525    m2: f64,
1526    /// Sample count
1527    count: u64,
1528    /// Z-score threshold for anomaly
1529    threshold: f64,
1530    /// Last value
1531    last_value: f64,
1532    /// Anomaly count
1533    anomaly_count: u64,
1534}
1535
1536impl Default for AnomalyDetector {
1537    fn default() -> Self {
1538        Self::new(3.0)
1539    }
1540}
1541
1542impl AnomalyDetector {
1543    /// Create a new anomaly detector
1544    ///
1545    /// # Arguments
1546    /// * `threshold` - Z-score threshold (typically 2.0-3.0)
1547    #[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    /// Create with 2-sigma threshold (95% confidence)
1560    #[must_use]
1561    pub fn two_sigma() -> Self {
1562        Self::new(2.0)
1563    }
1564
1565    /// Create with 3-sigma threshold (99.7% confidence)
1566    #[must_use]
1567    pub fn three_sigma() -> Self {
1568        Self::new(3.0)
1569    }
1570
1571    /// Update with a new value and return whether it's an anomaly (O(1))
1572    ///
1573    /// Uses Welford's online algorithm for running variance.
1574    pub fn update(&mut self, value: f64) -> bool {
1575        self.last_value = value;
1576        self.count += 1;
1577
1578        // First value can't be an anomaly
1579        if self.count == 1 {
1580            self.mean = value;
1581            return false;
1582        }
1583
1584        // Calculate z-score before updating stats
1585        let is_anomaly = self.is_anomaly(value);
1586        if is_anomaly {
1587            self.anomaly_count += 1;
1588        }
1589
1590        // Update running stats (Welford's algorithm)
1591        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    /// Check if a value would be an anomaly (O(1))
1600    #[must_use]
1601    pub fn is_anomaly(&self, value: f64) -> bool {
1602        if self.count < 10 {
1603            return false; // Need enough samples
1604        }
1605
1606        let z = self.z_score(value);
1607        z.abs() > self.threshold
1608    }
1609
1610    /// Calculate z-score for a value (O(1))
1611    #[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    /// Get running mean (O(1))
1621    #[must_use]
1622    pub fn mean(&self) -> f64 {
1623        self.mean
1624    }
1625
1626    /// Get running variance (O(1))
1627    #[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    /// Get running standard deviation (O(1))
1637    #[must_use]
1638    pub fn std_dev(&self) -> f64 {
1639        self.variance().sqrt()
1640    }
1641
1642    /// Get sample count
1643    #[must_use]
1644    pub fn count(&self) -> u64 {
1645        self.count
1646    }
1647
1648    /// Get anomaly count
1649    #[must_use]
1650    pub fn anomaly_count(&self) -> u64 {
1651        self.anomaly_count
1652    }
1653
1654    /// Get anomaly rate (percentage)
1655    #[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    /// Get threshold
1665    #[must_use]
1666    pub fn threshold(&self) -> f64 {
1667        self.threshold
1668    }
1669
1670    /// Reset detector
1671    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}