Skip to main content

presentar_terminal/perf_trace/
data_structures.rs

1// =============================================================================
2// FIXED-SIZE RING BUFFER (trueno-viz O(1) history pattern)
3// =============================================================================
4
5/// Fixed-size ring buffer for O(1) history access (trueno-viz pattern)
6///
7/// Provides constant-time insert, latest N values, and rolling statistics.
8/// No heap allocation after initial creation.
9#[derive(Debug, Clone)]
10pub struct RingBuffer<T, const N: usize> {
11    data: [T; N],
12    head: usize,
13    len: usize,
14}
15
16impl<T: Default + Copy, const N: usize> Default for RingBuffer<T, N> {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22impl<T: Default + Copy, const N: usize> RingBuffer<T, N> {
23    /// Create a new empty ring buffer
24    #[must_use]
25    pub fn new() -> Self {
26        Self {
27            data: [T::default(); N],
28            head: 0,
29            len: 0,
30        }
31    }
32
33    /// Push a value (O(1))
34    pub fn push(&mut self, value: T) {
35        self.data[self.head] = value;
36        self.head = (self.head + 1) % N;
37        self.len = self.len.saturating_add(1).min(N);
38    }
39
40    /// Get current length
41    #[must_use]
42    pub fn len(&self) -> usize {
43        self.len
44    }
45
46    /// Check if empty
47    #[must_use]
48    pub fn is_empty(&self) -> bool {
49        self.len == 0
50    }
51
52    /// Check if full
53    #[must_use]
54    pub fn is_full(&self) -> bool {
55        self.len == N
56    }
57
58    /// Get capacity
59    #[must_use]
60    pub fn capacity(&self) -> usize {
61        N
62    }
63
64    /// Get the most recent value (O(1))
65    #[must_use]
66    pub fn latest(&self) -> Option<&T> {
67        if self.len == 0 {
68            None
69        } else {
70            let idx = if self.head == 0 { N - 1 } else { self.head - 1 };
71            Some(&self.data[idx])
72        }
73    }
74
75    /// Get value at index from oldest (O(1))
76    /// Index 0 = oldest, index len-1 = newest
77    #[must_use]
78    pub fn get(&self, index: usize) -> Option<&T> {
79        if index >= self.len {
80            return None;
81        }
82        let start = if self.len < N { 0 } else { self.head };
83        let actual_idx = (start + index) % N;
84        Some(&self.data[actual_idx])
85    }
86
87    /// Iterate from oldest to newest
88    pub fn iter(&self) -> impl Iterator<Item = &T> {
89        let start = if self.len < N { 0 } else { self.head };
90        (0..self.len).map(move |i| {
91            let idx = (start + i) % N;
92            &self.data[idx]
93        })
94    }
95
96    /// Clear the buffer
97    pub fn clear(&mut self) {
98        self.head = 0;
99        self.len = 0;
100    }
101}
102
103/// Rolling statistics over a ring buffer (O(1) access)
104impl<const N: usize> RingBuffer<f64, N> {
105    /// Calculate running sum (O(n) but typically small N)
106    #[must_use]
107    pub fn sum(&self) -> f64 {
108        self.iter().sum()
109    }
110
111    /// Calculate mean (O(n))
112    #[must_use]
113    pub fn mean(&self) -> f64 {
114        if self.len == 0 {
115            0.0
116        } else {
117            self.sum() / self.len as f64
118        }
119    }
120
121    /// Get min value (O(n))
122    #[must_use]
123    pub fn min(&self) -> Option<f64> {
124        self.iter()
125            .copied()
126            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
127    }
128
129    /// Get max value (O(n))
130    #[must_use]
131    pub fn max(&self) -> Option<f64> {
132        self.iter()
133            .copied()
134            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
135    }
136}
137
138// =============================================================================
139// HISTOGRAM BIN (trueno-viz O(1) distribution pattern)
140// =============================================================================
141
142/// Fixed-bin histogram for O(1) distribution tracking (trueno-viz pattern)
143///
144/// Pre-defined bins for common latency ranges. Insert and query are O(1).
145#[derive(Debug, Clone)]
146pub struct LatencyHistogram {
147    /// Counts for bins: [0-1ms, 1-5ms, 5-10ms, 10-50ms, 50-100ms, 100-500ms, 500ms+]
148    bins: [u64; 7],
149    /// Total count
150    count: u64,
151}
152
153impl Default for LatencyHistogram {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159impl LatencyHistogram {
160    /// Create a new histogram
161    #[must_use]
162    pub fn new() -> Self {
163        Self {
164            bins: [0; 7],
165            count: 0,
166        }
167    }
168
169    /// Record a latency value in microseconds (O(1))
170    pub fn record(&mut self, latency_us: u64) {
171        let bin = match latency_us {
172            0..=999 => 0,         // 0-1ms
173            1000..=4999 => 1,     // 1-5ms
174            5000..=9999 => 2,     // 5-10ms
175            10000..=49999 => 3,   // 10-50ms
176            50000..=99999 => 4,   // 50-100ms
177            100000..=499999 => 5, // 100-500ms
178            _ => 6,               // 500ms+
179        };
180        self.bins[bin] += 1;
181        self.count += 1;
182    }
183
184    /// Get total count
185    #[must_use]
186    pub fn count(&self) -> u64 {
187        self.count
188    }
189
190    /// Get count for a specific bin
191    #[must_use]
192    pub fn bin_count(&self, bin: usize) -> u64 {
193        self.bins.get(bin).copied().unwrap_or(0)
194    }
195
196    /// Get percentage in each bin
197    #[must_use]
198    pub fn percentages(&self) -> [f64; 7] {
199        if self.count == 0 {
200            return [0.0; 7];
201        }
202        let mut pcts = [0.0; 7];
203        for (i, &count) in self.bins.iter().enumerate() {
204            pcts[i] = (count as f64 / self.count as f64) * 100.0;
205        }
206        pcts
207    }
208
209    /// Get bin label
210    #[must_use]
211    pub fn bin_label(bin: usize) -> &'static str {
212        match bin {
213            0 => "0-1ms",
214            1 => "1-5ms",
215            2 => "5-10ms",
216            3 => "10-50ms",
217            4 => "50-100ms",
218            5 => "100-500ms",
219            6 => "500ms+",
220            _ => "?",
221        }
222    }
223
224    /// Format as ASCII histogram
225    #[must_use]
226    pub fn ascii_histogram(&self, width: usize) -> String {
227        let pcts = self.percentages();
228        let mut lines = Vec::new();
229
230        for (i, pct) in pcts.iter().enumerate() {
231            let bar_len = ((*pct / 100.0) * width as f64) as usize;
232            let bar: String = "█".repeat(bar_len);
233            lines.push(format!("{:>10} {:5.1}% {}", Self::bin_label(i), pct, bar));
234        }
235
236        lines.join("\n")
237    }
238
239    /// Reset histogram
240    pub fn reset(&mut self) {
241        self.bins = [0; 7];
242        self.count = 0;
243    }
244}
245
246// =============================================================================
247// TRACKER MACRO (PMAT-019: reduce ResourceManagement entropy)
248// =============================================================================
249
250/// Generates a tracker struct with all-zero `new()`, `Default`, `Clone`, and `reset()`.
251/// Domain-specific methods are added via separate `impl` blocks.
252#[allow(unused_macros)]
253macro_rules! define_tracker {
254    (
255        $(#[$meta:meta])*
256        $vis:vis struct $name:ident {
257            $( $(#[$fmeta:meta])* $fvis:vis $fname:ident : $fty:ty ),+ $(,)?
258        }
259    ) => {
260        $(#[$meta])*
261        #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
262        $vis struct $name {
263            $( $(#[$fmeta])* $fvis $fname : $fty, )+
264        }
265
266        impl $name {
267            /// Create new zeroed tracker.
268            #[inline]
269            #[must_use]
270            pub const fn new() -> Self {
271                Self { $( $fname: 0, )+ }
272            }
273
274            /// Reset all counters to zero.
275            #[inline]
276            pub fn reset(&mut self) {
277                *self = Self::new();
278            }
279        }
280    };
281}