#
cargo/util/
counter.rs

    1use std::time::Instant;
    2
    3/// A metrics counter storing only latest `N` records.
    4pub struct MetricsCounter<const N: usize> {
    5    /// Slots to store metrics.
    6    slots: [(usize, Instant); N],
    7    /// The slot of the oldest record.
    8    /// Also the next slot to store the new record.
    9    index: usize,
    10}
    11
    12impl<const N: usize> MetricsCounter<N> {
    13    /// Creates a new counter with an initial value.
    14    pub fn new(init: usize, init_at: Instant) -> Self {
    15        assert!(N > 0, "number of slots must be greater than zero");
    16        Self {
    17            slots: [(init, init_at); N],
    18            index: 0,
    19        }
    20    }
    21
    22    /// Adds record to the counter.
    23    pub fn add(&mut self, data: usize, added_at: Instant) {
    24        self.slots[self.index] = (data, added_at);
    25        self.index = (self.index + 1) % N;
    26    }
    27
    28    /// Calculates per-second average rate of all slots.
    29    pub fn rate(&self) -> f32 {
    30        let latest = self.slots[self.index.checked_sub(1).unwrap_or(N - 1)];
    31        let oldest = self.slots[self.index];
    32        let duration = (latest.1 - oldest.1).as_secs_f32();
    33        let avg = (latest.0 - oldest.0) as f32 / duration;
    34        if f32::is_nan(avg) {
    35            0f32
    36        } else {
    37            avg
    38        }
    39    }
    40}
    41
    42#[cfg(test)]
    43mod tests {
    44    use super::MetricsCounter;
    45    use std::time::{Duration, Instant};
    46
    47    #[test]
    48    fn counter() {
    49        let now = Instant::now();
    50        let mut counter = MetricsCounter::<3>::new(0, now);
    51        assert_eq!(counter.rate(), 0f32);
    52        counter.add(1, now + Duration::from_secs(1));
    53        assert_eq!(counter.rate(), 1f32);
    54        counter.add(4, now + Duration::from_secs(2));
    55        assert_eq!(counter.rate(), 2f32);
    56        counter.add(7, now + Duration::from_secs(3));
    57        assert_eq!(counter.rate(), 3f32);
    58        counter.add(12, now + Duration::from_secs(4));
    59        assert_eq!(counter.rate(), 4f32);
    60    }
    61
    62    #[test]
    63    #[should_panic(expected = "number of slots must be greater than zero")]
    64    fn counter_zero_slot() {
    65        let _counter = MetricsCounter::<0>::new(0, Instant::now());
    66    }
    67}