use crate::{
Clock, Counter, ExponentiallyDecayingReservoir, Gauge, Histogram, Meter, MetricId, Timer,
};
use parking_lot::Mutex;
use std::collections::hash_map::Entry;
use std::collections::{hash_map, HashMap};
use std::sync::Arc;
#[derive(Clone)]
pub enum Metric {
Counter(Arc<Counter>),
Meter(Arc<Meter>),
Gauge(Arc<dyn Gauge>),
Histogram(Arc<Histogram>),
Timer(Arc<Timer>),
}
pub struct MetricRegistry {
metrics: Mutex<Arc<HashMap<Arc<MetricId>, Metric>>>,
clock: Arc<dyn Clock>,
}
impl Default for MetricRegistry {
fn default() -> Self {
MetricRegistry {
metrics: Mutex::new(Arc::new(HashMap::new())),
clock: crate::SYSTEM_CLOCK.clone(),
}
}
}
impl MetricRegistry {
#[inline]
pub fn new() -> MetricRegistry {
MetricRegistry::default()
}
#[inline]
pub fn set_clock(&mut self, clock: Arc<dyn Clock>) {
self.clock = clock;
}
#[inline]
pub fn clock(&self) -> &Arc<dyn Clock> {
&self.clock
}
pub fn counter_with<T, F>(&self, id: T, make_counter: F) -> Arc<Counter>
where
T: Into<MetricId>,
F: FnOnce() -> Counter,
{
match Arc::make_mut(&mut self.metrics.lock()).entry(Arc::new(id.into())) {
Entry::Occupied(e) => match e.get() {
Metric::Counter(c) => c.clone(),
_ => panic!("metric already registered as a non-counter: {:?}", e.key()),
},
Entry::Vacant(e) => {
let counter = Arc::new(make_counter());
e.insert(Metric::Counter(counter.clone()));
counter
}
}
}
pub fn counter<T>(&self, id: T) -> Arc<Counter>
where
T: Into<MetricId>,
{
self.counter_with(id, Counter::default)
}
pub fn meter_with<T, F>(&self, id: T, make_meter: F) -> Arc<Meter>
where
T: Into<MetricId>,
F: FnOnce() -> Meter,
{
match Arc::make_mut(&mut self.metrics.lock()).entry(Arc::new(id.into())) {
Entry::Occupied(e) => match e.get() {
Metric::Meter(m) => m.clone(),
_ => panic!("metric already registered as a non-meter: {:?}", e.key()),
},
Entry::Vacant(e) => {
let meter = Arc::new(make_meter());
e.insert(Metric::Meter(meter.clone()));
meter
}
}
}
pub fn meter<T>(&self, id: T) -> Arc<Meter>
where
T: Into<MetricId>,
{
self.meter_with(id, || Meter::new_with(self.clock.clone()))
}
pub fn gauge_with<T, F, G>(&self, id: T, make_gauge: F) -> Arc<dyn Gauge>
where
T: Into<MetricId>,
F: FnOnce() -> G,
G: Gauge,
{
match Arc::make_mut(&mut self.metrics.lock()).entry(Arc::new(id.into())) {
Entry::Occupied(e) => match e.get() {
Metric::Gauge(m) => m.clone(),
_ => panic!("metric already registered as a non-gauge: {:?}", e.key()),
},
Entry::Vacant(e) => {
let gauge = Arc::new(make_gauge());
e.insert(Metric::Gauge(gauge.clone()));
gauge
}
}
}
pub fn gauge<T, G>(&self, id: T, gauge: G) -> Arc<dyn Gauge>
where
T: Into<MetricId>,
G: Gauge,
{
self.gauge_with(id, || gauge)
}
pub fn replace_gauge<T, G>(&self, id: T, gauge: G)
where
T: Into<MetricId>,
G: Gauge,
{
Arc::make_mut(&mut self.metrics.lock())
.insert(Arc::new(id.into()), Metric::Gauge(Arc::new(gauge)));
}
pub fn histogram_with<T, F>(&self, id: T, make_histogram: F) -> Arc<Histogram>
where
T: Into<MetricId>,
F: FnOnce() -> Histogram,
{
match Arc::make_mut(&mut self.metrics.lock()).entry(Arc::new(id.into())) {
Entry::Occupied(e) => match e.get() {
Metric::Histogram(m) => m.clone(),
_ => panic!(
"metric already registered as a non-histogram: {:?}",
e.key()
),
},
Entry::Vacant(e) => {
let histogram = Arc::new(make_histogram());
e.insert(Metric::Histogram(histogram.clone()));
histogram
}
}
}
pub fn histogram<T>(&self, id: T) -> Arc<Histogram>
where
T: Into<MetricId>,
{
self.histogram_with(id, || {
Histogram::new(ExponentiallyDecayingReservoir::new_with(self.clock.clone()))
})
}
pub fn timer_with<T, F>(&self, id: T, make_timer: F) -> Arc<Timer>
where
T: Into<MetricId>,
F: FnOnce() -> Timer,
{
match Arc::make_mut(&mut self.metrics.lock()).entry(Arc::new(id.into())) {
Entry::Occupied(e) => match e.get() {
Metric::Timer(m) => m.clone(),
_ => panic!("metric already registered as a non-timer: {:?}", e.key()),
},
Entry::Vacant(e) => {
let timer = Arc::new(make_timer());
e.insert(Metric::Timer(timer.clone()));
timer
}
}
}
pub fn timer<T>(&self, id: T) -> Arc<Timer>
where
T: Into<MetricId>,
{
self.timer_with(id, || {
Timer::new_with(
ExponentiallyDecayingReservoir::new_with(self.clock.clone()),
self.clock.clone(),
)
})
}
pub fn remove<T>(&self, id: T) -> Option<Metric>
where
T: Into<MetricId>,
{
Arc::make_mut(&mut self.metrics.lock()).remove(&id.into())
}
pub fn metrics(&self) -> Metrics {
Metrics(self.metrics.lock().clone())
}
}
pub struct Metrics(Arc<HashMap<Arc<MetricId>, Metric>>);
impl Metrics {
pub fn iter(&self) -> MetricsIter<'_> {
MetricsIter(self.0.iter())
}
}
impl<'a> IntoIterator for &'a Metrics {
type Item = (&'a MetricId, &'a Metric);
type IntoIter = MetricsIter<'a>;
fn into_iter(self) -> MetricsIter<'a> {
self.iter()
}
}
pub struct MetricsIter<'a>(hash_map::Iter<'a, Arc<MetricId>, Metric>);
impl<'a> Iterator for MetricsIter<'a> {
type Item = (&'a MetricId, &'a Metric);
#[inline]
fn next(&mut self) -> Option<(&'a MetricId, &'a Metric)> {
self.0.next().map(|(k, v)| (&**k, v))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> ExactSizeIterator for MetricsIter<'a> {}
#[cfg(test)]
mod test {
use crate::{MetricId, MetricRegistry};
use serde_value::Value;
use std::time::Duration;
#[test]
fn first_metric_wins() {
let registry = MetricRegistry::new();
let a = registry.counter("counter");
let b = registry.counter("counter");
a.add(1);
assert_eq!(b.count(), 1);
registry.gauge("gauge", || 1);
let b = registry.gauge("gauge", || 2);
assert_eq!(b.value(), Value::I32(1));
let a = registry.histogram("histogram");
let b = registry.histogram("histogram");
a.update(0);
assert_eq!(b.count(), 1);
let a = registry.meter("meter");
let b = registry.meter("meter");
a.mark(1);
assert_eq!(b.count(), 1);
let a = registry.timer("timer");
let b = registry.timer("timer");
a.update(Duration::from_secs(0));
assert_eq!(b.count(), 1);
}
#[test]
fn metrics_returns_snapshot() {
let registry = MetricRegistry::new();
registry.counter("counter");
let metrics = registry.metrics();
registry.timer("timer");
let metrics = metrics.iter().collect::<Vec<_>>();
assert_eq!(metrics.len(), 1);
assert_eq!(metrics[0].0, &MetricId::new("counter"));
}
#[test]
fn tagged_distinct_from_untagged() {
let registry = MetricRegistry::new();
let a = registry.counter("counter");
let b = registry.counter(MetricId::new("counter").with_tag("foo", "bar"));
a.inc();
assert_eq!(b.count(), 0);
}
}