rust_observer 0.2.2

Express telemetry rust SDK
Documentation
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::RwLock;

static HTTP_METRICS: Lazy<HttpMetricsData> = Lazy::new(HttpMetricsData::default);

#[derive(Debug, Default)]
pub struct HttpMetricsData {
    pub method_count: RwLock<HashMap<String, u64>>,
    pub status_code_distribution: RwLock<HashMap<u16, u64>>,
    pub interval_counter: AtomicU64,
    pub request_duration: RwLock<HashMap<(String, u16), (u64, u64)>>, // (Method, Status Code) -> (Total Duration, Count)
}

impl HttpMetricsData {
    pub async fn increment_counter(&self) {
        self.interval_counter.fetch_add(1, Ordering::Relaxed);
    }

    pub async fn update_method_count(&self, method: &str) {
        let mut methods = self.method_count.write().await;
        let counter = methods.entry(method.to_string()).or_insert(0);
        *counter += 1;
    }

    pub async fn update_status_code(&self, status_code: u16) {
        let mut status_codes = self.status_code_distribution.write().await;
        let counter = status_codes.entry(status_code).or_insert(0);
        *counter += 1;
    }

    pub async fn update_request_duration(&self, method: &str, status_code: u16, duration: u64) {
        let mut durations = self.request_duration.write().await;
        let entry = durations
            .entry((method.to_string(), status_code))
            .or_insert((0, 0));
        entry.0 += duration;
        entry.1 += 1;
    }

    pub async fn flush(
        &self,
    ) -> (
        HashMap<String, u64>,
        HashMap<u16, u64>,
        u64,
        HashMap<(String, u16), u64>,
    ) {
        let mut methods = self.method_count.write().await;
        let mut status_codes = self.status_code_distribution.write().await;
        let mut durations = self.request_duration.write().await;

        let method_count_snapshot = methods.clone();
        let status_codes_snapshot = status_codes.clone();
        let rpi = self.interval_counter.swap(0, Ordering::Relaxed);

        let average_durations: HashMap<(String, u16), u64> = durations
            .iter()
            .map(|(key, &(total_duration, count))| (key.clone(), total_duration / count))
            .collect();

        methods.clear();
        status_codes.clear();
        durations.clear();

        (
            method_count_snapshot,
            status_codes_snapshot,
            rpi,
            average_durations,
        )
    }
}

pub struct HttpMetrics;

impl HttpMetrics {
    pub async fn increment_counter() {
        HTTP_METRICS.increment_counter().await;
    }

    pub async fn update_method_count(method: &str) {
        HTTP_METRICS.update_method_count(method).await;
    }

    pub async fn update_status_code(status_code: u16) {
        HTTP_METRICS.update_status_code(status_code).await;
    }

    pub async fn update_request_duration(method: &str, status_code: u16, duration: u64) {
        HTTP_METRICS
            .update_request_duration(method, status_code, duration)
            .await;
    }

    pub async fn flush() -> Option<(
        HashMap<String, u64>,
        HashMap<u16, u64>,
        u64,
        HashMap<(String, u16), u64>,
    )> {
        let result = HTTP_METRICS.flush().await;
        Some(result)
    }
}

#[derive(Debug, Clone)]
pub struct HttpMetricsSnapshot {
    pub requests_per_interval: u64,
    pub method_count: HashMap<String, u64>,
    pub status_code_distribution: HashMap<u16, u64>,
    pub average_request_duration: HashMap<(String, u16), u64>,
}