presentar_terminal/perf_trace/
helpers_batch.rs1#[derive(Debug, Clone)]
10pub struct BatchProcessor {
11 count: u64,
13 batch_size: u64,
15 batches_completed: u64,
17 total_items: u64,
19}
20
21impl Default for BatchProcessor {
22 fn default() -> Self {
23 Self::new(100)
24 }
25}
26
27impl BatchProcessor {
28 #[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 #[must_use]
41 pub fn for_network() -> Self {
42 Self::new(1000)
43 }
44
45 #[must_use]
47 pub fn for_disk() -> Self {
48 Self::new(100)
49 }
50
51 #[must_use]
53 pub fn for_metrics() -> Self {
54 Self::new(50)
55 }
56
57 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 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 #[must_use]
82 pub fn is_ready(&self) -> bool {
83 self.count >= self.batch_size
84 }
85
86 #[must_use]
88 pub fn fill_percentage(&self) -> f64 {
89 (self.count as f64 / self.batch_size as f64) * 100.0
90 }
91
92 #[must_use]
94 pub fn remaining(&self) -> u64 {
95 self.batch_size.saturating_sub(self.count)
96 }
97
98 #[must_use]
100 pub fn batches_completed(&self) -> u64 {
101 self.batches_completed
102 }
103
104 #[must_use]
106 pub fn total_items(&self) -> u64 {
107 self.total_items
108 }
109
110 pub fn flush(&mut self) {
112 if self.count > 0 {
113 self.count = 0;
114 self.batches_completed += 1;
115 }
116 }
117
118 pub fn reset(&mut self) {
120 self.count = 0;
121 self.batches_completed = 0;
122 self.total_items = 0;
123 }
124}
125
126#[derive(Debug, Clone)]
131pub struct PipelineStage {
132 in_flight: u64,
134 peak_in_flight: u64,
136 entered: u64,
138 exited: u64,
140 total_latency_us: u64,
142}
143
144impl Default for PipelineStage {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl PipelineStage {
151 #[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 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 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 pub fn exit_simple(&mut self) {
181 self.in_flight = self.in_flight.saturating_sub(1);
182 self.exited += 1;
183 }
184
185 #[must_use]
187 pub fn depth(&self) -> u64 {
188 self.in_flight
189 }
190
191 #[must_use]
193 pub fn peak_depth(&self) -> u64 {
194 self.peak_in_flight
195 }
196
197 #[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 #[must_use]
209 pub fn avg_latency_ms(&self) -> f64 {
210 self.avg_latency_us() / 1000.0
211 }
212
213 #[must_use]
215 pub fn throughput(&self) -> u64 {
216 self.exited
217 }
218
219 #[must_use]
221 pub fn total_entered(&self) -> u64 {
222 self.entered
223 }
224
225 #[must_use]
227 pub fn is_idle(&self) -> bool {
228 self.in_flight == 0
229 }
230
231 #[must_use]
233 pub fn is_backlogged(&self, threshold: u64) -> bool {
234 self.in_flight > threshold
235 }
236
237 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#[derive(Debug, Clone)]
251pub struct WorkQueue {
252 size: u64,
254 peak_size: u64,
256 enqueued: u64,
258 dequeued: u64,
260 total_wait_us: u64,
262 capacity: u64,
264}
265
266impl Default for WorkQueue {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272impl WorkQueue {
273 #[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 #[must_use]
288 pub fn with_capacity(capacity: u64) -> Self {
289 Self {
290 capacity,
291 ..Self::new()
292 }
293 }
294
295 pub fn enqueue(&mut self) -> bool {
297 if self.capacity > 0 && self.size >= self.capacity {
298 return false; }
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 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 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 #[must_use]
331 pub fn size(&self) -> u64 {
332 self.size
333 }
334
335 #[must_use]
337 pub fn peak_size(&self) -> u64 {
338 self.peak_size
339 }
340
341 #[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 #[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 #[must_use]
363 pub fn is_empty(&self) -> bool {
364 self.size == 0
365 }
366
367 #[must_use]
369 pub fn is_full(&self) -> bool {
370 self.capacity > 0 && self.size >= self.capacity
371 }
372
373 #[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 #[must_use]
385 pub fn total_enqueued(&self) -> u64 {
386 self.enqueued
387 }
388
389 #[must_use]
391 pub fn total_dequeued(&self) -> u64 {
392 self.dequeued
393 }
394
395 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#[derive(Debug, Clone)]
414pub struct LeakyBucket {
415 level: f64,
417 capacity: f64,
419 leak_rate: f64,
421 last_update_us: u64,
423 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 #[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 #[must_use]
448 pub fn for_api() -> Self {
449 Self::new(200.0, 100.0)
450 }
451
452 #[must_use]
454 pub fn for_network() -> Self {
455 Self::new(5_000_000.0, 1_000_000.0)
456 }
457
458 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 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 #[must_use]
485 pub fn level(&self) -> f64 {
486 self.level
487 }
488
489 #[must_use]
491 pub fn fill_percentage(&self) -> f64 {
492 (self.level / self.capacity) * 100.0
493 }
494
495 #[must_use]
497 pub fn overflows(&self) -> u64 {
498 self.overflows
499 }
500
501 #[must_use]
503 pub fn is_empty(&self) -> bool {
504 self.level <= 0.0
505 }
506
507 pub fn reset(&mut self) {
509 self.level = 0.0;
510 self.overflows = 0;
511 self.last_update_us = 0;
512 }
513
514 pub fn update_with_time(&mut self, now_us: u64) {
516 self.leak(now_us);
517 }
518}
519
520#[derive(Debug, Clone)]
525pub struct SlidingWindowRate {
526 windows: [u64; 10],
528 current: usize,
530 window_us: u64,
532 last_rotate_us: u64,
534 limit: u64,
536 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 #[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), last_rotate_us: 0,
555 limit,
556 exceeded: 0,
557 }
558 }
559
560 #[must_use]
562 pub fn per_second(limit: u64) -> Self {
563 Self::new(1_000_000, limit)
564 }
565
566 #[must_use]
568 pub fn per_minute(limit: u64) -> Self {
569 Self::new(60_000_000, limit)
570 }
571
572 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 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 #[must_use]
606 pub fn count(&self) -> u64 {
607 self.windows.iter().sum()
608 }
609
610 #[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 #[must_use]
622 pub fn would_exceed(&self) -> bool {
623 self.count() >= self.limit
624 }
625
626 #[must_use]
628 pub fn exceeded(&self) -> u64 {
629 self.exceeded
630 }
631
632 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 pub fn update_with_time(&mut self, now_us: u64) {
642 self.rotate(now_us);
643 }
644}
645
646#[derive(Debug, Clone)]
654pub struct ResourcePool {
655 capacity: u64,
657 in_use: u64,
659 peak_in_use: u64,
661 acquisitions: u64,
663 releases: u64,
665 timeouts: u64,
667 total_wait_us: u64,
669}
670
671impl Default for ResourcePool {
672 fn default() -> Self {
673 Self::new(10)
674 }
675}
676
677impl ResourcePool {
678 #[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 #[must_use]
694 pub fn for_database() -> Self {
695 Self::new(20)
696 }
697
698 #[must_use]
700 pub fn for_http() -> Self {
701 Self::new(100)
702 }
703
704 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 pub fn release(&mut self) {
721 if self.in_use > 0 {
722 self.in_use -= 1;
723 self.releases += 1;
724 }
725 }
726
727 #[must_use]
729 pub fn utilization(&self) -> f64 {
730 (self.in_use as f64 / self.capacity as f64) * 100.0
731 }
732
733 #[must_use]
735 pub fn available(&self) -> u64 {
736 self.capacity.saturating_sub(self.in_use)
737 }
738
739 #[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 #[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 #[must_use]
762 pub fn is_exhausted(&self) -> bool {
763 self.in_use >= self.capacity
764 }
765
766 #[must_use]
768 pub fn is_idle(&self) -> bool {
769 self.in_use == 0
770 }
771
772 #[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 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#[derive(Debug, Clone)]
793pub struct Histogram2D {
794 cells: [[u64; 10]; 10],
796 x_min: f64,
798 x_max: f64,
800 y_min: f64,
802 y_max: f64,
804 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 #[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 #[must_use]
830 pub fn for_latency_throughput() -> Self {
831 Self::new(0.0, 100.0, 0.0, 1000.0)
832 }
833
834 #[must_use]
836 pub fn for_cpu_memory() -> Self {
837 Self::new(0.0, 100.0, 0.0, 100.0)
838 }
839
840 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 #[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 #[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 #[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 #[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 #[must_use]
907 pub fn count(&self) -> u64 {
908 self.count
909 }
910
911 pub fn reset(&mut self) {
913 self.cells = [[0; 10]; 10];
914 self.count = 0;
915 }
916}
917
918#[derive(Debug, Clone)]
923pub struct ReservoirSampler {
924 samples: [f64; 16],
926 size: usize,
928 capacity: usize,
930 seen: u64,
932 rng_state: u64,
934}
935
936impl Default for ReservoirSampler {
937 fn default() -> Self {
938 Self::new(16)
939 }
940}
941
942impl ReservoirSampler {
943 #[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 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 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 let r = (self.next_random() % self.seen) as usize;
973 if r < self.capacity {
974 self.samples[r] = value;
975 }
976 }
977 }
978
979 #[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 #[must_use]
991 pub fn len(&self) -> usize {
992 self.size
993 }
994
995 #[must_use]
997 pub fn is_empty(&self) -> bool {
998 self.size == 0
999 }
1000
1001 #[must_use]
1003 pub fn total_seen(&self) -> u64 {
1004 self.seen
1005 }
1006
1007 #[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 #[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 #[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 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#[derive(Debug, Clone)]
1054pub struct ExponentialHistogram {
1055 buckets: [u64; 8],
1057 base: f64,
1059 count: u64,
1061 sum: f64,
1063}
1064
1065impl Default for ExponentialHistogram {
1066 fn default() -> Self {
1067 Self::new(1.0)
1068 }
1069}
1070
1071impl ExponentialHistogram {
1072 #[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 #[must_use]
1085 pub fn for_latency_ms() -> Self {
1086 Self::new(1.0)
1087 }
1088
1089 #[must_use]
1091 pub fn for_bytes_kb() -> Self {
1092 Self::new(1024.0)
1093 }
1094
1095 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 #[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 #[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 #[must_use]
1134 pub fn count(&self) -> u64 {
1135 self.count
1136 }
1137
1138 #[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 #[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 pub fn reset(&mut self) {
1161 self.buckets = [0; 8];
1162 self.count = 0;
1163 self.sum = 0.0;
1164 }
1165}