Skip to main content

presentar_terminal/perf_trace/
helpers_batch.rs

1// ============================================================================
2// BATCH PROCESSING & WORK QUEUE HELPERS (trueno-viz parity)
3// ============================================================================
4
5/// O(1) batch processor for fixed-size batch accumulation.
6///
7/// Accumulates items until batch is full, then signals ready for processing.
8/// Common pattern for batching network writes, disk flushes, metric exports.
9#[derive(Debug, Clone)]
10pub struct BatchProcessor {
11    /// Current batch count
12    count: u64,
13    /// Batch size threshold
14    batch_size: u64,
15    /// Total batches completed
16    batches_completed: u64,
17    /// Total items processed
18    total_items: u64,
19}
20
21impl Default for BatchProcessor {
22    fn default() -> Self {
23        Self::new(100)
24    }
25}
26
27impl BatchProcessor {
28    /// Create with specified batch size
29    #[must_use]
30    pub fn new(batch_size: u64) -> Self {
31        Self {
32            count: 0,
33            batch_size: batch_size.max(1),
34            batches_completed: 0,
35            total_items: 0,
36        }
37    }
38
39    /// Create for network operations (batch size 1000)
40    #[must_use]
41    pub fn for_network() -> Self {
42        Self::new(1000)
43    }
44
45    /// Create for disk operations (batch size 100)
46    #[must_use]
47    pub fn for_disk() -> Self {
48        Self::new(100)
49    }
50
51    /// Create for metrics export (batch size 50)
52    #[must_use]
53    pub fn for_metrics() -> Self {
54        Self::new(50)
55    }
56
57    /// Add item to batch, returns true if batch is now full
58    pub fn add(&mut self) -> bool {
59        self.count += 1;
60        self.total_items += 1;
61        if self.count >= self.batch_size {
62            self.count = 0;
63            self.batches_completed += 1;
64            true
65        } else {
66            false
67        }
68    }
69
70    /// Add multiple items, returns number of batches completed
71    pub fn add_many(&mut self, n: u64) -> u64 {
72        self.total_items += n;
73        let new_count = self.count + n;
74        let batches = new_count / self.batch_size;
75        self.count = new_count % self.batch_size;
76        self.batches_completed += batches;
77        batches
78    }
79
80    /// Check if batch is ready (full)
81    #[must_use]
82    pub fn is_ready(&self) -> bool {
83        self.count >= self.batch_size
84    }
85
86    /// Get current batch fill percentage
87    #[must_use]
88    pub fn fill_percentage(&self) -> f64 {
89        (self.count as f64 / self.batch_size as f64) * 100.0
90    }
91
92    /// Get items remaining until full batch
93    #[must_use]
94    pub fn remaining(&self) -> u64 {
95        self.batch_size.saturating_sub(self.count)
96    }
97
98    /// Get total batches completed
99    #[must_use]
100    pub fn batches_completed(&self) -> u64 {
101        self.batches_completed
102    }
103
104    /// Get total items processed
105    #[must_use]
106    pub fn total_items(&self) -> u64 {
107        self.total_items
108    }
109
110    /// Flush current batch (mark complete regardless of count)
111    pub fn flush(&mut self) {
112        if self.count > 0 {
113            self.count = 0;
114            self.batches_completed += 1;
115        }
116    }
117
118    /// Reset all counters
119    pub fn reset(&mut self) {
120        self.count = 0;
121        self.batches_completed = 0;
122        self.total_items = 0;
123    }
124}
125
126/// O(1) pipeline stage latency and throughput tracker.
127///
128/// Tracks items entering and exiting a pipeline stage for monitoring
129/// processing latency, queue depth, and throughput.
130#[derive(Debug, Clone)]
131pub struct PipelineStage {
132    /// Items currently in stage
133    in_flight: u64,
134    /// Peak in-flight items
135    peak_in_flight: u64,
136    /// Total items entered
137    entered: u64,
138    /// Total items exited
139    exited: u64,
140    /// Total latency (for average calculation)
141    total_latency_us: u64,
142}
143
144impl Default for PipelineStage {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150impl PipelineStage {
151    /// Create new pipeline stage tracker
152    #[must_use]
153    pub fn new() -> Self {
154        Self {
155            in_flight: 0,
156            peak_in_flight: 0,
157            entered: 0,
158            exited: 0,
159            total_latency_us: 0,
160        }
161    }
162
163    /// Record item entering the stage
164    pub fn enter(&mut self) {
165        self.in_flight += 1;
166        self.entered += 1;
167        if self.in_flight > self.peak_in_flight {
168            self.peak_in_flight = self.in_flight;
169        }
170    }
171
172    /// Record item exiting the stage with latency in microseconds
173    pub fn exit(&mut self, latency_us: u64) {
174        self.in_flight = self.in_flight.saturating_sub(1);
175        self.exited += 1;
176        self.total_latency_us += latency_us;
177    }
178
179    /// Record item exiting without latency tracking
180    pub fn exit_simple(&mut self) {
181        self.in_flight = self.in_flight.saturating_sub(1);
182        self.exited += 1;
183    }
184
185    /// Get current queue depth
186    #[must_use]
187    pub fn depth(&self) -> u64 {
188        self.in_flight
189    }
190
191    /// Get peak queue depth
192    #[must_use]
193    pub fn peak_depth(&self) -> u64 {
194        self.peak_in_flight
195    }
196
197    /// Get average latency in microseconds
198    #[must_use]
199    pub fn avg_latency_us(&self) -> f64 {
200        if self.exited == 0 {
201            0.0
202        } else {
203            self.total_latency_us as f64 / self.exited as f64
204        }
205    }
206
207    /// Get average latency in milliseconds
208    #[must_use]
209    pub fn avg_latency_ms(&self) -> f64 {
210        self.avg_latency_us() / 1000.0
211    }
212
213    /// Get throughput (items processed)
214    #[must_use]
215    pub fn throughput(&self) -> u64 {
216        self.exited
217    }
218
219    /// Get total items that entered
220    #[must_use]
221    pub fn total_entered(&self) -> u64 {
222        self.entered
223    }
224
225    /// Check if stage is idle (nothing in flight)
226    #[must_use]
227    pub fn is_idle(&self) -> bool {
228        self.in_flight == 0
229    }
230
231    /// Check if stage is backlogged (depth > threshold)
232    #[must_use]
233    pub fn is_backlogged(&self, threshold: u64) -> bool {
234        self.in_flight > threshold
235    }
236
237    /// Reset all counters
238    pub fn reset(&mut self) {
239        self.in_flight = 0;
240        self.peak_in_flight = 0;
241        self.entered = 0;
242        self.exited = 0;
243        self.total_latency_us = 0;
244    }
245}
246
247/// O(1) work queue metrics tracker.
248///
249/// Tracks enqueue/dequeue operations, wait times, and queue health.
250#[derive(Debug, Clone)]
251pub struct WorkQueue {
252    /// Current queue size
253    size: u64,
254    /// Peak queue size
255    peak_size: u64,
256    /// Total enqueued
257    enqueued: u64,
258    /// Total dequeued
259    dequeued: u64,
260    /// Total wait time (for average)
261    total_wait_us: u64,
262    /// Capacity limit (0 = unlimited)
263    capacity: u64,
264}
265
266impl Default for WorkQueue {
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272impl WorkQueue {
273    /// Create unbounded work queue tracker
274    #[must_use]
275    pub fn new() -> Self {
276        Self {
277            size: 0,
278            peak_size: 0,
279            enqueued: 0,
280            dequeued: 0,
281            total_wait_us: 0,
282            capacity: 0,
283        }
284    }
285
286    /// Create bounded work queue tracker
287    #[must_use]
288    pub fn with_capacity(capacity: u64) -> Self {
289        Self {
290            capacity,
291            ..Self::new()
292        }
293    }
294
295    /// Enqueue item
296    pub fn enqueue(&mut self) -> bool {
297        if self.capacity > 0 && self.size >= self.capacity {
298            return false; // Would exceed capacity
299        }
300        self.size += 1;
301        self.enqueued += 1;
302        if self.size > self.peak_size {
303            self.peak_size = self.size;
304        }
305        true
306    }
307
308    /// Dequeue item with wait time in microseconds
309    pub fn dequeue(&mut self, wait_us: u64) -> bool {
310        if self.size == 0 {
311            return false;
312        }
313        self.size -= 1;
314        self.dequeued += 1;
315        self.total_wait_us += wait_us;
316        true
317    }
318
319    /// Dequeue without wait time tracking
320    pub fn dequeue_simple(&mut self) -> bool {
321        if self.size == 0 {
322            return false;
323        }
324        self.size -= 1;
325        self.dequeued += 1;
326        true
327    }
328
329    /// Get current queue size
330    #[must_use]
331    pub fn size(&self) -> u64 {
332        self.size
333    }
334
335    /// Get peak queue size
336    #[must_use]
337    pub fn peak_size(&self) -> u64 {
338        self.peak_size
339    }
340
341    /// Get average wait time in microseconds
342    #[must_use]
343    pub fn avg_wait_us(&self) -> f64 {
344        if self.dequeued == 0 {
345            0.0
346        } else {
347            self.total_wait_us as f64 / self.dequeued as f64
348        }
349    }
350
351    /// Get queue utilization (current/capacity)
352    #[must_use]
353    pub fn utilization(&self) -> f64 {
354        if self.capacity == 0 {
355            0.0
356        } else {
357            (self.size as f64 / self.capacity as f64) * 100.0
358        }
359    }
360
361    /// Check if queue is empty
362    #[must_use]
363    pub fn is_empty(&self) -> bool {
364        self.size == 0
365    }
366
367    /// Check if queue is full (bounded only)
368    #[must_use]
369    pub fn is_full(&self) -> bool {
370        self.capacity > 0 && self.size >= self.capacity
371    }
372
373    /// Get remaining capacity (0 if unbounded)
374    #[must_use]
375    pub fn remaining_capacity(&self) -> u64 {
376        if self.capacity == 0 {
377            u64::MAX
378        } else {
379            self.capacity.saturating_sub(self.size)
380        }
381    }
382
383    /// Get total enqueued
384    #[must_use]
385    pub fn total_enqueued(&self) -> u64 {
386        self.enqueued
387    }
388
389    /// Get total dequeued
390    #[must_use]
391    pub fn total_dequeued(&self) -> u64 {
392        self.dequeued
393    }
394
395    /// Reset all counters
396    pub fn reset(&mut self) {
397        self.size = 0;
398        self.peak_size = 0;
399        self.enqueued = 0;
400        self.dequeued = 0;
401        self.total_wait_us = 0;
402    }
403}
404
405// ============================================================================
406// RATE LIMITING HELPERS (trueno-viz parity)
407// ============================================================================
408
409/// O(1) leaky bucket rate limiter.
410///
411/// Classic leaky bucket algorithm: tokens leak at constant rate,
412/// requests add tokens. Overflow = rate exceeded.
413#[derive(Debug, Clone)]
414pub struct LeakyBucket {
415    /// Current bucket level
416    level: f64,
417    /// Bucket capacity
418    capacity: f64,
419    /// Leak rate (units per second)
420    leak_rate: f64,
421    /// Last update timestamp (microseconds)
422    last_update_us: u64,
423    /// Total overflows
424    overflows: u64,
425}
426
427impl Default for LeakyBucket {
428    fn default() -> Self {
429        Self::new(100.0, 10.0)
430    }
431}
432
433impl LeakyBucket {
434    /// Create with capacity and leak rate
435    #[must_use]
436    pub fn new(capacity: f64, leak_rate: f64) -> Self {
437        Self {
438            level: 0.0,
439            capacity: capacity.max(1.0),
440            leak_rate: leak_rate.max(0.1),
441            last_update_us: 0,
442            overflows: 0,
443        }
444    }
445
446    /// Create for API rate limiting (100 req/s, burst 200)
447    #[must_use]
448    pub fn for_api() -> Self {
449        Self::new(200.0, 100.0)
450    }
451
452    /// Create for network throttling (1MB/s, burst 5MB)
453    #[must_use]
454    pub fn for_network() -> Self {
455        Self::new(5_000_000.0, 1_000_000.0)
456    }
457
458    /// Add tokens, returns true if accepted (no overflow)
459    pub fn add(&mut self, tokens: f64, now_us: u64) -> bool {
460        self.leak(now_us);
461        let new_level = self.level + tokens;
462        if new_level > self.capacity {
463            self.overflows += 1;
464            false
465        } else {
466            self.level = new_level;
467            true
468        }
469    }
470
471    /// Leak tokens based on elapsed time
472    fn leak(&mut self, now_us: u64) {
473        if self.last_update_us == 0 {
474            self.last_update_us = now_us;
475            return;
476        }
477        let elapsed_s = (now_us.saturating_sub(self.last_update_us)) as f64 / 1_000_000.0;
478        let leaked = elapsed_s * self.leak_rate;
479        self.level = (self.level - leaked).max(0.0);
480        self.last_update_us = now_us;
481    }
482
483    /// Get current bucket level
484    #[must_use]
485    pub fn level(&self) -> f64 {
486        self.level
487    }
488
489    /// Get fill percentage
490    #[must_use]
491    pub fn fill_percentage(&self) -> f64 {
492        (self.level / self.capacity) * 100.0
493    }
494
495    /// Get overflow count
496    #[must_use]
497    pub fn overflows(&self) -> u64 {
498        self.overflows
499    }
500
501    /// Check if bucket is empty
502    #[must_use]
503    pub fn is_empty(&self) -> bool {
504        self.level <= 0.0
505    }
506
507    /// Reset bucket
508    pub fn reset(&mut self) {
509        self.level = 0.0;
510        self.overflows = 0;
511        self.last_update_us = 0;
512    }
513
514    /// Update with current time (for testing)
515    pub fn update_with_time(&mut self, now_us: u64) {
516        self.leak(now_us);
517    }
518}
519
520/// O(1) sliding window rate counter.
521///
522/// Counts events in a sliding time window using sub-windows.
523/// More accurate than token bucket for bursty traffic.
524#[derive(Debug, Clone)]
525pub struct SlidingWindowRate {
526    /// Sub-window counts (circular buffer)
527    windows: [u64; 10],
528    /// Current window index
529    current: usize,
530    /// Window duration in microseconds
531    window_us: u64,
532    /// Last window rotation timestamp
533    last_rotate_us: u64,
534    /// Rate limit
535    limit: u64,
536    /// Exceeded count
537    exceeded: u64,
538}
539
540impl Default for SlidingWindowRate {
541    fn default() -> Self {
542        Self::new(1_000_000, 100)
543    }
544}
545
546impl SlidingWindowRate {
547    /// Create with window duration (us) and rate limit
548    #[must_use]
549    pub fn new(window_us: u64, limit: u64) -> Self {
550        Self {
551            windows: [0; 10],
552            current: 0,
553            window_us: window_us.max(10_000), // Min 10ms
554            last_rotate_us: 0,
555            limit,
556            exceeded: 0,
557        }
558    }
559
560    /// Create for 1 second window with limit
561    #[must_use]
562    pub fn per_second(limit: u64) -> Self {
563        Self::new(1_000_000, limit)
564    }
565
566    /// Create for 1 minute window with limit
567    #[must_use]
568    pub fn per_minute(limit: u64) -> Self {
569        Self::new(60_000_000, limit)
570    }
571
572    /// Record event, returns true if within limit
573    pub fn record(&mut self, now_us: u64) -> bool {
574        self.rotate(now_us);
575        let count = self.count();
576        if count >= self.limit {
577            self.exceeded += 1;
578            false
579        } else {
580            self.windows[self.current] += 1;
581            true
582        }
583    }
584
585    /// Rotate windows if needed
586    fn rotate(&mut self, now_us: u64) {
587        if self.last_rotate_us == 0 {
588            self.last_rotate_us = now_us;
589            return;
590        }
591        let sub_window_us = self.window_us / 10;
592        let elapsed = now_us.saturating_sub(self.last_rotate_us);
593        let rotations = (elapsed / sub_window_us).min(10) as usize;
594
595        for _ in 0..rotations {
596            self.current = (self.current + 1) % 10;
597            self.windows[self.current] = 0;
598        }
599        if rotations > 0 {
600            self.last_rotate_us = now_us;
601        }
602    }
603
604    /// Get current count across all windows
605    #[must_use]
606    pub fn count(&self) -> u64 {
607        self.windows.iter().sum()
608    }
609
610    /// Get current rate as percentage of limit
611    #[must_use]
612    pub fn rate_percentage(&self) -> f64 {
613        if self.limit == 0 {
614            0.0
615        } else {
616            (self.count() as f64 / self.limit as f64) * 100.0
617        }
618    }
619
620    /// Check if rate limit would be exceeded
621    #[must_use]
622    pub fn would_exceed(&self) -> bool {
623        self.count() >= self.limit
624    }
625
626    /// Get exceeded count
627    #[must_use]
628    pub fn exceeded(&self) -> u64 {
629        self.exceeded
630    }
631
632    /// Reset all windows
633    pub fn reset(&mut self) {
634        self.windows = [0; 10];
635        self.current = 0;
636        self.exceeded = 0;
637        self.last_rotate_us = 0;
638    }
639
640    /// Update with current time (for testing)
641    pub fn update_with_time(&mut self, now_us: u64) {
642        self.rotate(now_us);
643    }
644}
645
646// ============================================================================
647// RESOURCE POOL & SAMPLING HELPERS (trueno-viz parity)
648// ============================================================================
649
650/// O(1) resource pool tracker for connection/object pool monitoring.
651///
652/// Tracks pool utilization, wait times, and connection health.
653#[derive(Debug, Clone)]
654pub struct ResourcePool {
655    /// Total pool size
656    capacity: u64,
657    /// Currently in use
658    in_use: u64,
659    /// Peak in use
660    peak_in_use: u64,
661    /// Total acquisitions
662    acquisitions: u64,
663    /// Total releases
664    releases: u64,
665    /// Total timeouts
666    timeouts: u64,
667    /// Total wait time (for average)
668    total_wait_us: u64,
669}
670
671impl Default for ResourcePool {
672    fn default() -> Self {
673        Self::new(10)
674    }
675}
676
677impl ResourcePool {
678    /// Create pool with capacity
679    #[must_use]
680    pub fn new(capacity: u64) -> Self {
681        Self {
682            capacity: capacity.max(1),
683            in_use: 0,
684            peak_in_use: 0,
685            acquisitions: 0,
686            releases: 0,
687            timeouts: 0,
688            total_wait_us: 0,
689        }
690    }
691
692    /// Create for database connections (typical pool size 20)
693    #[must_use]
694    pub fn for_database() -> Self {
695        Self::new(20)
696    }
697
698    /// Create for HTTP connections (typical pool size 100)
699    #[must_use]
700    pub fn for_http() -> Self {
701        Self::new(100)
702    }
703
704    /// Acquire resource from pool
705    pub fn acquire(&mut self, wait_us: u64) -> bool {
706        if self.in_use >= self.capacity {
707            self.timeouts += 1;
708            return false;
709        }
710        self.in_use += 1;
711        self.acquisitions += 1;
712        self.total_wait_us += wait_us;
713        if self.in_use > self.peak_in_use {
714            self.peak_in_use = self.in_use;
715        }
716        true
717    }
718
719    /// Release resource back to pool
720    pub fn release(&mut self) {
721        if self.in_use > 0 {
722            self.in_use -= 1;
723            self.releases += 1;
724        }
725    }
726
727    /// Get current utilization percentage
728    #[must_use]
729    pub fn utilization(&self) -> f64 {
730        (self.in_use as f64 / self.capacity as f64) * 100.0
731    }
732
733    /// Get available resources
734    #[must_use]
735    pub fn available(&self) -> u64 {
736        self.capacity.saturating_sub(self.in_use)
737    }
738
739    /// Get average wait time in microseconds
740    #[must_use]
741    pub fn avg_wait_us(&self) -> f64 {
742        if self.acquisitions == 0 {
743            0.0
744        } else {
745            self.total_wait_us as f64 / self.acquisitions as f64
746        }
747    }
748
749    /// Get timeout rate
750    #[must_use]
751    pub fn timeout_rate(&self) -> f64 {
752        let total = self.acquisitions + self.timeouts;
753        if total == 0 {
754            0.0
755        } else {
756            (self.timeouts as f64 / total as f64) * 100.0
757        }
758    }
759
760    /// Check if pool is exhausted
761    #[must_use]
762    pub fn is_exhausted(&self) -> bool {
763        self.in_use >= self.capacity
764    }
765
766    /// Check if pool is idle
767    #[must_use]
768    pub fn is_idle(&self) -> bool {
769        self.in_use == 0
770    }
771
772    /// Get peak utilization percentage
773    #[must_use]
774    pub fn peak_utilization(&self) -> f64 {
775        (self.peak_in_use as f64 / self.capacity as f64) * 100.0
776    }
777
778    /// Reset all counters (keep capacity)
779    pub fn reset(&mut self) {
780        self.in_use = 0;
781        self.peak_in_use = 0;
782        self.acquisitions = 0;
783        self.releases = 0;
784        self.timeouts = 0;
785        self.total_wait_us = 0;
786    }
787}
788
789/// O(1) 2D histogram for heatmap data accumulation.
790///
791/// Fixed-grid 2D histogram for accumulating values in x,y space.
792#[derive(Debug, Clone)]
793pub struct Histogram2D {
794    /// Grid cells (10x10 = 100 cells)
795    cells: [[u64; 10]; 10],
796    /// X min
797    x_min: f64,
798    /// X max
799    x_max: f64,
800    /// Y min
801    y_min: f64,
802    /// Y max
803    y_max: f64,
804    /// Total samples
805    count: u64,
806}
807
808impl Default for Histogram2D {
809    fn default() -> Self {
810        Self::new(0.0, 100.0, 0.0, 100.0)
811    }
812}
813
814impl Histogram2D {
815    /// Create with x and y ranges
816    #[must_use]
817    pub fn new(x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
818        Self {
819            cells: [[0; 10]; 10],
820            x_min,
821            x_max: x_max.max(x_min + 1.0),
822            y_min,
823            y_max: y_max.max(y_min + 1.0),
824            count: 0,
825        }
826    }
827
828    /// Create for latency vs throughput (0-100ms, 0-1000 ops/s)
829    #[must_use]
830    pub fn for_latency_throughput() -> Self {
831        Self::new(0.0, 100.0, 0.0, 1000.0)
832    }
833
834    /// Create for CPU vs Memory (0-100%)
835    #[must_use]
836    pub fn for_cpu_memory() -> Self {
837        Self::new(0.0, 100.0, 0.0, 100.0)
838    }
839
840    /// Add sample
841    pub fn add(&mut self, x: f64, y: f64) {
842        let xi = self.x_to_index(x);
843        let yi = self.y_to_index(y);
844        self.cells[yi][xi] += 1;
845        self.count += 1;
846    }
847
848    fn x_to_index(&self, x: f64) -> usize {
849        let normalized = (x - self.x_min) / (self.x_max - self.x_min);
850        (normalized * 10.0).clamp(0.0, 9.0) as usize
851    }
852
853    fn y_to_index(&self, y: f64) -> usize {
854        let normalized = (y - self.y_min) / (self.y_max - self.y_min);
855        (normalized * 10.0).clamp(0.0, 9.0) as usize
856    }
857
858    /// Get cell count
859    #[must_use]
860    pub fn get(&self, xi: usize, yi: usize) -> u64 {
861        if xi < 10 && yi < 10 {
862            self.cells[yi][xi]
863        } else {
864            0
865        }
866    }
867
868    /// Get cell density (percentage of total)
869    #[must_use]
870    pub fn density(&self, xi: usize, yi: usize) -> f64 {
871        if self.count == 0 || xi >= 10 || yi >= 10 {
872            0.0
873        } else {
874            (self.cells[yi][xi] as f64 / self.count as f64) * 100.0
875        }
876    }
877
878    /// Get max cell count
879    #[must_use]
880    pub fn max_count(&self) -> u64 {
881        self.cells
882            .iter()
883            .flat_map(|r| r.iter())
884            .copied()
885            .max()
886            .unwrap_or(0)
887    }
888
889    /// Get hotspot (cell with max count)
890    #[must_use]
891    pub fn hotspot(&self) -> (usize, usize) {
892        let mut max_val = 0;
893        let mut max_pos = (0, 0);
894        for (yi, row) in self.cells.iter().enumerate() {
895            for (xi, &val) in row.iter().enumerate() {
896                if val > max_val {
897                    max_val = val;
898                    max_pos = (xi, yi);
899                }
900            }
901        }
902        max_pos
903    }
904
905    /// Get total sample count
906    #[must_use]
907    pub fn count(&self) -> u64 {
908        self.count
909    }
910
911    /// Reset all cells
912    pub fn reset(&mut self) {
913        self.cells = [[0; 10]; 10];
914        self.count = 0;
915    }
916}
917
918/// O(1) reservoir sampler for uniform sampling of streams.
919///
920/// Maintains a fixed-size sample of items seen in a stream using
921/// Algorithm R (reservoir sampling).
922#[derive(Debug, Clone)]
923pub struct ReservoirSampler {
924    /// Sample values
925    samples: [f64; 16],
926    /// Number of valid samples
927    size: usize,
928    /// Capacity
929    capacity: usize,
930    /// Total items seen
931    seen: u64,
932    /// Simple LCG state for deterministic sampling
933    rng_state: u64,
934}
935
936impl Default for ReservoirSampler {
937    fn default() -> Self {
938        Self::new(16)
939    }
940}
941
942impl ReservoirSampler {
943    /// Create with capacity (max 16)
944    #[must_use]
945    pub fn new(capacity: usize) -> Self {
946        Self {
947            samples: [0.0; 16],
948            size: 0,
949            capacity: capacity.min(16),
950            seen: 0,
951            rng_state: 12345,
952        }
953    }
954
955    /// Simple LCG random number generator
956    fn next_random(&mut self) -> u64 {
957        self.rng_state = self
958            .rng_state
959            .wrapping_mul(6364136223846793005)
960            .wrapping_add(1);
961        self.rng_state
962    }
963
964    /// Add item to reservoir
965    pub fn add(&mut self, value: f64) {
966        self.seen += 1;
967        if self.size < self.capacity {
968            self.samples[self.size] = value;
969            self.size += 1;
970        } else {
971            // Reservoir sampling: replace with probability capacity/seen
972            let r = (self.next_random() % self.seen) as usize;
973            if r < self.capacity {
974                self.samples[r] = value;
975            }
976        }
977    }
978
979    /// Get sample at index
980    #[must_use]
981    pub fn get(&self, index: usize) -> Option<f64> {
982        if index < self.size {
983            Some(self.samples[index])
984        } else {
985            None
986        }
987    }
988
989    /// Get current sample size
990    #[must_use]
991    pub fn len(&self) -> usize {
992        self.size
993    }
994
995    /// Check if empty
996    #[must_use]
997    pub fn is_empty(&self) -> bool {
998        self.size == 0
999    }
1000
1001    /// Get total items seen
1002    #[must_use]
1003    pub fn total_seen(&self) -> u64 {
1004        self.seen
1005    }
1006
1007    /// Get sample mean
1008    #[must_use]
1009    pub fn mean(&self) -> f64 {
1010        if self.size == 0 {
1011            0.0
1012        } else {
1013            self.samples[..self.size].iter().sum::<f64>() / self.size as f64
1014        }
1015    }
1016
1017    /// Get sample min
1018    #[must_use]
1019    pub fn min(&self) -> f64 {
1020        if self.size == 0 {
1021            0.0
1022        } else {
1023            self.samples[..self.size]
1024                .iter()
1025                .fold(f64::MAX, |a, &b| a.min(b))
1026        }
1027    }
1028
1029    /// Get sample max
1030    #[must_use]
1031    pub fn max(&self) -> f64 {
1032        if self.size == 0 {
1033            0.0
1034        } else {
1035            self.samples[..self.size]
1036                .iter()
1037                .fold(f64::MIN, |a, &b| a.max(b))
1038        }
1039    }
1040
1041    /// Reset sampler
1042    pub fn reset(&mut self) {
1043        self.samples = [0.0; 16];
1044        self.size = 0;
1045        self.seen = 0;
1046        self.rng_state = 12345;
1047    }
1048}
1049
1050/// O(1) exponential histogram for log-scale binning.
1051///
1052/// Bins values into exponential buckets for wide-range distributions.
1053#[derive(Debug, Clone)]
1054pub struct ExponentialHistogram {
1055    /// Bucket counts (8 buckets: 1, 2, 4, 8, 16, 32, 64, 128+)
1056    buckets: [u64; 8],
1057    /// Base value (bucket boundaries are base * 2^i)
1058    base: f64,
1059    /// Total count
1060    count: u64,
1061    /// Sum of all values
1062    sum: f64,
1063}
1064
1065impl Default for ExponentialHistogram {
1066    fn default() -> Self {
1067        Self::new(1.0)
1068    }
1069}
1070
1071impl ExponentialHistogram {
1072    /// Create with base value
1073    #[must_use]
1074    pub fn new(base: f64) -> Self {
1075        Self {
1076            buckets: [0; 8],
1077            base: base.max(0.001),
1078            count: 0,
1079            sum: 0.0,
1080        }
1081    }
1082
1083    /// Create for latency (base 1ms: 1, 2, 4, 8, 16, 32, 64, 128+ ms)
1084    #[must_use]
1085    pub fn for_latency_ms() -> Self {
1086        Self::new(1.0)
1087    }
1088
1089    /// Create for bytes (base 1KB: 1, 2, 4, 8, 16, 32, 64, 128+ KB)
1090    #[must_use]
1091    pub fn for_bytes_kb() -> Self {
1092        Self::new(1024.0)
1093    }
1094
1095    /// Add value
1096    pub fn add(&mut self, value: f64) {
1097        self.count += 1;
1098        self.sum += value;
1099        let bucket = self.value_to_bucket(value);
1100        self.buckets[bucket] += 1;
1101    }
1102
1103    fn value_to_bucket(&self, value: f64) -> usize {
1104        if value < self.base {
1105            return 0;
1106        }
1107        let ratio = value / self.base;
1108        let bucket = ratio.log2().floor() as usize;
1109        bucket.min(7)
1110    }
1111
1112    /// Get bucket count
1113    #[must_use]
1114    pub fn bucket_count(&self, bucket: usize) -> u64 {
1115        if bucket < 8 {
1116            self.buckets[bucket]
1117        } else {
1118            0
1119        }
1120    }
1121
1122    /// Get bucket upper bound
1123    #[must_use]
1124    pub fn bucket_upper_bound(&self, bucket: usize) -> f64 {
1125        if bucket >= 7 {
1126            f64::INFINITY
1127        } else {
1128            self.base * 2.0_f64.powi(bucket as i32 + 1)
1129        }
1130    }
1131
1132    /// Get total count
1133    #[must_use]
1134    pub fn count(&self) -> u64 {
1135        self.count
1136    }
1137
1138    /// Get mean value
1139    #[must_use]
1140    pub fn mean(&self) -> f64 {
1141        if self.count == 0 {
1142            0.0
1143        } else {
1144            self.sum / self.count as f64
1145        }
1146    }
1147
1148    /// Get bucket with most samples
1149    #[must_use]
1150    pub fn mode_bucket(&self) -> usize {
1151        self.buckets
1152            .iter()
1153            .enumerate()
1154            .max_by_key(|(_, &c)| c)
1155            .map(|(i, _)| i)
1156            .unwrap_or(0)
1157    }
1158
1159    /// Reset histogram
1160    pub fn reset(&mut self) {
1161        self.buckets = [0; 8];
1162        self.count = 0;
1163        self.sum = 0.0;
1164    }
1165}