1use crate::math::usize_f64;
7#[cfg(not(any(feature = "std", test)))]
10use crate::math::FloatExt;
11use crate::pipeline::MetricSlot;
12
13pub struct TumblingWindow {
25 pub window_ms: u64,
27 current_start: u64,
29 current: MetricSlot,
31 alpha: f64,
33}
34
35impl TumblingWindow {
36 #[must_use]
38 pub fn new(window_ms: u64, alpha: f64) -> Self {
39 Self {
40 window_ms: window_ms.max(1),
41 current_start: 0,
42 current: MetricSlot::new(0, alpha),
43 alpha,
44 }
45 }
46
47 pub fn insert(&mut self, value: f64, timestamp_ms: u64) -> Option<WindowResult> {
54 if self.current_start == 0 {
56 self.current_start = timestamp_ms;
57 }
58
59 if timestamp_ms >= self.current_start.saturating_add(self.window_ms) {
61 let completed = core::mem::replace(&mut self.current, MetricSlot::new(0, self.alpha));
62 let result = WindowResult {
63 start_ms: self.current_start,
64 end_ms: self.current_start.saturating_add(self.window_ms),
65 event_count: completed.event_count,
66 counter: completed.counter,
67 gauge: completed.gauge,
68 mean: completed.ddsketch.mean(),
69 min: completed.ddsketch.min(),
70 max: completed.ddsketch.max(),
71 p50: completed.ddsketch.quantile(0.50),
72 p99: completed.ddsketch.quantile(0.99),
73 };
74
75 self.current_start = timestamp_ms - (timestamp_ms % self.window_ms);
77 self.current.ddsketch.insert(value);
78 self.current.event_count = 1;
79
80 Some(result)
81 } else {
82 self.current.ddsketch.insert(value);
83 self.current.event_count += 1;
84 None
85 }
86 }
87
88 #[must_use]
90 pub fn flush(&mut self) -> WindowResult {
91 let completed = core::mem::replace(&mut self.current, MetricSlot::new(0, self.alpha));
92 let result = WindowResult {
93 start_ms: self.current_start,
94 end_ms: self.current_start.saturating_add(self.window_ms),
95 event_count: completed.event_count,
96 counter: completed.counter,
97 gauge: completed.gauge,
98 mean: completed.ddsketch.mean(),
99 min: completed.ddsketch.min(),
100 max: completed.ddsketch.max(),
101 p50: completed.ddsketch.quantile(0.50),
102 p99: completed.ddsketch.quantile(0.99),
103 };
104 self.current_start = 0;
105 result
106 }
107
108 #[inline]
110 #[must_use]
111 pub const fn current_count(&self) -> u64 {
112 self.current.event_count
113 }
114}
115
116pub struct SlidingWindow<const N: usize> {
124 buffer: [f64; N],
126 write_pos: usize,
128 count: usize,
130 sum: f64,
132 min: f64,
134 max: f64,
136}
137
138impl<const N: usize> SlidingWindow<N> {
139 #[must_use]
141 pub const fn new() -> Self {
142 Self {
143 buffer: [0.0; N],
144 write_pos: 0,
145 count: 0,
146 sum: 0.0,
147 min: f64::INFINITY,
148 max: f64::NEG_INFINITY,
149 }
150 }
151
152 pub fn push(&mut self, value: f64) {
161 assert!(N > 0, "SlidingWindow needs N > 0 (got a zero-width window)");
162 if self.count >= N {
163 self.sum -= self.buffer[self.write_pos];
165 }
166 self.buffer[self.write_pos] = value;
167 self.sum += value;
168 self.write_pos = (self.write_pos + 1) % N;
169 if self.count < N {
170 self.count += 1;
171 }
172
173 if self.count < N && value <= self.min {
175 self.min = value;
176 }
177 if self.count < N && value >= self.max {
178 self.max = value;
179 }
180 }
181
182 #[inline]
184 #[must_use]
185 pub fn mean(&self) -> f64 {
186 if self.count == 0 {
187 return 0.0;
188 }
189 self.sum / usize_f64(self.count)
190 }
191
192 #[inline]
194 #[must_use]
195 pub const fn sum(&self) -> f64 {
196 self.sum
197 }
198
199 #[inline]
201 #[must_use]
202 pub const fn len(&self) -> usize {
203 self.count
204 }
205
206 #[inline]
208 #[must_use]
209 pub const fn is_empty(&self) -> bool {
210 self.count == 0
211 }
212
213 #[must_use]
215 pub fn min(&self) -> f64 {
216 if self.count == 0 {
217 return f64::INFINITY;
218 }
219 let mut m = f64::INFINITY;
220 for i in 0..self.count {
221 m = alice_det_math::min64(m, self.buffer[i]);
222 }
223 m
224 }
225
226 #[must_use]
228 pub fn max(&self) -> f64 {
229 if self.count == 0 {
230 return f64::NEG_INFINITY;
231 }
232 let mut m = f64::NEG_INFINITY;
233 for i in 0..self.count {
234 m = alice_det_math::max64(m, self.buffer[i]);
235 }
236 m
237 }
238
239 #[must_use]
241 pub fn variance(&self) -> f64 {
242 if self.count == 0 {
243 return 0.0;
244 }
245 let mean = self.mean();
246 let mut sum_sq = 0.0;
247 for i in 0..self.count {
248 let d = self.buffer[i] - mean;
249 sum_sq += d * d;
250 }
251 sum_sq / usize_f64(self.count)
252 }
253
254 #[inline]
256 #[must_use]
257 pub fn std_dev(&self) -> f64 {
258 self.variance().sqrt()
259 }
260
261 pub const fn clear(&mut self) {
263 self.write_pos = 0;
264 self.count = 0;
265 self.sum = 0.0;
266 self.min = f64::INFINITY;
267 self.max = f64::NEG_INFINITY;
268 }
269}
270
271impl<const N: usize> Default for SlidingWindow<N> {
272 fn default() -> Self {
273 Self::new()
274 }
275}
276
277#[derive(Clone, Debug)]
283pub struct WindowResult {
284 pub start_ms: u64,
286 pub end_ms: u64,
288 pub event_count: u64,
290 pub counter: f64,
292 pub gauge: f64,
294 pub mean: f64,
296 pub min: f64,
298 pub max: f64,
300 pub p50: f64,
302 pub p99: f64,
304}
305
306pub struct HierarchicalRollup {
315 levels: [TumblingWindow; 3],
317 window_sizes: [u64; 3],
319 results: [Option<WindowResult>; 3],
321}
322
323impl HierarchicalRollup {
324 #[must_use]
328 pub fn new(level0_ms: u64, level1_ms: u64, level2_ms: u64, alpha: f64) -> Self {
329 Self {
330 levels: [
331 TumblingWindow::new(level0_ms, alpha),
332 TumblingWindow::new(level1_ms, alpha),
333 TumblingWindow::new(level2_ms, alpha),
334 ],
335 window_sizes: [level0_ms, level1_ms, level2_ms],
336 results: [None, None, None],
337 }
338 }
339
340 pub fn insert(&mut self, value: f64, timestamp_ms: u64) {
342 if let Some(r) = self.levels[0].insert(value, timestamp_ms) {
344 self.results[0] = Some(r);
345 if let Some(ref r0) = self.results[0] {
347 let mean = r0.mean;
348 if let Some(r1) = self.levels[1].insert(mean, timestamp_ms) {
349 self.results[1] = Some(r1);
350 if let Some(ref r1_res) = self.results[1] {
352 let mean1 = r1_res.mean;
353 if let Some(r2) = self.levels[2].insert(mean1, timestamp_ms) {
354 self.results[2] = Some(r2);
355 }
356 }
357 }
358 }
359 }
360 }
361
362 #[must_use]
364 pub const fn result(&self, level: usize) -> Option<&WindowResult> {
365 if level < 3 {
366 self.results[level].as_ref()
367 } else {
368 None
369 }
370 }
371
372 #[inline]
374 #[must_use]
375 pub const fn level_count(&self) -> usize {
376 3
377 }
378
379 #[must_use]
381 pub const fn window_size(&self, level: usize) -> u64 {
382 if level < 3 {
383 self.window_sizes[level]
384 } else {
385 0
386 }
387 }
388}
389
390#[cfg(test)]
395#[allow(clippy::float_cmp)]
396mod tests {
397 use super::*;
398 use crate::math::u64_f64;
399
400 #[test]
401 fn tumbling_window_basic() {
402 let mut w = TumblingWindow::new(100, 0.05);
403 assert!(w.insert(10.0, 10).is_none());
405 assert!(w.insert(20.0, 50).is_none());
406 assert_eq!(w.current_count(), 2);
407
408 let result = w.insert(30.0, 110).unwrap();
410 assert_eq!(result.start_ms, 10);
411 assert_eq!(result.end_ms, 110);
412 assert_eq!(result.event_count, 2);
413 }
414
415 #[test]
416 fn tumbling_window_flush() {
417 let mut w = TumblingWindow::new(1000, 0.05);
418 w.insert(5.0, 100);
419 w.insert(15.0, 200);
420 let result = w.flush();
421 assert_eq!(result.event_count, 2);
422 }
423
424 #[test]
425 fn tumbling_window_statistics() {
426 let mut w = TumblingWindow::new(100, 0.05);
427 for i in 0..10 {
428 w.insert(u64_f64(i + 1) * 10.0, i * 5);
429 }
430 let result = w.flush();
431 assert!(result.mean > 0.0);
432 assert!(result.min > 0.0);
433 assert!(result.max >= result.min);
434 }
435
436 #[test]
437 fn sliding_window_basic() {
438 let mut w = SlidingWindow::<4>::new();
439 w.push(10.0);
440 w.push(20.0);
441 w.push(30.0);
442 assert_eq!(w.len(), 3);
443 assert!((w.mean() - 20.0).abs() < 1e-6);
444 assert!((w.min() - 10.0).abs() < 1e-6);
445 assert!((w.max() - 30.0).abs() < 1e-6);
446 }
447
448 #[test]
449 fn sliding_window_overflow() {
450 let mut w = SlidingWindow::<3>::new();
451 w.push(10.0);
452 w.push(20.0);
453 w.push(30.0);
454 w.push(40.0); assert_eq!(w.len(), 3);
456 assert!((w.mean() - 30.0).abs() < 1e-6);
457 assert!((w.min() - 20.0).abs() < 1e-6);
458 }
459
460 #[test]
461 fn sliding_window_variance() {
462 let mut w = SlidingWindow::<4>::new();
463 w.push(2.0);
464 w.push(4.0);
465 w.push(4.0);
466 w.push(4.0);
467 assert!((w.variance() - 0.75).abs() < 1e-6);
469 }
470
471 #[test]
472 fn sliding_window_empty() {
473 let w = SlidingWindow::<8>::new();
474 assert!(w.is_empty());
475 assert!((w.mean() - 0.0).abs() < 1e-6);
476 assert!((w.variance() - 0.0).abs() < 1e-6);
477 }
478
479 #[test]
480 fn sliding_window_clear() {
481 let mut w = SlidingWindow::<8>::new();
482 w.push(1.0);
483 w.push(2.0);
484 w.clear();
485 assert!(w.is_empty());
486 assert_eq!(w.len(), 0);
487 }
488
489 #[test]
490 fn hierarchical_basic() {
491 let mut rollup = HierarchicalRollup::new(10, 50, 200, 0.05);
493 assert_eq!(rollup.level_count(), 3);
494 assert_eq!(rollup.window_size(0), 10);
495 assert_eq!(rollup.window_size(1), 50);
496 assert_eq!(rollup.window_size(2), 200);
497
498 for i in 0..20 {
500 rollup.insert(100.0, i);
501 }
502 assert!(rollup.result(0).is_some());
504 }
505
506 #[test]
507 fn hierarchical_cascading() {
508 let mut rollup = HierarchicalRollup::new(10, 30, 90, 0.05);
509 for i in 0..100 {
511 rollup.insert(50.0, i);
512 }
513 assert!(rollup.result(0).is_some());
515 }
516
517 #[test]
518 fn hierarchical_invalid_level() {
519 let rollup = HierarchicalRollup::new(10, 50, 200, 0.05);
520 assert!(rollup.result(5).is_none());
521 assert_eq!(rollup.window_size(99), 0);
522 }
523
524 #[test]
525 fn sliding_window_std_dev() {
526 let mut w = SlidingWindow::<3>::new();
527 w.push(10.0);
528 w.push(10.0);
529 w.push(10.0);
530 assert!((w.std_dev() - 0.0).abs() < 1e-6);
532 }
533
534 #[test]
535 fn sliding_window_default() {
536 let w = SlidingWindow::<16>::default();
537 assert!(w.is_empty());
538 }
539}