senax-common 0.4.8

Senax common library
Documentation
use fxhash::FxHashMap;
use std::{
    borrow::Borrow,
    hash::Hash,
    time::{Duration, SystemTime, UNIX_EPOCH},
};
use tokio::sync::RwLock;

pub struct StreamCounter<K> {
    tick: u64,
    divisor: u64,
    counter: RwLock<FxHashMap<u64, FxHashMap<K, usize>>>,
}

impl<K> StreamCounter<K>
where
    K: Hash + Eq,
{
    pub fn new(span: Duration, divisor: u64) -> Self {
        let span = span.as_millis() as u64;
        let tick = std::cmp::max(1, span / divisor);
        Self {
            tick,
            divisor,
            counter: Default::default(),
        }
    }

    pub async fn add(&self, key: K) {
        self.add_with_time(key, SystemTime::now()).await;
    }

    pub async fn add_with_time(&self, key: K, time: SystemTime) {
        let mut counter = self.counter.write().await;
        let time = time.duration_since(UNIX_EPOCH).unwrap().as_millis() as u64 / self.tick;
        let c = counter.entry(time).or_default().entry(key).or_default();
        *c += 1;
        let limit = time - self.divisor;
        counter.retain(|t, _| *t > limit);
    }

    pub async fn count<Q>(&self, key: &Q)
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        self.count_with_time(key, SystemTime::now()).await;
    }

    pub async fn count_with_time<Q>(&self, key: &Q, time: SystemTime) -> usize
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        let counter = self.counter.read().await;
        let time =
            time.duration_since(UNIX_EPOCH).unwrap().as_millis() as u64 / self.tick - self.divisor;
        let mut count = 0;
        for (t, map) in counter.iter() {
            if *t > time
                && let Some(c) = map.get(key.borrow())
            {
                count += c;
            }
        }
        count
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[tokio::test]
    async fn test() {
        let counter = StreamCounter::new(Duration::from_secs(60), 10);
        let now = UNIX_EPOCH + Duration::from_secs(600);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 1);
        assert_eq!(counter.count_with_time("dummy", now).await, 0);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 2);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 3);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 4);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 5);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 6);
        let now = now + Duration::from_secs(9);
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 7);
        let now = now + Duration::from_secs(2);
        counter.add_with_time("key".to_owned(), now).await;
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 8);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 9);
        let now = now + Duration::from_secs(10);
        counter.add_with_time("key".to_owned(), now).await;
        counter.add_with_time("key".to_owned(), now).await;
        assert_eq!(counter.count_with_time("key", now).await, 10);
    }
}