use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder};
use super::{MetricType, TypedMetric};
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::collections::HashMap;
use std::sync::Arc;
pub struct Family<S, M, C = fn() -> M> {
metrics: Arc<RwLock<HashMap<S, M>>>,
constructor: C,
}
impl<S: std::fmt::Debug, M: std::fmt::Debug, C> std::fmt::Debug for Family<S, M, C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Family")
.field("metrics", &self.metrics)
.finish()
}
}
pub trait MetricConstructor<M> {
fn new_metric(&self) -> M;
}
impl<M, F: Fn() -> M> MetricConstructor<M> for F {
fn new_metric(&self) -> M {
self()
}
}
impl<S: Clone + std::hash::Hash + Eq, M: Default> Default for Family<S, M> {
fn default() -> Self {
Self {
metrics: Arc::new(RwLock::new(Default::default())),
constructor: M::default,
}
}
}
impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
pub fn new_with_constructor(constructor: C) -> Self {
Self {
metrics: Arc::new(RwLock::new(Default::default())),
constructor,
}
}
}
impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
pub fn get_or_create(&self, label_set: &S) -> MappedRwLockReadGuard<M> {
if let Ok(metric) =
RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set))
{
return metric;
}
let mut write_guard = self.metrics.write();
write_guard
.entry(label_set.clone())
.or_insert_with(|| self.constructor.new_metric());
let read_guard = RwLockWriteGuard::downgrade(write_guard);
RwLockReadGuard::map(read_guard, |metrics| {
metrics
.get(label_set)
.expect("Metric to exist after creating it.")
})
}
pub fn remove(&self, label_set: &S) -> bool {
self.metrics.write().remove(label_set).is_some()
}
pub fn clear(&self) {
self.metrics.write().clear()
}
pub(crate) fn read(&self) -> RwLockReadGuard<HashMap<S, M>> {
self.metrics.read()
}
}
impl<S, M, C: Clone> Clone for Family<S, M, C> {
fn clone(&self) -> Self {
Family {
metrics: self.metrics.clone(),
constructor: self.constructor.clone(),
}
}
}
impl<S, M: TypedMetric, C> TypedMetric for Family<S, M, C> {
const TYPE: MetricType = <M as TypedMetric>::TYPE;
}
impl<S, M, C> EncodeMetric for Family<S, M, C>
where
S: Clone + std::hash::Hash + Eq + EncodeLabelSet,
M: EncodeMetric + TypedMetric,
C: MetricConstructor<M>,
{
fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
let guard = self.read();
for (label_set, m) in guard.iter() {
let encoder = encoder.encode_family(label_set)?;
m.encode(encoder)?;
}
Ok(())
}
fn metric_type(&self) -> MetricType {
M::TYPE
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metrics::counter::Counter;
use crate::metrics::histogram::{exponential_buckets, Histogram};
#[test]
fn counter_family() {
let family = Family::<Vec<(String, String)>, Counter>::default();
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
}
#[test]
fn histogram_family() {
Family::<(), Histogram>::new_with_constructor(|| {
Histogram::new(exponential_buckets(1.0, 2.0, 10))
});
}
#[test]
fn histogram_family_with_struct_constructor() {
struct CustomBuilder {
custom_start: f64,
}
impl MetricConstructor<Histogram> for CustomBuilder {
fn new_metric(&self) -> Histogram {
Histogram::new(exponential_buckets(self.custom_start, 2.0, 10))
}
}
let custom_builder = CustomBuilder { custom_start: 1.0 };
Family::<(), Histogram, CustomBuilder>::new_with_constructor(custom_builder);
}
#[test]
fn counter_family_remove() {
let family = Family::<Vec<(String, String)>, Counter>::default();
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.inc_by(2);
assert_eq!(
2,
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.get()
);
assert!(family.remove(&vec![("method".to_string(), "POST".to_string())]));
assert!(!family.remove(&vec![("method".to_string(), "POST".to_string())]));
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.inc();
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "POST".to_string())])
.get()
);
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
}
#[test]
fn counter_family_clear() {
let family = Family::<Vec<(String, String)>, Counter>::default();
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
family.clear();
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();
assert_eq!(
1,
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.get()
);
}
}