1use khora_core::telemetry::MetricId;
18use std::collections::HashMap;
19
20#[derive(Debug, Clone)]
22pub struct RingBuffer<T, const N: usize> {
23 data: [T; N],
24 index: usize,
25 count: usize,
26}
27
28impl<T: Default + Copy, const N: usize> Default for RingBuffer<T, N> {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl<T: Default + Copy, const N: usize> RingBuffer<T, N> {
35 pub fn new() -> Self {
37 Self {
38 data: [T::default(); N],
39 index: 0,
40 count: 0,
41 }
42 }
43
44 pub fn push(&mut self, value: T) {
46 self.data[self.index] = value;
47 self.index = (self.index + 1) % N;
48 if self.count < N {
49 self.count += 1;
50 }
51 }
52
53 pub fn count(&self) -> usize {
55 self.count
56 }
57
58 pub fn iter(&self) -> impl Iterator<Item = &T> {
60 let (left, right) = self.data.split_at(self.index);
61 if self.count < N {
62 right[N - self.index..]
64 .iter()
65 .chain(left[..self.index].iter())
66 } else {
67 right.iter().chain(left.iter())
69 }
70 }
71}
72
73impl<const N: usize> RingBuffer<f32, N> {
74 pub fn average(&self) -> f32 {
76 if self.count == 0 {
77 return 0.0;
78 }
79 self.iter().sum::<f32>() / self.count as f32
80 }
81
82 pub fn trend(&self) -> f32 {
86 if self.count < 2 {
87 return 0.0;
88 }
89 let half = self.count / 2;
90 let first_half_avg: f32 = self.iter().take(half).sum::<f32>() / half as f32;
91 let last_half_avg: f32 = self.iter().skip(self.count - half).sum::<f32>() / half as f32;
92 last_half_avg - first_half_avg
93 }
94
95 pub fn variance(&self) -> f32 {
99 if self.count < 2 {
100 return 0.0;
101 }
102 let avg = self.average();
103 let sum_sq: f32 = self.iter().map(|v| (v - avg) * (v - avg)).sum();
104 sum_sq / self.count as f32
105 }
106
107 pub fn min(&self) -> f32 {
109 if self.count == 0 {
110 return f32::MAX;
111 }
112 self.iter().copied().fold(f32::MAX, f32::min)
113 }
114
115 pub fn max(&self) -> f32 {
117 if self.count == 0 {
118 return f32::MIN;
119 }
120 self.iter().copied().fold(f32::MIN, f32::max)
121 }
122}
123
124#[derive(Debug, Default)]
126pub struct MetricStore {
127 buffers: HashMap<MetricId, RingBuffer<f32, 120>>, }
131
132impl MetricStore {
133 pub fn new() -> Self {
135 Self::default()
136 }
137
138 pub fn push(&mut self, id: MetricId, value: f32) {
140 self.buffers.entry(id).or_default().push(value);
141 }
142
143 pub fn get_average(&self, id: &MetricId) -> f32 {
145 self.buffers.get(id).map(|b| b.average()).unwrap_or(0.0)
146 }
147
148 pub fn get_trend(&self, id: &MetricId) -> f32 {
150 self.buffers.get(id).map(|b| b.trend()).unwrap_or(0.0)
151 }
152
153 pub fn get_variance(&self, id: &MetricId) -> f32 {
157 self.buffers.get(id).map(|b| b.variance()).unwrap_or(0.0)
158 }
159
160 pub fn get_max(&self, id: &MetricId) -> f32 {
162 self.buffers.get(id).map(|b| b.max()).unwrap_or(f32::MIN)
163 }
164
165 pub fn get_min(&self, id: &MetricId) -> f32 {
167 self.buffers.get(id).map(|b| b.min()).unwrap_or(f32::MAX)
168 }
169
170 pub fn get_sample_count(&self, id: &MetricId) -> usize {
172 self.buffers.get(id).map(|b| b.count()).unwrap_or(0)
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_ring_buffer_push_and_iter() {
182 let mut rb = RingBuffer::<f32, 3>::new();
183 rb.push(1.0);
184 rb.push(2.0);
185 rb.push(3.0);
186 rb.push(4.0); let values: Vec<f32> = rb.iter().copied().collect();
189 assert_eq!(values, vec![2.0, 3.0, 4.0]);
190 assert_eq!(rb.count(), 3);
191 }
192
193 #[test]
194 fn test_ring_buffer_average() {
195 let mut rb = RingBuffer::<f32, 4>::new();
196 rb.push(10.0);
197 rb.push(20.0);
198 assert_eq!(rb.average(), 15.0);
199 }
200
201 #[test]
202 fn test_ring_buffer_trend() {
203 let mut rb = RingBuffer::<f32, 4>::new();
204 rb.push(1.0);
205 rb.push(1.1);
206 rb.push(2.0);
207 rb.push(2.1);
208 assert!((rb.trend() - 1.0).abs() < 0.001);
212 }
213
214 #[test]
215 fn test_ring_buffer_variance() {
216 let mut rb = RingBuffer::<f32, 4>::new();
217 rb.push(10.0);
218 rb.push(10.0);
219 rb.push(10.0);
220 rb.push(10.0);
221 assert_eq!(rb.variance(), 0.0); let mut rb2 = RingBuffer::<f32, 4>::new();
224 rb2.push(5.0);
225 rb2.push(15.0);
226 rb2.push(5.0);
227 rb2.push(15.0);
228 assert!((rb2.variance() - 25.0).abs() < 0.001);
230 }
231
232 #[test]
233 fn test_ring_buffer_min_max() {
234 let mut rb = RingBuffer::<f32, 4>::new();
235 rb.push(3.0);
236 rb.push(1.0);
237 rb.push(4.0);
238 rb.push(1.5);
239 assert_eq!(rb.min(), 1.0);
240 assert_eq!(rb.max(), 4.0);
241 }
242
243 #[test]
244 fn test_ring_buffer_empty() {
245 let rb = RingBuffer::<f32, 4>::new();
246 assert_eq!(rb.average(), 0.0);
247 assert_eq!(rb.trend(), 0.0);
248 assert_eq!(rb.variance(), 0.0);
249 assert_eq!(rb.count(), 0);
250 }
251
252 #[test]
253 fn test_metric_store_variance_and_extremes() {
254 let mut store = MetricStore::new();
255 let id = MetricId::new("test", "values");
256 store.push(id.clone(), 5.0);
257 store.push(id.clone(), 15.0);
258 store.push(id.clone(), 5.0);
259 store.push(id.clone(), 15.0);
260
261 assert!((store.get_variance(&id) - 25.0).abs() < 0.01);
262 assert_eq!(store.get_min(&id), 5.0);
263 assert_eq!(store.get_max(&id), 15.0);
264 assert_eq!(store.get_sample_count(&id), 4);
265 }
266}