use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetricKind {
Counter,
Gauge,
Histogram,
}
pub trait MetricKey: fmt::Debug + Send + Sync + 'static {
fn name(&self) -> &str;
}
pub trait MetricsCollector: Send + Sync {
fn record(&self, key: &dyn MetricKey, kind: MetricKind, value: f64);
fn record_counter(&self, key: &dyn MetricKey, value: u64) {
self.record(key, MetricKind::Counter, value as f64);
}
fn record_gauge(&self, key: &dyn MetricKey, value: f64) {
self.record(key, MetricKind::Gauge, value);
}
fn record_histogram(&self, key: &dyn MetricKey, value: f64) {
self.record(key, MetricKind::Histogram, value);
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NullCollector;
impl MetricsCollector for NullCollector {
fn record(&self, _key: &dyn MetricKey, _kind: MetricKind, _value: f64) {}
}
impl MetricsCollector for Arc<dyn MetricsCollector> {
fn record(&self, key: &dyn MetricKey, kind: MetricKind, value: f64) {
(**self).record(key, kind, value);
}
}
#[derive(Debug, Default)]
pub struct CountingCollector {
pub calls: AtomicU64,
pub counter_sum: AtomicU64,
}
impl CountingCollector {
pub fn new() -> Self {
Self::default()
}
pub fn shared(self) -> Arc<Self> {
Arc::new(self)
}
}
impl MetricsCollector for CountingCollector {
fn record(&self, _key: &dyn MetricKey, kind: MetricKind, value: f64) {
self.calls.fetch_add(1, Ordering::Relaxed);
if matches!(kind, MetricKind::Counter) && value >= 0.0 && value.is_finite() {
self.counter_sum.fetch_add(value as u64, Ordering::Relaxed);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_collector_smoke() {
let c = NullCollector;
c.record_counter(&TestKey, 1);
c.record_gauge(&TestKey, 1.0);
c.record_histogram(&TestKey, 0.5);
}
#[test]
fn counting_collector_sums() {
let c = CountingCollector::new();
c.record_counter(&TestKey, 3);
c.record_counter(&TestKey, 2);
assert_eq!(c.counter_sum.load(Ordering::Relaxed), 5);
assert_eq!(c.calls.load(Ordering::Relaxed), 2);
}
#[derive(Debug)]
struct TestKey;
impl MetricKey for TestKey {
fn name(&self) -> &str {
"test"
}
}
}