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) { 0f32 } else { avg }
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::MetricsCounter;
41    use std::time::{Duration, Instant};
42
43    #[test]
44    fn counter() {
45        let now = Instant::now();
46        let mut counter = MetricsCounter::<3>::new(0, now);
47        assert_eq!(counter.rate(), 0f32);
48        counter.add(1, now + Duration::from_secs(1));
49        assert_eq!(counter.rate(), 1f32);
50        counter.add(4, now + Duration::from_secs(2));
51        assert_eq!(counter.rate(), 2f32);
52        counter.add(7, now + Duration::from_secs(3));
53        assert_eq!(counter.rate(), 3f32);
54        counter.add(12, now + Duration::from_secs(4));
55        assert_eq!(counter.rate(), 4f32);
56    }
57
58    #[test]
59    #[should_panic(expected = "number of slots must be greater than zero")]
60    fn counter_zero_slot() {
61        let _counter = MetricsCounter::<0>::new(0, Instant::now());
62    }
63}