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 Some(metric) = self.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 get(&self, label_set: &S) -> Option<MappedRwLockReadGuard<M>> {
RwLockReadGuard::try_map(self.metrics.read(), |metrics| metrics.get(label_set)).ok()
}
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()
);
}
#[test]
fn test_get() {
let family = Family::<Vec<(String, String)>, Counter>::default();
let non_existent = family.get(&vec![("method".to_string(), "GET".to_string())]);
assert!(non_existent.is_none());
family
.get_or_create(&vec![("method".to_string(), "GET".to_string())])
.inc();
let existing = family.get(&vec![("method".to_string(), "GET".to_string())]);
assert!(existing.is_some());
assert_eq!(existing.unwrap().get(), 1);
let another_non_existent = family.get(&vec![("method".to_string(), "POST".to_string())]);
assert!(another_non_existent.is_none());
if let Some(metric) = family.get(&vec![("method".to_string(), "GET".to_string())]) {
metric.inc();
}
let modified = family.get(&vec![("method".to_string(), "GET".to_string())]);
assert_eq!(modified.unwrap().get(), 2);
let string_family = Family::<String, Counter>::default();
string_family.get_or_create(&"test".to_string()).inc();
let string_metric = string_family.get(&"test".to_string());
assert!(string_metric.is_some());
assert_eq!(string_metric.unwrap().get(), 1);
let non_existent_string = string_family.get(&"non_existent".to_string());
assert!(non_existent_string.is_none());
}
}