open_metrics_client/metrics/
histogram.rs1use super::{MetricType, TypedMetric};
6use owning_ref::OwningRef;
7use std::iter::{self, once};
8use std::sync::{Arc, Mutex, MutexGuard};
9
10pub struct Histogram {
20 inner: Arc<Mutex<Inner>>,
21}
22
23impl Clone for Histogram {
24 fn clone(&self) -> Self {
25 Histogram {
26 inner: self.inner.clone(),
27 }
28 }
29}
30
31pub(crate) struct Inner {
32 sum: f64,
34 count: u64,
35 buckets: Vec<(f64, u64)>,
37}
38
39impl Histogram {
40 pub fn new(buckets: impl Iterator<Item = f64>) -> Self {
41 Self {
42 inner: Arc::new(Mutex::new(Inner {
43 sum: Default::default(),
44 count: Default::default(),
45 buckets: buckets
46 .into_iter()
47 .chain(once(f64::MAX))
48 .map(|upper_bound| (upper_bound, 0))
49 .collect(),
50 })),
51 }
52 }
53
54 pub fn observe(&self, v: f64) {
55 self.observe_and_bucket(v);
56 }
57
58 pub(crate) fn observe_and_bucket(&self, v: f64) -> Option<usize> {
64 let mut inner = self.inner.lock().unwrap();
65 inner.sum += v;
66 inner.count += 1;
67
68 let first_bucket = inner
69 .buckets
70 .iter_mut()
71 .enumerate()
72 .find(|(_i, (upper_bound, _value))| upper_bound >= &v);
73
74 match first_bucket {
75 Some((i, (_upper_bound, value))) => {
76 *value += 1;
77 Some(i)
78 }
79 None => None,
80 }
81 }
82
83 pub(crate) fn get(&self) -> (f64, u64, MutexGuardedBuckets) {
84 let inner = self.inner.lock().unwrap();
85 let sum = inner.sum;
86 let count = inner.count;
87 let buckets = OwningRef::new(inner).map(|inner| &inner.buckets);
88 (sum, count, buckets)
89 }
90}
91
92pub(crate) type MutexGuardedBuckets<'a> = OwningRef<MutexGuard<'a, Inner>, Vec<(f64, u64)>>;
93
94impl TypedMetric for Histogram {
95 const TYPE: MetricType = MetricType::Histogram;
96}
97
98pub fn exponential_buckets(start: f64, factor: f64, length: u16) -> impl Iterator<Item = f64> {
99 iter::repeat(())
100 .enumerate()
101 .map(move |(i, _)| start * factor.powf(i as f64))
102 .take(length.into())
103}
104
105pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator<Item = f64> {
106 iter::repeat(())
107 .enumerate()
108 .map(move |(i, _)| start + (width * (i as f64)))
109 .take(length.into())
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn histogram() {
118 let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10));
119 histogram.observe(1.0);
120 }
121
122 #[test]
123 fn exponential() {
124 assert_eq!(
125 vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0],
126 exponential_buckets(1.0, 2.0, 10).collect::<Vec<_>>()
127 );
128 }
129
130 #[test]
131 fn linear() {
132 assert_eq!(
133 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
134 linear_buckets(0.0, 1.0, 10).collect::<Vec<_>>()
135 );
136 }
137}