metriken/
histogram.rs

1use std::sync::OnceLock;
2
3pub use histogram::{Bucket, Config, Error, Histogram};
4use parking_lot::RwLock;
5
6use crate::{Metric, Value};
7
8/// A histogram that uses free-running atomic counters to track the distribution
9/// of values. They are only useful for recording values and producing
10/// [`crate::Snapshot`]s of the histogram state which can then be used for
11/// reporting.
12///
13/// The `AtomicHistogram` should be preferred when individual events are being
14/// recorded. The `RwLockHistogram` should be preferred when bulk-updating the
15/// histogram from pre-aggregated data with a compatible layout.
16pub struct AtomicHistogram {
17    inner: OnceLock<histogram::AtomicHistogram>,
18    config: Config,
19}
20
21impl AtomicHistogram {
22    /// Create a new [`::histogram::AtomicHistogram`] with the given parameters.
23    ///
24    /// # Panics
25    /// This will panic if the `grouping_power` and `max_value_power` do not
26    /// adhere to the following constraints:
27    ///
28    /// - `max_value_power` must be in the range 1..=64
29    /// - `grouping_power` must be in the range `0..=(max_value_power - 1)`
30    pub const fn new(grouping_power: u8, max_value_power: u8) -> Self {
31        let config = match ::histogram::Config::new(grouping_power, max_value_power) {
32            Ok(c) => c,
33            Err(_) => panic!("invalid histogram config"),
34        };
35
36        Self {
37            inner: OnceLock::new(),
38            config,
39        }
40    }
41
42    /// Increments the bucket for a corresponding value.
43    pub fn increment(&self, value: u64) -> Result<(), Error> {
44        self.get_or_init().increment(value)
45    }
46
47    pub fn config(&self) -> Config {
48        self.config
49    }
50
51    /// Loads and returns the histogram. Returns `None` if the histogram has
52    /// never been incremented.
53    pub fn load(&self) -> Option<Histogram> {
54        self.inner.get().map(|h| h.load())
55    }
56
57    fn get_or_init(&self) -> &::histogram::AtomicHistogram {
58        self.inner
59            .get_or_init(|| ::histogram::AtomicHistogram::with_config(&self.config))
60    }
61}
62
63impl Metric for AtomicHistogram {
64    fn as_any(&self) -> Option<&dyn std::any::Any> {
65        Some(self)
66    }
67
68    fn value(&self) -> Option<Value> {
69        Some(Value::Other(self))
70    }
71}
72
73/// A histogram that uses free-running non-atomic counters to track the
74/// distribution of values. They are only useful for bulk recording of values
75/// and producing [`crate::Snapshot`]s of the histogram state which can then be
76/// used for reporting.
77///
78/// The `AtomicHistogram` should be preferred when individual events are being
79/// recorded. The `RwLockHistogram` should be preferred when bulk-updating the
80/// histogram from pre-aggregated data with a compatible layout.
81pub struct RwLockHistogram {
82    inner: OnceLock<RwLock<histogram::Histogram>>,
83    config: Config,
84}
85
86impl RwLockHistogram {
87    /// Create a new [`::histogram::AtomicHistogram`] with the given parameters.
88    ///
89    /// # Panics
90    /// This will panic if the `grouping_power` and `max_value_power` do not
91    /// adhere to the following constraints:
92    ///
93    /// - `max_value_power` must be in the range 1..=64
94    /// - `grouping_power` must be in the range `0..=(max_value_power - 1)`
95    pub const fn new(grouping_power: u8, max_value_power: u8) -> Self {
96        let config = match ::histogram::Config::new(grouping_power, max_value_power) {
97            Ok(c) => c,
98            Err(_e) => panic!("invalid histogram config"),
99        };
100
101        Self {
102            inner: OnceLock::new(),
103            config,
104        }
105    }
106
107    /// Updates the histogram counts from raw data.
108    pub fn update_from(&self, data: &[u64]) -> Result<(), Error> {
109        if data.len() != self.config.total_buckets() {
110            return Err(Error::IncompatibleParameters);
111        }
112
113        let mut histogram = self.get_or_init().write();
114
115        let buckets = histogram.as_mut_slice();
116        buckets.copy_from_slice(data);
117
118        Ok(())
119    }
120
121    pub fn config(&self) -> Config {
122        self.config
123    }
124
125    /// Loads and returns the histogram. Returns `None` if the histogram has
126    /// never been incremented.
127    pub fn load(&self) -> Option<Histogram> {
128        self.inner.get().map(|h| h.read().clone())
129    }
130
131    fn get_or_init(&self) -> &RwLock<::histogram::Histogram> {
132        self.inner
133            .get_or_init(|| ::histogram::Histogram::with_config(&self.config).into())
134    }
135}
136
137impl Metric for RwLockHistogram {
138    fn as_any(&self) -> Option<&dyn std::any::Any> {
139        Some(self)
140    }
141
142    fn value(&self) -> Option<Value> {
143        Some(Value::Other(self))
144    }
145}