presentar_terminal/perf_trace/
data_structures.rs1#[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 #[must_use]
25 pub fn new() -> Self {
26 Self {
27 data: [T::default(); N],
28 head: 0,
29 len: 0,
30 }
31 }
32
33 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 #[must_use]
42 pub fn len(&self) -> usize {
43 self.len
44 }
45
46 #[must_use]
48 pub fn is_empty(&self) -> bool {
49 self.len == 0
50 }
51
52 #[must_use]
54 pub fn is_full(&self) -> bool {
55 self.len == N
56 }
57
58 #[must_use]
60 pub fn capacity(&self) -> usize {
61 N
62 }
63
64 #[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 #[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 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 pub fn clear(&mut self) {
98 self.head = 0;
99 self.len = 0;
100 }
101}
102
103impl<const N: usize> RingBuffer<f64, N> {
105 #[must_use]
107 pub fn sum(&self) -> f64 {
108 self.iter().sum()
109 }
110
111 #[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 #[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 #[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#[derive(Debug, Clone)]
146pub struct LatencyHistogram {
147 bins: [u64; 7],
149 count: u64,
151}
152
153impl Default for LatencyHistogram {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl LatencyHistogram {
160 #[must_use]
162 pub fn new() -> Self {
163 Self {
164 bins: [0; 7],
165 count: 0,
166 }
167 }
168
169 pub fn record(&mut self, latency_us: u64) {
171 let bin = match latency_us {
172 0..=999 => 0, 1000..=4999 => 1, 5000..=9999 => 2, 10000..=49999 => 3, 50000..=99999 => 4, 100000..=499999 => 5, _ => 6, };
180 self.bins[bin] += 1;
181 self.count += 1;
182 }
183
184 #[must_use]
186 pub fn count(&self) -> u64 {
187 self.count
188 }
189
190 #[must_use]
192 pub fn bin_count(&self, bin: usize) -> u64 {
193 self.bins.get(bin).copied().unwrap_or(0)
194 }
195
196 #[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 #[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 #[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 pub fn reset(&mut self) {
241 self.bins = [0; 7];
242 self.count = 0;
243 }
244}
245
246#[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 #[inline]
269 #[must_use]
270 pub const fn new() -> Self {
271 Self { $( $fname: 0, )+ }
272 }
273
274 #[inline]
276 pub fn reset(&mut self) {
277 *self = Self::new();
278 }
279 }
280 };
281}