prometheus_client/metrics/
histogram.rs1use crate::encoding::{EncodeMetric, MetricEncoder, NoLabelSet};
6
7use super::{MetricType, TypedMetric};
8use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
9use std::iter::{self, once};
10use std::sync::Arc;
11
12#[derive(Debug)]
37pub struct Histogram {
38 inner: Arc<RwLock<Inner>>,
39}
40
41impl Clone for Histogram {
42 fn clone(&self) -> Self {
43 Histogram {
44 inner: self.inner.clone(),
45 }
46 }
47}
48
49#[derive(Debug)]
50pub(crate) struct Inner {
51 sum: f64,
53 count: u64,
54 buckets: Vec<(f64, u64)>,
56}
57
58impl Histogram {
59 pub fn new(buckets: impl IntoIterator<Item = f64>) -> Self {
66 Self {
67 inner: Arc::new(RwLock::new(Inner {
68 sum: Default::default(),
69 count: Default::default(),
70 buckets: buckets
71 .into_iter()
72 .chain(once(f64::MAX))
73 .map(|upper_bound| (upper_bound, 0))
74 .collect(),
75 })),
76 }
77 }
78
79 pub fn observe(&self, v: f64) {
81 self.observe_and_bucket(v);
82 }
83
84 pub(crate) fn observe_and_bucket(&self, v: f64) -> Option<usize> {
90 let mut inner = self.inner.write();
91 inner.sum += v;
92 inner.count += 1;
93
94 let first_bucket = inner
95 .buckets
96 .iter_mut()
97 .enumerate()
98 .find(|(_i, (upper_bound, _value))| upper_bound >= &v);
99
100 match first_bucket {
101 Some((i, (_upper_bound, value))) => {
102 *value += 1;
103 Some(i)
104 }
105 None => None,
106 }
107 }
108
109 pub(crate) fn get(&self) -> (f64, u64, MappedRwLockReadGuard<Vec<(f64, u64)>>) {
110 let inner = self.inner.read();
111 let sum = inner.sum;
112 let count = inner.count;
113 let buckets = RwLockReadGuard::map(inner, |inner| &inner.buckets);
114 (sum, count, buckets)
115 }
116}
117
118impl TypedMetric for Histogram {
119 const TYPE: MetricType = MetricType::Histogram;
120}
121
122pub fn exponential_buckets(start: f64, factor: f64, length: u16) -> impl Iterator<Item = f64> {
124 iter::repeat(())
125 .enumerate()
126 .map(move |(i, _)| start * factor.powf(i as f64))
127 .take(length.into())
128}
129
130pub fn exponential_buckets_range(min: f64, max: f64, length: u16) -> impl Iterator<Item = f64> {
136 let mut len_observed = length;
137 let mut min_bucket = min;
138 if length < 1 || min <= 0.0 {
142 len_observed = 0;
143 min_bucket = 1.0;
144 }
145 let growth_factor = (max / min_bucket).powf(1.0 / (len_observed as f64 - 1.0));
147
148 iter::repeat(())
149 .enumerate()
150 .map(move |(i, _)| min_bucket * growth_factor.powf(i as f64))
151 .take(len_observed.into())
152}
153
154pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator<Item = f64> {
156 iter::repeat(())
157 .enumerate()
158 .map(move |(i, _)| start + (width * (i as f64)))
159 .take(length.into())
160}
161
162impl EncodeMetric for Histogram {
163 fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
164 let (sum, count, buckets) = self.get();
165 encoder.encode_histogram::<NoLabelSet>(sum, count, &buckets, None)
166 }
167
168 fn metric_type(&self) -> MetricType {
169 Self::TYPE
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn histogram() {
179 let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10));
180 histogram.observe(1.0);
181 }
182
183 #[test]
184 fn exponential() {
185 assert_eq!(
186 vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0],
187 exponential_buckets(1.0, 2.0, 10).collect::<Vec<_>>()
188 );
189 }
190
191 #[test]
192 fn linear() {
193 assert_eq!(
194 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
195 linear_buckets(0.0, 1.0, 10).collect::<Vec<_>>()
196 );
197 }
198
199 #[test]
200 fn exponential_range() {
201 assert_eq!(
202 vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0],
203 exponential_buckets_range(1.0, 32.0, 6).collect::<Vec<_>>()
204 );
205 }
206
207 #[test]
208 fn exponential_range_incorrect() {
209 let res = exponential_buckets_range(1.0, 32.0, 0).collect::<Vec<_>>();
210 assert!(res.is_empty());
211
212 let res = exponential_buckets_range(0.0, 32.0, 6).collect::<Vec<_>>();
213 assert!(res.is_empty());
214 }
215}