Skip to main content

presentar_terminal/perf_trace/
helpers_infra.rs

1// ============================================================================
2// CACHE & LOAD BALANCING HELPERS (trueno-viz parity)
3// ============================================================================
4
5/// O(1) cache statistics tracker.
6///
7/// Tracks cache hits, misses, evictions, and calculates hit rate.
8#[derive(Debug, Clone)]
9pub struct CacheStats {
10    /// Total hits
11    hits: u64,
12    /// Total misses
13    misses: u64,
14    /// Total evictions
15    evictions: u64,
16    /// Total insertions
17    insertions: u64,
18    /// Bytes in cache
19    bytes_cached: u64,
20    /// Capacity in bytes
21    capacity_bytes: u64,
22}
23
24impl Default for CacheStats {
25    fn default() -> Self {
26        Self::new(0)
27    }
28}
29
30impl CacheStats {
31    /// Create with capacity in bytes
32    #[must_use]
33    pub fn new(capacity_bytes: u64) -> Self {
34        Self {
35            hits: 0,
36            misses: 0,
37            evictions: 0,
38            insertions: 0,
39            bytes_cached: 0,
40            capacity_bytes,
41        }
42    }
43
44    /// Create for L1 cache (32KB typical)
45    #[must_use]
46    pub fn for_l1_cache() -> Self {
47        Self::new(32 * 1024)
48    }
49
50    /// Create for L2 cache (256KB typical)
51    #[must_use]
52    pub fn for_l2_cache() -> Self {
53        Self::new(256 * 1024)
54    }
55
56    /// Create for application cache (16MB)
57    #[must_use]
58    pub fn for_app_cache() -> Self {
59        Self::new(16 * 1024 * 1024)
60    }
61
62    /// Record a cache hit
63    pub fn hit(&mut self) {
64        self.hits += 1;
65    }
66
67    /// Record a cache miss
68    pub fn miss(&mut self) {
69        self.misses += 1;
70    }
71
72    /// Record an eviction
73    pub fn evict(&mut self, bytes: u64) {
74        self.evictions += 1;
75        self.bytes_cached = self.bytes_cached.saturating_sub(bytes);
76    }
77
78    /// Record an insertion
79    pub fn insert(&mut self, bytes: u64) {
80        self.insertions += 1;
81        self.bytes_cached += bytes;
82    }
83
84    /// Get hit rate as percentage
85    #[must_use]
86    pub fn hit_rate(&self) -> f64 {
87        let total = self.hits + self.misses;
88        if total == 0 {
89            0.0
90        } else {
91            (self.hits as f64 / total as f64) * 100.0
92        }
93    }
94
95    /// Get miss rate as percentage
96    #[must_use]
97    pub fn miss_rate(&self) -> f64 {
98        100.0 - self.hit_rate()
99    }
100
101    /// Get eviction rate (evictions per insertion)
102    #[must_use]
103    pub fn eviction_rate(&self) -> f64 {
104        if self.insertions == 0 {
105            0.0
106        } else {
107            self.evictions as f64 / self.insertions as f64
108        }
109    }
110
111    /// Get fill percentage
112    #[must_use]
113    pub fn fill_percentage(&self) -> f64 {
114        if self.capacity_bytes == 0 {
115            0.0
116        } else {
117            (self.bytes_cached as f64 / self.capacity_bytes as f64) * 100.0
118        }
119    }
120
121    /// Get total requests
122    #[must_use]
123    pub fn total_requests(&self) -> u64 {
124        self.hits + self.misses
125    }
126
127    /// Check if cache is effective (hit rate > threshold)
128    #[must_use]
129    pub fn is_effective(&self, threshold: f64) -> bool {
130        self.hit_rate() >= threshold
131    }
132
133    /// Reset all counters
134    pub fn reset(&mut self) {
135        self.hits = 0;
136        self.misses = 0;
137        self.evictions = 0;
138        self.insertions = 0;
139        self.bytes_cached = 0;
140    }
141}
142
143/// O(1) Bloom filter for probabilistic membership testing.
144///
145/// Fixed-size bloom filter with configurable hash count.
146/// False positives possible, false negatives impossible.
147#[derive(Debug, Clone)]
148pub struct BloomFilter {
149    /// Bit array (using u64 words)
150    bits: [u64; 16], // 1024 bits
151    /// Number of hash functions
152    hash_count: u32,
153    /// Items added
154    items: u64,
155}
156
157impl Default for BloomFilter {
158    fn default() -> Self {
159        Self::new(3)
160    }
161}
162
163impl BloomFilter {
164    /// Create with number of hash functions
165    #[must_use]
166    pub fn new(hash_count: u32) -> Self {
167        Self {
168            bits: [0; 16],
169            hash_count: hash_count.clamp(1, 10),
170            items: 0,
171        }
172    }
173
174    /// Create optimized for ~100 items (3 hashes)
175    #[must_use]
176    pub fn for_small() -> Self {
177        Self::new(3)
178    }
179
180    /// Create optimized for ~500 items (5 hashes)
181    #[must_use]
182    pub fn for_medium() -> Self {
183        Self::new(5)
184    }
185
186    /// Simple hash function (FNV-1a style)
187    fn hash(&self, value: u64, seed: u32) -> usize {
188        let mut h = value.wrapping_mul(0x517cc1b727220a95);
189        h = h.wrapping_add(seed as u64);
190        h ^= h >> 33;
191        h = h.wrapping_mul(0xff51afd7ed558ccd);
192        (h as usize) % 1024
193    }
194
195    /// Add item to filter
196    pub fn add(&mut self, value: u64) {
197        for i in 0..self.hash_count {
198            let bit_idx = self.hash(value, i);
199            let word_idx = bit_idx / 64;
200            let bit_pos = bit_idx % 64;
201            self.bits[word_idx] |= 1 << bit_pos;
202        }
203        self.items += 1;
204    }
205
206    /// Check if item might be in filter
207    #[must_use]
208    pub fn might_contain(&self, value: u64) -> bool {
209        for i in 0..self.hash_count {
210            let bit_idx = self.hash(value, i);
211            let word_idx = bit_idx / 64;
212            let bit_pos = bit_idx % 64;
213            if self.bits[word_idx] & (1 << bit_pos) == 0 {
214                return false;
215            }
216        }
217        true
218    }
219
220    /// Get number of items added
221    #[must_use]
222    pub fn len(&self) -> u64 {
223        self.items
224    }
225
226    /// Check if empty
227    #[must_use]
228    pub fn is_empty(&self) -> bool {
229        self.items == 0
230    }
231
232    /// Get estimated false positive rate
233    #[must_use]
234    pub fn false_positive_rate(&self) -> f64 {
235        let m = 1024.0; // bits
236        let k = self.hash_count as f64;
237        let n = self.items as f64;
238        if n == 0.0 {
239            return 0.0;
240        }
241        (1.0 - (-k * n / m).exp()).powf(k)
242    }
243
244    /// Get fill percentage (bits set / total bits)
245    #[must_use]
246    pub fn fill_percentage(&self) -> f64 {
247        let set_bits: u32 = self.bits.iter().map(|w| w.count_ones()).sum();
248        (set_bits as f64 / 1024.0) * 100.0
249    }
250
251    /// Reset filter
252    pub fn reset(&mut self) {
253        self.bits = [0; 16];
254        self.items = 0;
255    }
256}
257
258/// O(1) weighted round-robin load balancer.
259///
260/// Distributes load across backends with configurable weights.
261#[derive(Debug, Clone)]
262pub struct LoadBalancer {
263    /// Backend weights
264    weights: [u32; 8],
265    /// Current weights (for WRR algorithm)
266    current: [i32; 8],
267    /// Active backends count
268    active: usize,
269    /// Total requests dispatched
270    dispatched: u64,
271    /// Requests per backend
272    per_backend: [u64; 8],
273}
274
275impl Default for LoadBalancer {
276    fn default() -> Self {
277        Self::new()
278    }
279}
280
281impl LoadBalancer {
282    /// Create empty load balancer
283    #[must_use]
284    pub fn new() -> Self {
285        Self {
286            weights: [0; 8],
287            current: [0; 8],
288            active: 0,
289            dispatched: 0,
290            per_backend: [0; 8],
291        }
292    }
293
294    /// Create with equal weights for n backends
295    #[must_use]
296    pub fn equal_weights(n: usize) -> Self {
297        let mut lb = Self::new();
298        for _ in 0..n.min(8) {
299            lb.add_backend(1);
300        }
301        lb
302    }
303
304    /// Add backend with weight
305    pub fn add_backend(&mut self, weight: u32) {
306        if self.active < 8 {
307            self.weights[self.active] = weight.max(1);
308            self.current[self.active] = 0;
309            self.active += 1;
310        }
311    }
312
313    /// Select next backend (weighted round-robin)
314    #[must_use]
315    pub fn select_backend(&mut self) -> Option<usize> {
316        if self.active == 0 {
317            return None;
318        }
319
320        // Weighted round-robin: select backend with highest current weight
321        let total_weight: i32 = self.weights[..self.active].iter().map(|&w| w as i32).sum();
322
323        // Add weights to current
324        for i in 0..self.active {
325            self.current[i] += self.weights[i] as i32;
326        }
327
328        // Find max current weight
329        let mut max_idx = 0;
330        let mut max_weight = self.current[0];
331        for i in 1..self.active {
332            if self.current[i] > max_weight {
333                max_weight = self.current[i];
334                max_idx = i;
335            }
336        }
337
338        // Subtract total weight from selected
339        self.current[max_idx] -= total_weight;
340        self.dispatched += 1;
341        self.per_backend[max_idx] += 1;
342
343        Some(max_idx)
344    }
345
346    /// Get distribution percentage for backend
347    #[must_use]
348    pub fn distribution(&self, backend: usize) -> f64 {
349        if self.dispatched == 0 || backend >= self.active {
350            0.0
351        } else {
352            (self.per_backend[backend] as f64 / self.dispatched as f64) * 100.0
353        }
354    }
355
356    /// Get total dispatched
357    #[must_use]
358    pub fn total_dispatched(&self) -> u64 {
359        self.dispatched
360    }
361
362    /// Get active backend count
363    #[must_use]
364    pub fn backend_count(&self) -> usize {
365        self.active
366    }
367
368    /// Check if load is balanced (within threshold)
369    #[must_use]
370    pub fn is_balanced(&self, threshold: f64) -> bool {
371        if self.active <= 1 || self.dispatched < 10 {
372            return true;
373        }
374        let avg = self.dispatched as f64 / self.active as f64;
375        for i in 0..self.active {
376            let deviation = ((self.per_backend[i] as f64 - avg) / avg).abs() * 100.0;
377            if deviation > threshold {
378                return false;
379            }
380        }
381        true
382    }
383
384    /// Reset all counters
385    pub fn reset(&mut self) {
386        self.current = [0; 8];
387        self.dispatched = 0;
388        self.per_backend = [0; 8];
389    }
390}
391
392/// O(1) token bucket with burst tracking.
393///
394/// Enhanced token bucket that tracks burst patterns.
395#[derive(Debug, Clone)]
396pub struct BurstTracker {
397    /// Current tokens
398    tokens: f64,
399    /// Bucket capacity
400    capacity: f64,
401    /// Refill rate (tokens per second)
402    refill_rate: f64,
403    /// Last update timestamp (us)
404    last_update_us: u64,
405    /// Current burst count
406    burst_count: u64,
407    /// Max burst seen
408    max_burst: u64,
409    /// Total bursts
410    total_bursts: u64,
411}
412
413impl Default for BurstTracker {
414    fn default() -> Self {
415        Self::new(100.0, 10.0)
416    }
417}
418
419impl BurstTracker {
420    /// Create with capacity and refill rate
421    #[must_use]
422    pub fn new(capacity: f64, refill_rate: f64) -> Self {
423        Self {
424            tokens: capacity,
425            capacity: capacity.max(1.0),
426            refill_rate: refill_rate.max(0.1),
427            last_update_us: 0,
428            burst_count: 0,
429            max_burst: 0,
430            total_bursts: 0,
431        }
432    }
433
434    /// Create for API rate limiting
435    #[must_use]
436    pub fn for_api() -> Self {
437        Self::new(100.0, 50.0)
438    }
439
440    /// Create for network throttling
441    #[must_use]
442    pub fn for_network() -> Self {
443        Self::new(1000.0, 100.0)
444    }
445
446    /// Consume tokens, returns true if allowed
447    pub fn consume(&mut self, tokens: f64, now_us: u64) -> bool {
448        self.refill(now_us);
449
450        if tokens <= self.tokens {
451            self.tokens -= tokens;
452            self.burst_count += 1;
453            if self.burst_count > self.max_burst {
454                self.max_burst = self.burst_count;
455            }
456            true
457        } else {
458            // End of burst
459            if self.burst_count > 0 {
460                self.total_bursts += 1;
461            }
462            self.burst_count = 0;
463            false
464        }
465    }
466
467    fn refill(&mut self, now_us: u64) {
468        if self.last_update_us == 0 {
469            self.last_update_us = now_us;
470            return;
471        }
472        let elapsed_s = (now_us.saturating_sub(self.last_update_us)) as f64 / 1_000_000.0;
473        let refill = elapsed_s * self.refill_rate;
474        self.tokens = (self.tokens + refill).min(self.capacity);
475        self.last_update_us = now_us;
476    }
477
478    /// Get current token count
479    #[must_use]
480    pub fn tokens(&self) -> f64 {
481        self.tokens
482    }
483
484    /// Get fill percentage
485    #[must_use]
486    pub fn fill_percentage(&self) -> f64 {
487        (self.tokens / self.capacity) * 100.0
488    }
489
490    /// Get max burst size seen
491    #[must_use]
492    pub fn max_burst(&self) -> u64 {
493        self.max_burst
494    }
495
496    /// Get total bursts
497    #[must_use]
498    pub fn total_bursts(&self) -> u64 {
499        self.total_bursts
500    }
501
502    /// Get average burst size
503    #[must_use]
504    pub fn avg_burst(&self) -> f64 {
505        if self.total_bursts == 0 {
506            0.0
507        } else {
508            self.max_burst as f64 // Approximation
509        }
510    }
511
512    /// Reset tracker
513    pub fn reset(&mut self) {
514        self.tokens = self.capacity;
515        self.burst_count = 0;
516        self.max_burst = 0;
517        self.total_bursts = 0;
518        self.last_update_us = 0;
519    }
520}
521
522// ============================================================================
523// TopKTracker - Fixed-size top-K value tracker (O(1) amortized insertion)
524// ============================================================================
525
526/// O(1) amortized top-K value tracker.
527/// Uses a fixed-size array with insertion sort for small K values.
528#[derive(Debug, Clone)]
529pub struct TopKTracker {
530    values: [f64; 32],
531    count: usize,
532    k: usize,
533}
534
535impl Default for TopKTracker {
536    fn default() -> Self {
537        Self::new(10)
538    }
539}
540
541impl TopKTracker {
542    /// Create new top-K tracker
543    #[must_use]
544    pub fn new(k: usize) -> Self {
545        Self {
546            values: [f64::NEG_INFINITY; 32],
547            count: 0,
548            k: k.min(32),
549        }
550    }
551
552    /// Create for metrics (top 10)
553    #[must_use]
554    pub fn for_metrics() -> Self {
555        Self::new(10)
556    }
557
558    /// Create for processes (top 20)
559    #[must_use]
560    pub fn for_processes() -> Self {
561        Self::new(20)
562    }
563
564    /// Add value (O(k) insertion)
565    pub fn add(&mut self, value: f64) {
566        if self.count < self.k {
567            // Not full yet, insert in sorted order
568            let mut i = self.count;
569            while i > 0 && self.values[i - 1] < value {
570                self.values[i] = self.values[i - 1];
571                i -= 1;
572            }
573            self.values[i] = value;
574            self.count += 1;
575        } else if value > self.values[self.k - 1] {
576            // Replace minimum if value is larger
577            let mut i = self.k - 1;
578            while i > 0 && self.values[i - 1] < value {
579                self.values[i] = self.values[i - 1];
580                i -= 1;
581            }
582            self.values[i] = value;
583        }
584    }
585
586    /// Get top-K values (sorted descending)
587    #[must_use]
588    pub fn top(&self) -> &[f64] {
589        &self.values[..self.count]
590    }
591
592    /// Get K value
593    #[must_use]
594    pub fn k(&self) -> usize {
595        self.k
596    }
597
598    /// Get count of tracked values
599    #[must_use]
600    pub fn count(&self) -> usize {
601        self.count
602    }
603
604    /// Get minimum value in top-K
605    #[must_use]
606    pub fn minimum(&self) -> Option<f64> {
607        if self.count > 0 {
608            Some(self.values[self.count - 1])
609        } else {
610            None
611        }
612    }
613
614    /// Get maximum value (always at index 0)
615    #[must_use]
616    pub fn maximum(&self) -> Option<f64> {
617        if self.count > 0 {
618            Some(self.values[0])
619        } else {
620            None
621        }
622    }
623
624    /// Reset tracker
625    pub fn reset(&mut self) {
626        self.values = [f64::NEG_INFINITY; 32];
627        self.count = 0;
628    }
629}
630
631// ============================================================================
632// QuotaTracker - Resource quota tracking
633// ============================================================================
634
635/// O(1) resource quota tracker.
636/// Tracks usage against a limit with percentage and exhaustion checks.
637#[derive(Debug, Clone)]
638pub struct QuotaTracker {
639    limit: u64,
640    used: u64,
641    peak_usage: u64,
642}
643
644impl Default for QuotaTracker {
645    fn default() -> Self {
646        Self::new(1000)
647    }
648}
649
650impl QuotaTracker {
651    /// Create with limit
652    #[must_use]
653    pub fn new(limit: u64) -> Self {
654        Self {
655            limit: limit.max(1),
656            used: 0,
657            peak_usage: 0,
658        }
659    }
660
661    /// Create for API daily limit (10K requests)
662    #[must_use]
663    pub fn for_api_daily() -> Self {
664        Self::new(10000)
665    }
666
667    /// Create for storage limit (100 GB)
668    #[must_use]
669    pub fn for_storage_gb() -> Self {
670        Self::new(100)
671    }
672
673    /// Use quota, returns false if would exceed
674    pub fn use_quota(&mut self, amount: u64) -> bool {
675        if self.used + amount > self.limit {
676            false
677        } else {
678            self.used += amount;
679            if self.used > self.peak_usage {
680                self.peak_usage = self.used;
681            }
682            true
683        }
684    }
685
686    /// Release quota
687    pub fn release(&mut self, amount: u64) {
688        self.used = self.used.saturating_sub(amount);
689    }
690
691    /// Get limit
692    #[must_use]
693    pub fn limit(&self) -> u64 {
694        self.limit
695    }
696
697    /// Get remaining quota
698    #[must_use]
699    pub fn remaining(&self) -> u64 {
700        self.limit.saturating_sub(self.used)
701    }
702
703    /// Get usage percentage
704    #[must_use]
705    pub fn usage_percentage(&self) -> f64 {
706        (self.used as f64 / self.limit as f64) * 100.0
707    }
708
709    /// Check if exhausted
710    #[must_use]
711    pub fn is_exhausted(&self) -> bool {
712        self.used >= self.limit
713    }
714
715    /// Get peak usage
716    #[must_use]
717    pub fn peak_usage(&self) -> u64 {
718        self.peak_usage
719    }
720
721    /// Reset tracker
722    pub fn reset(&mut self) {
723        self.used = 0;
724        self.peak_usage = 0;
725    }
726}
727
728// ============================================================================
729// FrequencyCounter - Categorical frequency tracking
730// ============================================================================
731
732/// O(1) categorical frequency counter.
733/// Tracks occurrence counts and calculates frequencies for up to 16 categories.
734#[derive(Debug, Clone)]
735pub struct FrequencyCounter {
736    counts: [u64; 16],
737    total: u64,
738}
739
740impl Default for FrequencyCounter {
741    fn default() -> Self {
742        Self::new()
743    }
744}
745
746impl FrequencyCounter {
747    /// Create new counter
748    #[must_use]
749    pub fn new() -> Self {
750        Self {
751            counts: [0; 16],
752            total: 0,
753        }
754    }
755
756    /// Increment category count
757    pub fn increment(&mut self, category: usize) {
758        if category < 16 {
759            self.counts[category] += 1;
760            self.total += 1;
761        }
762    }
763
764    /// Add multiple to category
765    pub fn add(&mut self, category: usize, count: u64) {
766        if category < 16 {
767            self.counts[category] += count;
768            self.total += count;
769        }
770    }
771
772    /// Get count for category
773    #[must_use]
774    pub fn count(&self, category: usize) -> u64 {
775        if category < 16 {
776            self.counts[category]
777        } else {
778            0
779        }
780    }
781
782    /// Get frequency percentage for category
783    #[must_use]
784    pub fn frequency(&self, category: usize) -> f64 {
785        if self.total == 0 || category >= 16 {
786            0.0
787        } else {
788            (self.counts[category] as f64 / self.total as f64) * 100.0
789        }
790    }
791
792    /// Get total count
793    #[must_use]
794    pub fn total(&self) -> u64 {
795        self.total
796    }
797
798    /// Get most frequent category
799    #[must_use]
800    pub fn most_frequent(&self) -> Option<usize> {
801        if self.total == 0 {
802            return None;
803        }
804        let mut max_idx = 0;
805        let mut max_count = self.counts[0];
806        for i in 1..16 {
807            if self.counts[i] > max_count {
808                max_count = self.counts[i];
809                max_idx = i;
810            }
811        }
812        Some(max_idx)
813    }
814
815    /// Get number of non-zero categories
816    #[must_use]
817    pub fn non_zero_count(&self) -> usize {
818        self.counts.iter().filter(|&&c| c > 0).count()
819    }
820
821    /// Calculate Shannon entropy (normalized 0-1)
822    #[must_use]
823    pub fn entropy(&self) -> f64 {
824        if self.total == 0 {
825            return 0.0;
826        }
827        let mut entropy = 0.0;
828        for &count in &self.counts {
829            if count > 0 {
830                let p = count as f64 / self.total as f64;
831                entropy -= p * p.log2();
832            }
833        }
834        // Normalize by max entropy (log2(16) = 4)
835        entropy / 4.0
836    }
837
838    /// Reset counter
839    pub fn reset(&mut self) {
840        self.counts = [0; 16];
841        self.total = 0;
842    }
843}
844
845// ============================================================================
846// MovingRange - Moving min/max range tracking for volatility
847// ============================================================================
848
849/// O(1) moving range tracker for volatility analysis.
850/// Maintains min/max over a sliding window for range and volatility metrics.
851#[derive(Debug, Clone)]
852pub struct MovingRange {
853    values: [f64; 128],
854    window_size: usize,
855    head: usize,
856    count: usize,
857    current_min: f64,
858    current_max: f64,
859}
860
861impl Default for MovingRange {
862    fn default() -> Self {
863        Self::new(10)
864    }
865}
866
867impl MovingRange {
868    /// Create with window size
869    #[must_use]
870    pub fn new(window_size: usize) -> Self {
871        Self {
872            values: [0.0; 128],
873            window_size: window_size.min(128),
874            head: 0,
875            count: 0,
876            current_min: f64::INFINITY,
877            current_max: f64::NEG_INFINITY,
878        }
879    }
880
881    /// Create for price volatility (20 samples)
882    #[must_use]
883    pub fn for_prices() -> Self {
884        Self::new(20)
885    }
886
887    /// Create for latency volatility (100 samples)
888    #[must_use]
889    pub fn for_latency() -> Self {
890        Self::new(100)
891    }
892
893    /// Add value to window
894    pub fn add(&mut self, value: f64) {
895        let idx = self.head;
896        self.values[idx] = value;
897        self.head = (self.head + 1) % self.window_size;
898        if self.count < self.window_size {
899            self.count += 1;
900        }
901        self.recalculate_minmax();
902    }
903
904    fn recalculate_minmax(&mut self) {
905        self.current_min = f64::INFINITY;
906        self.current_max = f64::NEG_INFINITY;
907        for i in 0..self.count {
908            let v = self.values[i];
909            if v < self.current_min {
910                self.current_min = v;
911            }
912            if v > self.current_max {
913                self.current_max = v;
914            }
915        }
916    }
917
918    /// Get window size
919    #[must_use]
920    pub fn window_size(&self) -> usize {
921        self.window_size
922    }
923
924    /// Get current count
925    #[must_use]
926    pub fn count(&self) -> usize {
927        self.count
928    }
929
930    /// Get minimum value
931    #[must_use]
932    pub fn min(&self) -> Option<f64> {
933        if self.count > 0 {
934            Some(self.current_min)
935        } else {
936            None
937        }
938    }
939
940    /// Get maximum value
941    #[must_use]
942    pub fn max(&self) -> Option<f64> {
943        if self.count > 0 {
944            Some(self.current_max)
945        } else {
946            None
947        }
948    }
949
950    /// Get range (max - min)
951    #[must_use]
952    pub fn range(&self) -> f64 {
953        if self.count > 0 {
954            self.current_max - self.current_min
955        } else {
956            0.0
957        }
958    }
959
960    /// Get mid-range ((max + min) / 2)
961    #[must_use]
962    pub fn midrange(&self) -> f64 {
963        if self.count > 0 {
964            (self.current_max + self.current_min) / 2.0
965        } else {
966            0.0
967        }
968    }
969
970    /// Get volatility (range / midrange * 100)
971    #[must_use]
972    pub fn volatility(&self) -> f64 {
973        let mid = self.midrange();
974        if mid.abs() < 0.0001 {
975            0.0
976        } else {
977            (self.range() / mid) * 100.0
978        }
979    }
980
981    /// Reset tracker
982    pub fn reset(&mut self) {
983        self.values = [0.0; 128];
984        self.head = 0;
985        self.count = 0;
986        self.current_min = f64::INFINITY;
987        self.current_max = f64::NEG_INFINITY;
988    }
989}
990
991// ============================================================================
992// TimeoutTracker - Operation timeout tracking
993// ============================================================================
994
995/// O(1) operation timeout tracker.
996/// Tracks successful and timed-out operations with configurable timeout threshold.
997#[derive(Debug, Clone)]
998pub struct TimeoutTracker {
999    timeout_us: u64,
1000    total: u64,
1001    timed_out: u64,
1002    last_duration_us: u64,
1003    max_duration_us: u64,
1004}
1005
1006impl Default for TimeoutTracker {
1007    fn default() -> Self {
1008        Self::new(1_000_000) // 1 second default
1009    }
1010}
1011
1012impl TimeoutTracker {
1013    /// Create with timeout threshold in microseconds
1014    #[must_use]
1015    pub fn new(timeout_us: u64) -> Self {
1016        Self {
1017            timeout_us: timeout_us.max(1),
1018            total: 0,
1019            timed_out: 0,
1020            last_duration_us: 0,
1021            max_duration_us: 0,
1022        }
1023    }
1024
1025    /// Create for network operations (5s timeout)
1026    #[must_use]
1027    pub fn for_network() -> Self {
1028        Self::new(5_000_000)
1029    }
1030
1031    /// Create for database operations (30s timeout)
1032    #[must_use]
1033    pub fn for_database() -> Self {
1034        Self::new(30_000_000)
1035    }
1036
1037    /// Create for fast operations (100ms timeout)
1038    #[must_use]
1039    pub fn for_fast() -> Self {
1040        Self::new(100_000)
1041    }
1042
1043    /// Record operation completion
1044    pub fn record(&mut self, duration_us: u64) {
1045        self.total += 1;
1046        self.last_duration_us = duration_us;
1047        if duration_us > self.max_duration_us {
1048            self.max_duration_us = duration_us;
1049        }
1050        if duration_us > self.timeout_us {
1051            self.timed_out += 1;
1052        }
1053    }
1054
1055    /// Get total operations
1056    #[must_use]
1057    pub fn total(&self) -> u64 {
1058        self.total
1059    }
1060
1061    /// Get timed out count
1062    #[must_use]
1063    pub fn timed_out(&self) -> u64 {
1064        self.timed_out
1065    }
1066
1067    /// Get timeout rate as percentage
1068    #[must_use]
1069    pub fn timeout_rate(&self) -> f64 {
1070        if self.total == 0 {
1071            0.0
1072        } else {
1073            (self.timed_out as f64 / self.total as f64) * 100.0
1074        }
1075    }
1076
1077    /// Get success rate as percentage
1078    #[must_use]
1079    pub fn success_rate(&self) -> f64 {
1080        100.0 - self.timeout_rate()
1081    }
1082
1083    /// Check if timeout rate is acceptable
1084    #[must_use]
1085    pub fn is_healthy(&self, max_timeout_rate: f64) -> bool {
1086        self.timeout_rate() <= max_timeout_rate
1087    }
1088
1089    /// Get max duration seen
1090    #[must_use]
1091    pub fn max_duration_us(&self) -> u64 {
1092        self.max_duration_us
1093    }
1094
1095    /// Get timeout threshold
1096    #[must_use]
1097    pub fn timeout_threshold_us(&self) -> u64 {
1098        self.timeout_us
1099    }
1100
1101    /// Reset tracker
1102    pub fn reset(&mut self) {
1103        self.total = 0;
1104        self.timed_out = 0;
1105        self.last_duration_us = 0;
1106        self.max_duration_us = 0;
1107    }
1108}
1109
1110// ============================================================================
1111// RetryTracker - Retry attempt tracking with backoff state
1112// ============================================================================
1113
1114/// O(1) retry tracking with exponential backoff state.
1115/// Tracks retry attempts, success after retry, and calculates next retry delay.
1116#[derive(Debug, Clone)]
1117pub struct RetryTracker {
1118    max_retries: u32,
1119    base_delay_ms: u64,
1120    max_delay_ms: u64,
1121    total_attempts: u64,
1122    total_retries: u64,
1123    successful_retries: u64,
1124    current_retry: u32,
1125}
1126
1127impl Default for RetryTracker {
1128    fn default() -> Self {
1129        Self::new(3, 100, 10000)
1130    }
1131}
1132
1133impl RetryTracker {
1134    /// Create with max retries, base delay, and max delay in ms
1135    #[must_use]
1136    pub fn new(max_retries: u32, base_delay_ms: u64, max_delay_ms: u64) -> Self {
1137        Self {
1138            max_retries,
1139            base_delay_ms: base_delay_ms.max(1),
1140            max_delay_ms: max_delay_ms.max(base_delay_ms),
1141            total_attempts: 0,
1142            total_retries: 0,
1143            successful_retries: 0,
1144            current_retry: 0,
1145        }
1146    }
1147
1148    /// Create for API retries (3 retries, 100ms base, 10s max)
1149    #[must_use]
1150    pub fn for_api() -> Self {
1151        Self::new(3, 100, 10000)
1152    }
1153
1154    /// Create for network retries (5 retries, 1s base, 30s max)
1155    #[must_use]
1156    pub fn for_network() -> Self {
1157        Self::new(5, 1000, 30000)
1158    }
1159
1160    /// Record attempt start
1161    pub fn attempt(&mut self) {
1162        self.total_attempts += 1;
1163    }
1164
1165    /// Record retry (failed attempt, will retry)
1166    pub fn retry(&mut self) {
1167        self.total_retries += 1;
1168        if self.current_retry < self.max_retries {
1169            self.current_retry += 1;
1170        }
1171    }
1172
1173    /// Record success (resets current retry count)
1174    pub fn success(&mut self) {
1175        if self.current_retry > 0 {
1176            self.successful_retries += 1;
1177        }
1178        self.current_retry = 0;
1179    }
1180
1181    /// Get next retry delay in ms (exponential backoff)
1182    #[must_use]
1183    pub fn next_delay_ms(&self) -> u64 {
1184        let delay = self.base_delay_ms * (1 << self.current_retry);
1185        delay.min(self.max_delay_ms)
1186    }
1187
1188    /// Check if retries exhausted
1189    #[must_use]
1190    pub fn retries_exhausted(&self) -> bool {
1191        self.current_retry >= self.max_retries
1192    }
1193
1194    /// Get retry rate as percentage
1195    #[must_use]
1196    pub fn retry_rate(&self) -> f64 {
1197        if self.total_attempts == 0 {
1198            0.0
1199        } else {
1200            (self.total_retries as f64 / self.total_attempts as f64) * 100.0
1201        }
1202    }
1203
1204    /// Get successful retry rate
1205    #[must_use]
1206    pub fn successful_retry_rate(&self) -> f64 {
1207        if self.total_retries == 0 {
1208            0.0
1209        } else {
1210            (self.successful_retries as f64 / self.total_retries as f64) * 100.0
1211        }
1212    }
1213
1214    /// Get current retry count
1215    #[must_use]
1216    pub fn current_retry(&self) -> u32 {
1217        self.current_retry
1218    }
1219
1220    /// Reset tracker
1221    pub fn reset(&mut self) {
1222        self.total_attempts = 0;
1223        self.total_retries = 0;
1224        self.successful_retries = 0;
1225        self.current_retry = 0;
1226    }
1227}
1228
1229// ============================================================================
1230// ScheduleSlot - Time-based slot scheduling
1231// ============================================================================
1232
1233/// O(1) time-based slot scheduler.
1234/// Divides time into slots and tracks which slot is currently active.
1235#[derive(Debug, Clone)]
1236pub struct ScheduleSlot {
1237    slot_duration_us: u64,
1238    num_slots: usize,
1239    current_slot: usize,
1240    slot_start_us: u64,
1241    executions_per_slot: [u64; 16],
1242}
1243
1244impl Default for ScheduleSlot {
1245    fn default() -> Self {
1246        Self::new(1_000_000, 10) // 1 second slots, 10 slots
1247    }
1248}
1249
1250impl ScheduleSlot {
1251    /// Create with slot duration in microseconds and number of slots
1252    #[must_use]
1253    pub fn new(slot_duration_us: u64, num_slots: usize) -> Self {
1254        Self {
1255            slot_duration_us: slot_duration_us.max(1),
1256            num_slots: num_slots.min(16).max(1),
1257            current_slot: 0,
1258            slot_start_us: 0,
1259            executions_per_slot: [0; 16],
1260        }
1261    }
1262
1263    /// Create for round-robin scheduling (1 second slots, 10 slots)
1264    #[must_use]
1265    pub fn for_round_robin() -> Self {
1266        Self::new(1_000_000, 10)
1267    }
1268
1269    /// Create for minute-based scheduling (1 minute slots, 5 slots)
1270    #[must_use]
1271    pub fn for_minute() -> Self {
1272        Self::new(60_000_000, 5)
1273    }
1274
1275    /// Update slot based on current time
1276    pub fn update(&mut self, now_us: u64) {
1277        if self.slot_start_us == 0 {
1278            self.slot_start_us = now_us;
1279            return;
1280        }
1281
1282        let elapsed = now_us.saturating_sub(self.slot_start_us);
1283        let slots_passed = (elapsed / self.slot_duration_us) as usize;
1284
1285        if slots_passed > 0 {
1286            self.current_slot = (self.current_slot + slots_passed) % self.num_slots;
1287            self.slot_start_us = now_us;
1288        }
1289    }
1290
1291    /// Record execution in current slot
1292    pub fn execute(&mut self, now_us: u64) {
1293        self.update(now_us);
1294        if self.current_slot < 16 {
1295            self.executions_per_slot[self.current_slot] += 1;
1296        }
1297    }
1298
1299    /// Get current slot
1300    #[must_use]
1301    pub fn current_slot(&self) -> usize {
1302        self.current_slot
1303    }
1304
1305    /// Get number of slots
1306    #[must_use]
1307    pub fn num_slots(&self) -> usize {
1308        self.num_slots
1309    }
1310
1311    /// Get executions for a slot
1312    #[must_use]
1313    pub fn executions(&self, slot: usize) -> u64 {
1314        if slot < 16 {
1315            self.executions_per_slot[slot]
1316        } else {
1317            0
1318        }
1319    }
1320
1321    /// Get total executions across all slots
1322    #[must_use]
1323    pub fn total_executions(&self) -> u64 {
1324        self.executions_per_slot[..self.num_slots].iter().sum()
1325    }
1326
1327    /// Check if slots are evenly distributed (within threshold %)
1328    #[must_use]
1329    pub fn is_balanced(&self, threshold: f64) -> bool {
1330        let total = self.total_executions();
1331        if total == 0 {
1332            return true;
1333        }
1334        let expected = total as f64 / self.num_slots as f64;
1335        for i in 0..self.num_slots {
1336            let diff = (self.executions_per_slot[i] as f64 - expected).abs();
1337            if diff / expected * 100.0 > threshold {
1338                return false;
1339            }
1340        }
1341        true
1342    }
1343
1344    /// Reset tracker
1345    pub fn reset(&mut self) {
1346        self.current_slot = 0;
1347        self.slot_start_us = 0;
1348        self.executions_per_slot = [0; 16];
1349    }
1350}
1351
1352// ============================================================================
1353// CooldownTimer - Cooldown period tracking
1354// ============================================================================
1355
1356/// O(1) cooldown timer for rate limiting actions.
1357/// Tracks when an action can next be performed based on cooldown period.
1358#[derive(Debug, Clone)]
1359pub struct CooldownTimer {
1360    cooldown_us: u64,
1361    last_action_us: u64,
1362    total_actions: u64,
1363    blocked_attempts: u64,
1364}
1365
1366impl Default for CooldownTimer {
1367    fn default() -> Self {
1368        Self::new(1_000_000) // 1 second cooldown
1369    }
1370}
1371
1372impl CooldownTimer {
1373    /// Create with cooldown period in microseconds
1374    #[must_use]
1375    pub fn new(cooldown_us: u64) -> Self {
1376        Self {
1377            cooldown_us: cooldown_us.max(1),
1378            last_action_us: 0,
1379            total_actions: 0,
1380            blocked_attempts: 0,
1381        }
1382    }
1383
1384    /// Create for fast cooldown (100ms)
1385    #[must_use]
1386    pub fn for_fast() -> Self {
1387        Self::new(100_000)
1388    }
1389
1390    /// Create for normal cooldown (1 second)
1391    #[must_use]
1392    pub fn for_normal() -> Self {
1393        Self::new(1_000_000)
1394    }
1395
1396    /// Create for slow cooldown (10 seconds)
1397    #[must_use]
1398    pub fn for_slow() -> Self {
1399        Self::new(10_000_000)
1400    }
1401
1402    /// Check if action is ready (cooldown expired)
1403    #[must_use]
1404    pub fn is_ready(&self, now_us: u64) -> bool {
1405        if self.last_action_us == 0 {
1406            return true;
1407        }
1408        now_us.saturating_sub(self.last_action_us) >= self.cooldown_us
1409    }
1410
1411    /// Try to perform action, returns true if allowed
1412    pub fn try_action(&mut self, now_us: u64) -> bool {
1413        if self.is_ready(now_us) {
1414            self.last_action_us = now_us;
1415            self.total_actions += 1;
1416            true
1417        } else {
1418            self.blocked_attempts += 1;
1419            false
1420        }
1421    }
1422
1423    /// Force action (ignores cooldown)
1424    pub fn force_action(&mut self, now_us: u64) {
1425        self.last_action_us = now_us;
1426        self.total_actions += 1;
1427    }
1428
1429    /// Get remaining cooldown time in microseconds
1430    #[must_use]
1431    pub fn remaining_us(&self, now_us: u64) -> u64 {
1432        if self.is_ready(now_us) {
1433            0
1434        } else {
1435            self.cooldown_us
1436                .saturating_sub(now_us.saturating_sub(self.last_action_us))
1437        }
1438    }
1439
1440    /// Get cooldown period
1441    #[must_use]
1442    pub fn cooldown_us(&self) -> u64 {
1443        self.cooldown_us
1444    }
1445
1446    /// Get total actions performed
1447    #[must_use]
1448    pub fn total_actions(&self) -> u64 {
1449        self.total_actions
1450    }
1451
1452    /// Get blocked attempts
1453    #[must_use]
1454    pub fn blocked_attempts(&self) -> u64 {
1455        self.blocked_attempts
1456    }
1457
1458    /// Get block rate as percentage
1459    #[must_use]
1460    pub fn block_rate(&self) -> f64 {
1461        let total = self.total_actions + self.blocked_attempts;
1462        if total == 0 {
1463            0.0
1464        } else {
1465            (self.blocked_attempts as f64 / total as f64) * 100.0
1466        }
1467    }
1468
1469    /// Reset timer
1470    pub fn reset(&mut self) {
1471        self.last_action_us = 0;
1472        self.total_actions = 0;
1473        self.blocked_attempts = 0;
1474    }
1475}
1476
1477// ============================================================================
1478// BackpressureMonitor - Track backpressure signals
1479// ============================================================================
1480
1481/// O(1) backpressure monitoring.
1482/// Tracks when downstream systems signal overload and calculates pressure rates.
1483#[derive(Debug, Clone)]
1484pub struct BackpressureMonitor {
1485    signals: u64,
1486    total_ops: u64,
1487    consecutive: u32,
1488    max_consecutive: u32,
1489    last_signal_us: u64,
1490}
1491
1492impl Default for BackpressureMonitor {
1493    fn default() -> Self {
1494        Self::new()
1495    }
1496}
1497
1498impl BackpressureMonitor {
1499    /// Create new monitor
1500    #[must_use]
1501    pub fn new() -> Self {
1502        Self {
1503            signals: 0,
1504            total_ops: 0,
1505            consecutive: 0,
1506            max_consecutive: 0,
1507            last_signal_us: 0,
1508        }
1509    }
1510
1511    /// Record successful operation (no backpressure)
1512    pub fn success(&mut self) {
1513        self.total_ops += 1;
1514        self.consecutive = 0;
1515    }
1516
1517    /// Record backpressure signal
1518    pub fn signal(&mut self, now_us: u64) {
1519        self.signals += 1;
1520        self.total_ops += 1;
1521        self.consecutive += 1;
1522        self.last_signal_us = now_us;
1523        if self.consecutive > self.max_consecutive {
1524            self.max_consecutive = self.consecutive;
1525        }
1526    }
1527
1528    /// Get backpressure rate as percentage
1529    #[must_use]
1530    pub fn pressure_rate(&self) -> f64 {
1531        if self.total_ops == 0 {
1532            0.0
1533        } else {
1534            (self.signals as f64 / self.total_ops as f64) * 100.0
1535        }
1536    }
1537
1538    /// Check if currently under pressure (consecutive signals)
1539    #[must_use]
1540    pub fn is_under_pressure(&self, threshold: u32) -> bool {
1541        self.consecutive >= threshold
1542    }
1543
1544    /// Get consecutive signal count
1545    #[must_use]
1546    pub fn consecutive(&self) -> u32 {
1547        self.consecutive
1548    }
1549
1550    /// Get max consecutive signals
1551    #[must_use]
1552    pub fn max_consecutive(&self) -> u32 {
1553        self.max_consecutive
1554    }
1555
1556    /// Get total signals
1557    #[must_use]
1558    pub fn total_signals(&self) -> u64 {
1559        self.signals
1560    }
1561
1562    /// Check if healthy (below threshold)
1563    #[must_use]
1564    pub fn is_healthy(&self, max_rate: f64) -> bool {
1565        self.pressure_rate() <= max_rate
1566    }
1567
1568    /// Reset monitor
1569    pub fn reset(&mut self) {
1570        self.signals = 0;
1571        self.total_ops = 0;
1572        self.consecutive = 0;
1573        self.max_consecutive = 0;
1574        self.last_signal_us = 0;
1575    }
1576}
1577
1578// ============================================================================
1579// CapacityPlanner - Track capacity utilization for planning
1580// ============================================================================
1581
1582/// O(1) capacity planning tracker.
1583/// Monitors utilization over time and predicts when capacity will be exhausted.
1584#[derive(Debug, Clone)]
1585pub struct CapacityPlanner {
1586    capacity: u64,
1587    current: u64,
1588    peak: u64,
1589    samples: u32,
1590    sum_utilization: f64,
1591    growth_rate: f64,
1592}
1593
1594impl Default for CapacityPlanner {
1595    fn default() -> Self {
1596        Self::new(1000)
1597    }
1598}
1599
1600impl CapacityPlanner {
1601    /// Create with capacity
1602    #[must_use]
1603    pub fn new(capacity: u64) -> Self {
1604        Self {
1605            capacity: capacity.max(1),
1606            current: 0,
1607            peak: 0,
1608            samples: 0,
1609            sum_utilization: 0.0,
1610            growth_rate: 0.0,
1611        }
1612    }
1613
1614    /// Create for connections (1000)
1615    #[must_use]
1616    pub fn for_connections() -> Self {
1617        Self::new(1000)
1618    }
1619
1620    /// Create for storage GB (100)
1621    #[must_use]
1622    pub fn for_storage() -> Self {
1623        Self::new(100)
1624    }
1625
1626    /// Update current usage
1627    pub fn update(&mut self, current: u64) {
1628        let old = self.current;
1629        self.current = current;
1630        if current > self.peak {
1631            self.peak = current;
1632        }
1633        self.samples += 1;
1634        self.sum_utilization += self.utilization();
1635
1636        // Calculate growth rate (simple difference)
1637        if old > 0 {
1638            self.growth_rate = (current as f64 - old as f64) / old as f64;
1639        }
1640    }
1641
1642    /// Get current utilization as percentage
1643    #[must_use]
1644    pub fn utilization(&self) -> f64 {
1645        (self.current as f64 / self.capacity as f64) * 100.0
1646    }
1647
1648    /// Get peak utilization as percentage
1649    #[must_use]
1650    pub fn peak_utilization(&self) -> f64 {
1651        (self.peak as f64 / self.capacity as f64) * 100.0
1652    }
1653
1654    /// Get average utilization
1655    #[must_use]
1656    pub fn avg_utilization(&self) -> f64 {
1657        if self.samples == 0 {
1658            0.0
1659        } else {
1660            self.sum_utilization / self.samples as f64
1661        }
1662    }
1663
1664    /// Get remaining capacity
1665    #[must_use]
1666    pub fn remaining(&self) -> u64 {
1667        self.capacity.saturating_sub(self.current)
1668    }
1669
1670    /// Check if at risk (above threshold)
1671    #[must_use]
1672    pub fn at_risk(&self, threshold: f64) -> bool {
1673        self.utilization() >= threshold
1674    }
1675
1676    /// Get growth rate
1677    #[must_use]
1678    pub fn growth_rate(&self) -> f64 {
1679        self.growth_rate
1680    }
1681
1682    /// Reset planner
1683    pub fn reset(&mut self) {
1684        self.current = 0;
1685        self.peak = 0;
1686        self.samples = 0;
1687        self.sum_utilization = 0.0;
1688        self.growth_rate = 0.0;
1689    }
1690}
1691
1692// ============================================================================
1693// DriftTracker - Track clock/timing drift
1694// ============================================================================
1695
1696/// O(1) drift tracking for timing synchronization.
1697/// Monitors deviation from expected intervals and detects clock drift.
1698#[derive(Debug, Clone)]
1699pub struct DriftTracker {
1700    expected_interval_us: u64,
1701    last_timestamp_us: u64,
1702    total_drift_us: i64,
1703    samples: u64,
1704    max_drift_us: i64,
1705    min_drift_us: i64,
1706}
1707
1708impl Default for DriftTracker {
1709    fn default() -> Self {
1710        Self::new(1_000_000) // 1 second expected interval
1711    }
1712}
1713
1714impl DriftTracker {
1715    /// Create with expected interval in microseconds
1716    #[must_use]
1717    pub fn new(expected_interval_us: u64) -> Self {
1718        Self {
1719            expected_interval_us: expected_interval_us.max(1),
1720            last_timestamp_us: 0,
1721            total_drift_us: 0,
1722            samples: 0,
1723            max_drift_us: i64::MIN,
1724            min_drift_us: i64::MAX,
1725        }
1726    }
1727
1728    /// Create for 60fps (16.67ms interval)
1729    #[must_use]
1730    pub fn for_60fps() -> Self {
1731        Self::new(16_667)
1732    }
1733
1734    /// Create for 1 second heartbeat
1735    #[must_use]
1736    pub fn for_heartbeat() -> Self {
1737        Self::new(1_000_000)
1738    }
1739
1740    /// Record timestamp and calculate drift
1741    pub fn record(&mut self, now_us: u64) {
1742        if self.last_timestamp_us == 0 {
1743            self.last_timestamp_us = now_us;
1744            return;
1745        }
1746
1747        let actual_interval = now_us.saturating_sub(self.last_timestamp_us);
1748        let drift = actual_interval as i64 - self.expected_interval_us as i64;
1749
1750        self.total_drift_us += drift;
1751        self.samples += 1;
1752
1753        if drift > self.max_drift_us {
1754            self.max_drift_us = drift;
1755        }
1756        if drift < self.min_drift_us {
1757            self.min_drift_us = drift;
1758        }
1759
1760        self.last_timestamp_us = now_us;
1761    }
1762
1763    /// Get average drift in microseconds
1764    #[must_use]
1765    pub fn avg_drift_us(&self) -> f64 {
1766        if self.samples == 0 {
1767            0.0
1768        } else {
1769            self.total_drift_us as f64 / self.samples as f64
1770        }
1771    }
1772
1773    /// Get max drift (positive = late, negative = early)
1774    #[must_use]
1775    pub fn max_drift_us(&self) -> i64 {
1776        if self.samples == 0 {
1777            0
1778        } else {
1779            self.max_drift_us
1780        }
1781    }
1782
1783    /// Get min drift
1784    #[must_use]
1785    pub fn min_drift_us(&self) -> i64 {
1786        if self.samples == 0 {
1787            0
1788        } else {
1789            self.min_drift_us
1790        }
1791    }
1792
1793    /// Check if drift is within tolerance
1794    #[must_use]
1795    pub fn is_stable(&self, tolerance_us: i64) -> bool {
1796        self.avg_drift_us().abs() < tolerance_us as f64
1797    }
1798
1799    /// Get drift range
1800    #[must_use]
1801    pub fn drift_range_us(&self) -> i64 {
1802        if self.samples == 0 {
1803            0
1804        } else {
1805            self.max_drift_us - self.min_drift_us
1806        }
1807    }
1808
1809    /// Get sample count
1810    #[must_use]
1811    pub fn samples(&self) -> u64 {
1812        self.samples
1813    }
1814
1815    /// Reset tracker
1816    pub fn reset(&mut self) {
1817        self.last_timestamp_us = 0;
1818        self.total_drift_us = 0;
1819        self.samples = 0;
1820        self.max_drift_us = i64::MIN;
1821        self.min_drift_us = i64::MAX;
1822    }
1823}
1824
1825// ============================================================================
1826// SemaphoreTracker - Track semaphore/permit usage
1827// ============================================================================
1828
1829/// O(1) semaphore usage tracker.
1830/// Monitors permit acquisition and release patterns.
1831#[derive(Debug, Clone)]
1832pub struct SemaphoreTracker {
1833    total_permits: u32,
1834    acquired: u32,
1835    peak_acquired: u32,
1836    acquisitions: u64,
1837    releases: u64,
1838    contentions: u64,
1839}
1840
1841impl Default for SemaphoreTracker {
1842    fn default() -> Self {
1843        Self::new(10)
1844    }
1845}
1846
1847impl SemaphoreTracker {
1848    /// Create with total permits
1849    #[must_use]
1850    pub fn new(total_permits: u32) -> Self {
1851        Self {
1852            total_permits: total_permits.max(1),
1853            acquired: 0,
1854            peak_acquired: 0,
1855            acquisitions: 0,
1856            releases: 0,
1857            contentions: 0,
1858        }
1859    }
1860
1861    /// Create for database connections (20)
1862    #[must_use]
1863    pub fn for_database() -> Self {
1864        Self::new(20)
1865    }
1866
1867    /// Create for worker threads (8)
1868    #[must_use]
1869    pub fn for_workers() -> Self {
1870        Self::new(8)
1871    }
1872
1873    /// Try to acquire permit, returns true if successful
1874    pub fn try_acquire(&mut self) -> bool {
1875        if self.acquired < self.total_permits {
1876            self.acquired += 1;
1877            self.acquisitions += 1;
1878            if self.acquired > self.peak_acquired {
1879                self.peak_acquired = self.acquired;
1880            }
1881            true
1882        } else {
1883            self.contentions += 1;
1884            false
1885        }
1886    }
1887
1888    /// Release permit
1889    pub fn release(&mut self) {
1890        if self.acquired > 0 {
1891            self.acquired -= 1;
1892            self.releases += 1;
1893        }
1894    }
1895
1896    /// Get available permits
1897    #[must_use]
1898    pub fn available(&self) -> u32 {
1899        self.total_permits.saturating_sub(self.acquired)
1900    }
1901
1902    /// Get utilization as percentage
1903    #[must_use]
1904    pub fn utilization(&self) -> f64 {
1905        (self.acquired as f64 / self.total_permits as f64) * 100.0
1906    }
1907
1908    /// Get peak utilization as percentage
1909    #[must_use]
1910    pub fn peak_utilization(&self) -> f64 {
1911        (self.peak_acquired as f64 / self.total_permits as f64) * 100.0
1912    }
1913
1914    /// Get contention rate
1915    #[must_use]
1916    pub fn contention_rate(&self) -> f64 {
1917        let total = self.acquisitions + self.contentions;
1918        if total == 0 {
1919            0.0
1920        } else {
1921            (self.contentions as f64 / total as f64) * 100.0
1922        }
1923    }
1924
1925    /// Check if healthy (low contention)
1926    #[must_use]
1927    pub fn is_healthy(&self, max_contention: f64) -> bool {
1928        self.contention_rate() <= max_contention
1929    }
1930
1931    /// Get total permits
1932    #[must_use]
1933    pub fn total_permits(&self) -> u32 {
1934        self.total_permits
1935    }
1936
1937    /// Reset tracker
1938    pub fn reset(&mut self) {
1939        self.acquired = 0;
1940        self.peak_acquired = 0;
1941        self.acquisitions = 0;
1942        self.releases = 0;
1943        self.contentions = 0;
1944    }
1945}