use std::sync::Mutex;
use crate::metrics::MetricsSink;
#[derive(Debug, Default)]
pub struct RecordingMetrics {
counters: Mutex<Vec<CounterEvent>>,
histograms: Mutex<Vec<HistogramEvent>>,
gauges: Mutex<Vec<GaugeEvent>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CounterEvent {
pub name: String,
pub value: u64,
pub labels: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HistogramEvent {
pub name: String,
pub value: f64,
pub labels: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GaugeEvent {
pub name: String,
pub value: f64,
pub labels: Vec<(String, String)>,
}
impl RecordingMetrics {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn counters(&self) -> Vec<CounterEvent> {
self.counters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
#[must_use]
pub fn histograms(&self) -> Vec<HistogramEvent> {
self.histograms
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
#[must_use]
pub fn counter_sum(&self, name: &str) -> u64 {
self.counters()
.into_iter()
.filter(|e| e.name == name)
.map(|e| e.value)
.sum()
}
#[must_use]
pub fn saw(&self, name: &str) -> bool {
self.counters().iter().any(|e| e.name == name)
|| self.histograms().iter().any(|e| e.name == name)
|| self
.gauges
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.iter()
.any(|e| e.name == name)
}
}
impl MetricsSink for RecordingMetrics {
fn counter(&self, name: &str, value: u64, labels: &[(&str, &str)]) {
self.counters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(CounterEvent {
name: name.to_owned(),
value,
labels: owned_labels(labels),
});
}
fn histogram(&self, name: &str, value: f64, labels: &[(&str, &str)]) {
self.histograms
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(HistogramEvent {
name: name.to_owned(),
value,
labels: owned_labels(labels),
});
}
fn gauge(&self, name: &str, value: f64, labels: &[(&str, &str)]) {
self.gauges
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(GaugeEvent {
name: name.to_owned(),
value,
labels: owned_labels(labels),
});
}
}
fn owned_labels(labels: &[(&str, &str)]) -> Vec<(String, String)> {
labels
.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metrics::{METRIC_TURNS_TOTAL, record_turn};
#[test]
fn captures_turn() {
let rec = RecordingMetrics::new();
record_turn(&rec, "ok", 2, 5.0);
assert!(rec.saw(METRIC_TURNS_TOTAL));
assert_eq!(rec.counter_sum(METRIC_TURNS_TOTAL), 1);
assert!(!rec.histograms().is_empty());
}
}