~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
137 lines · 4.5 KB Raw History
use std::sync::atomic::{AtomicU64, Ordering};

use crate::packet::DropReason;

#[derive(Default)]
pub struct Stats {
    pub tun_read_packets: AtomicU64,
    pub tun_write_packets: AtomicU64,
    pub datagrams_sent: AtomicU64,
    pub datagrams_received: AtomicU64,
    pub bytes_sent: AtomicU64,
    pub bytes_received: AtomicU64,
    pub dropped_queue_full: AtomicU64,
    pub dropped_no_connection: AtomicU64,
    pub dropped_too_large: AtomicU64,
    pub dropped_send_failed: AtomicU64,
    pub dropped_tun_write_failed: AtomicU64,
    pub connections_dialed: AtomicU64,
    pub connections_accepted: AtomicU64,
    pub connections_refused: AtomicU64,
    pub dial_failures: AtomicU64,
    pub route_sync_failures: AtomicU64,
    pub node_list_updates: AtomicU64,
    dropped_by_reason: [AtomicU64; DropReason::ALL.len()],
}

impl Stats {
    pub fn add(counter: &AtomicU64, n: u64) {
        counter.fetch_add(n, Ordering::Relaxed);
    }

    pub fn bump(counter: &AtomicU64) {
        counter.fetch_add(1, Ordering::Relaxed);
    }

    pub fn dropped(&self, reason: DropReason) {
        Stats::bump(&self.dropped_by_reason[reason.index()]);
    }

    pub fn dropped_count(&self, reason: DropReason) -> u64 {
        self.dropped_by_reason[reason.index()].load(Ordering::Relaxed)
    }

    pub fn counters(&self) -> Vec<(String, u64)> {
        let load = |c: &AtomicU64| c.load(Ordering::Relaxed);
        let mut out = vec![
            ("tun_read_packets".to_string(), load(&self.tun_read_packets)),
            (
                "tun_write_packets".to_string(),
                load(&self.tun_write_packets),
            ),
            ("datagrams_sent".to_string(), load(&self.datagrams_sent)),
            (
                "datagrams_received".to_string(),
                load(&self.datagrams_received),
            ),
            ("bytes_sent".to_string(), load(&self.bytes_sent)),
            ("bytes_received".to_string(), load(&self.bytes_received)),
            (
                "dropped_queue_full".to_string(),
                load(&self.dropped_queue_full),
            ),
            (
                "dropped_no_connection".to_string(),
                load(&self.dropped_no_connection),
            ),
            (
                "dropped_too_large".to_string(),
                load(&self.dropped_too_large),
            ),
            (
                "dropped_send_failed".to_string(),
                load(&self.dropped_send_failed),
            ),
            (
                "dropped_tun_write_failed".to_string(),
                load(&self.dropped_tun_write_failed),
            ),
            (
                "connections_dialed".to_string(),
                load(&self.connections_dialed),
            ),
            (
                "connections_accepted".to_string(),
                load(&self.connections_accepted),
            ),
            (
                "connections_refused".to_string(),
                load(&self.connections_refused),
            ),
            ("dial_failures".to_string(), load(&self.dial_failures)),
            (
                "route_sync_failures".to_string(),
                load(&self.route_sync_failures),
            ),
            (
                "node_list_updates".to_string(),
                load(&self.node_list_updates),
            ),
        ];
        for reason in DropReason::ALL {
            out.push((
                format!("dropped_{}", reason.label()),
                self.dropped_count(reason),
            ));
        }
        out
    }

    pub fn prometheus(&self) -> String {
        let mut text = String::new();
        for (name, value) in self.counters() {
            text.push_str(&format!(
                "# TYPE socialweb_meshd_{name}_total counter\nsocialweb_meshd_{name}_total {value}\n"
            ));
        }
        text
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn every_drop_reason_has_its_own_counter_in_the_metrics_text() {
        let stats = Stats::default();
        stats.dropped(DropReason::UnknownDestination);
        stats.dropped(DropReason::UnknownDestination);
        stats.dropped(DropReason::UnknownPeer);
        assert_eq!(stats.dropped_count(DropReason::UnknownDestination), 2);
        let text = stats.prometheus();
        assert!(text.contains("socialweb_meshd_dropped_unknown_destination_total 2\n"));
        assert!(text.contains("socialweb_meshd_dropped_unknown_peer_total 1\n"));
        assert!(text.contains("socialweb_meshd_dropped_malformed_total 0\n"));
    }
}