| use std::time::Instant; |
| |
| /// A metrics counter storing only latest `N` records. |
| pub struct MetricsCounter<const N: usize> { |
| /// Slots to store metrics. |
| slots: [(usize, Instant); N], |
| /// The slot of the oldest record. |
| /// Also the next slot to store the new record. |
| index: usize, |
| } |
| |
| impl<const N: usize> MetricsCounter<N> { |
| /// Creates a new counter with an initial value. |
| pub fn new(init: usize, init_at: Instant) -> Self { |
| assert!(N > 0, "number of slots must be greater than zero"); |
| Self { |
| slots: [(init, init_at); N], |
| index: 0, |
| } |
| } |
| |
| /// Adds record to the counter. |
| pub fn add(&mut self, data: usize, added_at: Instant) { |
| self.slots[self.index] = (data, added_at); |
| self.index = (self.index + 1) % N; |
| } |
| |
| /// Calculates per-second average rate of all slots. |
| pub fn rate(&self) -> f32 { |
| let latest = self.slots[self.index.checked_sub(1).unwrap_or(N - 1)]; |
| let oldest = self.slots[self.index]; |
| let duration = (latest.1 - oldest.1).as_secs_f32(); |
| let avg = (latest.0 - oldest.0) as f32 / duration; |
| if f32::is_nan(avg) { 0f32 } else { avg } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::MetricsCounter; |
| use std::time::{Duration, Instant}; |
| |
| #[test] |
| fn counter() { |
| let now = Instant::now(); |
| let mut counter = MetricsCounter::<3>::new(0, now); |
| assert_eq!(counter.rate(), 0f32); |
| counter.add(1, now + Duration::from_secs(1)); |
| assert_eq!(counter.rate(), 1f32); |
| counter.add(4, now + Duration::from_secs(2)); |
| assert_eq!(counter.rate(), 2f32); |
| counter.add(7, now + Duration::from_secs(3)); |
| assert_eq!(counter.rate(), 3f32); |
| counter.add(12, now + Duration::from_secs(4)); |
| assert_eq!(counter.rate(), 4f32); |
| } |
| |
| #[test] |
| #[should_panic(expected = "number of slots must be greater than zero")] |
| fn counter_zero_slot() { |
| let _counter = MetricsCounter::<0>::new(0, Instant::now()); |
| } |
| } |