metrics-procession 0.1.0

A compact in-memory collector for metrics.rs
Documentation
use std::sync::{Arc, Mutex, MutexGuard};

use metrics::{CounterFn, GaugeFn, HistogramFn, Recorder};

use crate::{
    event::{Entry, Op},
    procession::Procession,
};

#[derive(Debug, Clone, Default)]
pub struct ProcessionRecorder(Arc<Mutex<Procession>>);

impl ProcessionRecorder {
    pub fn lock(&self) -> MutexGuard<Procession> {
        self.0.lock().unwrap_or_else(|e| e.into_inner())
    }
    pub fn memory_size(&self) -> usize {
        self.0.lock().unwrap().memory_size()
    }
}

impl Recorder for ProcessionRecorder {
    fn describe_counter(
        &self,
        _: metrics::KeyName,
        _: Option<metrics::Unit>,
        _: metrics::SharedString,
    ) {
    }

    fn describe_gauge(
        &self,
        _: metrics::KeyName,
        _: Option<metrics::Unit>,
        _: metrics::SharedString,
    ) {
    }

    fn describe_histogram(
        &self,
        _: metrics::KeyName,
        _: Option<metrics::Unit>,
        _: metrics::SharedString,
    ) {
    }

    fn register_counter(&self, key: &metrics::Key, _: &metrics::Metadata<'_>) -> metrics::Counter {
        let label = self.0.lock().unwrap().ensure_label(key);
        metrics::Counter::from_arc(Arc::new(Counter(label, self.clone())))
    }

    fn register_gauge(&self, key: &metrics::Key, _: &metrics::Metadata<'_>) -> metrics::Gauge {
        let label = self.0.lock().unwrap().ensure_label(key);
        metrics::Gauge::from_arc(Arc::new(Gauge(label, self.clone())))
    }

    fn register_histogram(
        &self,
        key: &metrics::Key,
        _: &metrics::Metadata<'_>,
    ) -> metrics::Histogram {
        let label = self.0.lock().unwrap().ensure_label(key);
        metrics::Histogram::from_arc(Arc::new(Histo(label, self.clone())))
    }
}

struct Counter(u16, ProcessionRecorder);

impl CounterFn for Counter {
    fn increment(&self, value: u64) {
        self.insert(value, Op::Add);
    }

    fn absolute(&self, value: u64) {
        self.insert(value, Op::Set);
    }
}

impl Counter {
    pub fn insert(&self, value: u64, op: Op) {
        let Ok(value) = u32::try_from(value) else {
            eprintln!("value has exceeded a u32, skipping event");
            return;
        };
        self.1
            .0
            .lock()
            .unwrap()
            .insert_entry(Entry::Counter { value, op }, self.0);
    }
}

struct Gauge(u16, ProcessionRecorder);

impl GaugeFn for Gauge {
    fn increment(&self, value: f64) {
        self.1.0.lock().unwrap().insert_entry(
            Entry::Gauge {
                value: value as f32,
                op: Op::Add,
            },
            self.0,
        );
    }

    fn decrement(&self, value: f64) {
        self.1.0.lock().unwrap().insert_entry(
            Entry::Gauge {
                value: value as f32,
                op: Op::Sub,
            },
            self.0,
        );
    }

    fn set(&self, value: f64) {
        self.1.0.lock().unwrap().insert_entry(
            Entry::Gauge {
                value: value as f32,
                op: Op::Set,
            },
            self.0,
        );
    }
}

struct Histo(u16, ProcessionRecorder);

impl HistogramFn for Histo {
    fn record(&self, value: f64) {
        self.1.0.lock().unwrap().insert_entry(
            Entry::Histogram {
                value: value as f32,
            },
            self.0,
        );
    }
}

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

    #[test]
    fn install_and_emit() {
        let recorder = ProcessionRecorder::default();
        metrics::with_local_recorder(&recorder, || {
            let ct = metrics::counter!("one_counter");
            let ct2 = metrics::counter!("with_label", "label" => "this-one");
            let g = metrics::gauge!("one_gauge");
            let g2 = metrics::gauge!("with_labels", "label1" => "value1", "label2" => "value2");
            let h = metrics::histogram!("one_histo");
            let h2 = metrics::histogram!("with_label", "tid" => format!("{:?}", std::thread::current().id()));

            for i in 0..1000 {
                ct.increment(1);
                ct2.increment(2);
                g.set(i as f64);
                g2.set((i / 2) as f64);
                h.record(i as f64);
                h2.record((i / 2) as f64);
            }
        });
    }
}