Skip to main content

cbtop/
ring_buffer.rs

1//! SIMD-optimized ring buffer for time-series data
2//!
3//! # Design
4//!
5//! - Bounded capacity (Muda: no unbounded growth)
6//! - Zero-copy iteration
7//! - Cache-friendly layout
8//!
9//! # Reference
10//!
11//! Ohno, T. (1988). "Toyota Production System" - Waste elimination
12
13use std::collections::VecDeque;
14
15/// Ring buffer with fixed capacity
16#[derive(Debug, Clone)]
17pub struct RingBuffer<T> {
18    data: VecDeque<T>,
19    capacity: usize,
20}
21
22impl<T> RingBuffer<T> {
23    /// Create new ring buffer with specified capacity
24    pub fn new(capacity: usize) -> Self {
25        Self {
26            data: VecDeque::with_capacity(capacity),
27            capacity,
28        }
29    }
30
31    /// Push value, evicting oldest if at capacity
32    pub fn push(&mut self, value: T) {
33        if self.data.len() >= self.capacity {
34            self.data.pop_front();
35        }
36        self.data.push_back(value);
37    }
38
39    /// Get number of elements
40    pub fn len(&self) -> usize {
41        self.data.len()
42    }
43
44    /// Check if empty
45    pub fn is_empty(&self) -> bool {
46        self.data.is_empty()
47    }
48
49    /// Get capacity
50    pub fn capacity(&self) -> usize {
51        self.capacity
52    }
53
54    /// Get latest value
55    pub fn back(&self) -> Option<&T> {
56        self.data.back()
57    }
58
59    /// Get oldest value
60    pub fn front(&self) -> Option<&T> {
61        self.data.front()
62    }
63
64    /// Clear all data
65    pub fn clear(&mut self) {
66        self.data.clear();
67    }
68
69    /// Iterate from oldest to newest
70    pub fn iter(&self) -> impl Iterator<Item = &T> {
71        self.data.iter()
72    }
73
74    /// Get value at index (0 = oldest)
75    pub fn get(&self, index: usize) -> Option<&T> {
76        self.data.get(index)
77    }
78
79    /// Get last N values as slice (newest last)
80    pub fn last_n(&self, n: usize) -> impl Iterator<Item = &T> {
81        let skip = self.data.len().saturating_sub(n);
82        self.data.iter().skip(skip)
83    }
84}
85
86impl<T: Clone> RingBuffer<T> {
87    /// Get all values as a Vec (oldest first)
88    pub fn to_vec(&self) -> Vec<T> {
89        self.data.iter().cloned().collect()
90    }
91}
92
93impl<T: Copy + Default> RingBuffer<T> {
94    /// Get values as contiguous slice for SIMD operations
95    /// Returns (slice1, slice2) where slice2 may be empty
96    pub fn as_slices(&self) -> (&[T], &[T]) {
97        self.data.as_slices()
98    }
99}
100
101impl<T> Default for RingBuffer<T> {
102    fn default() -> Self {
103        Self::new(64)
104    }
105}
106
107/// Statistics over ring buffer (SIMD-friendly when possible)
108impl RingBuffer<f64> {
109    /// Calculate mean of all values
110    pub fn mean(&self) -> f64 {
111        if self.is_empty() {
112            return 0.0;
113        }
114        let sum: f64 = self.data.iter().sum();
115        sum / self.data.len() as f64
116    }
117
118    /// Calculate min value
119    pub fn min(&self) -> f64 {
120        self.data.iter().copied().fold(f64::INFINITY, f64::min)
121    }
122
123    /// Calculate max value
124    pub fn max(&self) -> f64 {
125        self.data.iter().copied().fold(f64::NEG_INFINITY, f64::max)
126    }
127
128    /// Calculate percentile (0.0 - 1.0)
129    pub fn percentile(&self, p: f64) -> f64 {
130        if self.is_empty() {
131            return 0.0;
132        }
133        let mut sorted: Vec<f64> = self.data.iter().copied().collect();
134        sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
135
136        let idx = ((sorted.len() - 1) as f64 * p.clamp(0.0, 1.0)) as usize;
137        sorted[idx]
138    }
139
140    /// Calculate standard deviation (delegates to batuta-common).
141    pub fn std_dev(&self) -> f64 {
142        let data: Vec<f64> = self.data.iter().copied().collect();
143        batuta_common::math::std_dev(&data)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_ring_buffer_push() {
153        let mut rb = RingBuffer::new(3);
154        rb.push(1);
155        rb.push(2);
156        rb.push(3);
157        assert_eq!(rb.len(), 3);
158
159        rb.push(4);
160        assert_eq!(rb.len(), 3);
161        assert_eq!(rb.front(), Some(&2));
162        assert_eq!(rb.back(), Some(&4));
163    }
164
165    #[test]
166    fn test_ring_buffer_iter() {
167        let mut rb = RingBuffer::new(3);
168        rb.push(1);
169        rb.push(2);
170        rb.push(3);
171
172        let values: Vec<_> = rb.iter().copied().collect();
173        assert_eq!(values, vec![1, 2, 3]);
174    }
175
176    #[test]
177    fn test_ring_buffer_last_n() {
178        let mut rb = RingBuffer::new(5);
179        for i in 1..=5 {
180            rb.push(i);
181        }
182
183        let last3: Vec<_> = rb.last_n(3).copied().collect();
184        assert_eq!(last3, vec![3, 4, 5]);
185    }
186
187    #[test]
188    fn test_ring_buffer_statistics() {
189        let mut rb: RingBuffer<f64> = RingBuffer::new(5);
190        rb.push(1.0);
191        rb.push(2.0);
192        rb.push(3.0);
193        rb.push(4.0);
194        rb.push(5.0);
195
196        assert_eq!(rb.mean(), 3.0);
197        assert_eq!(rb.min(), 1.0);
198        assert_eq!(rb.max(), 5.0);
199        assert_eq!(rb.percentile(0.5), 3.0);
200    }
201
202    #[test]
203    fn test_ring_buffer_empty() {
204        let rb: RingBuffer<f64> = RingBuffer::new(5);
205        assert!(rb.is_empty());
206        assert_eq!(rb.mean(), 0.0);
207        assert_eq!(rb.percentile(0.5), 0.0);
208    }
209}