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}