glean_core/histogram/
mod.rs1use std::any::TypeId;
8use std::collections::HashMap;
9
10use once_cell::sync::OnceCell;
11use serde::{Deserialize, Serialize};
12
13use crate::error::{Error, ErrorKind};
14
15pub use exponential::PrecomputedExponential;
16pub use functional::Functional;
17pub use linear::PrecomputedLinear;
18
19mod exponential;
20mod functional;
21mod linear;
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum HistogramType {
27 Linear,
29 Exponential,
31}
32
33impl TryFrom<i32> for HistogramType {
34 type Error = Error;
35
36 fn try_from(value: i32) -> Result<HistogramType, Self::Error> {
37 match value {
38 0 => Ok(HistogramType::Linear),
39 1 => Ok(HistogramType::Exponential),
40 e => Err(ErrorKind::HistogramType(e).into()),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63pub struct Histogram<B> {
64 values: HashMap<u64, u64>,
66
67 count: u64,
69 sum: u64,
71
72 bucketing: B,
74}
75
76pub trait Bucketing {
81 fn sample_to_bucket_minimum(&self, sample: u64) -> u64;
83
84 fn ranges(&self) -> &[u64];
86}
87
88impl<B: Bucketing> Histogram<B> {
89 pub fn bucket_count(&self) -> usize {
91 self.values.len()
92 }
93
94 pub fn accumulate(&mut self, sample: u64) {
96 let bucket_min = self.bucketing.sample_to_bucket_minimum(sample);
97 let entry = self.values.entry(bucket_min).or_insert(0);
98 *entry += 1;
99 self.sum = self.sum.saturating_add(sample);
100 self.count += 1;
101 }
102
103 pub fn sum(&self) -> u64 {
105 self.sum
106 }
107
108 pub fn count(&self) -> u64 {
110 self.count
111 }
112
113 pub fn values(&self) -> &HashMap<u64, u64> {
115 &self.values
116 }
117
118 pub fn is_empty(&self) -> bool {
120 self.count() == 0
121 }
122
123 pub fn snapshot_values(&self) -> HashMap<u64, u64> {
126 let mut res = self.values.clone();
127
128 let max_bucket = self.values.keys().max().cloned().unwrap_or(0);
129
130 for &min_bucket in self.bucketing.ranges() {
131 let _ = res.entry(min_bucket).or_insert(0);
133 if min_bucket > max_bucket {
135 break;
136 }
137 }
138 res
139 }
140
141 pub fn clear(&mut self) {
143 self.sum = 0;
144 self.count = 0;
145 self.values.clear();
146 }
147}
148
149pub enum LinearOrExponential {
153 Linear(PrecomputedLinear),
154 Exponential(PrecomputedExponential),
155}
156
157impl Histogram<LinearOrExponential> {
158 pub fn _linear(min: u64, max: u64, bucket_count: usize) -> Histogram<LinearOrExponential> {
163 Histogram {
164 values: HashMap::new(),
165 count: 0,
166 sum: 0,
167 bucketing: LinearOrExponential::Linear(PrecomputedLinear {
168 bucket_ranges: OnceCell::new(),
169 min,
170 max,
171 bucket_count,
172 }),
173 }
174 }
175
176 pub fn _exponential(min: u64, max: u64, bucket_count: usize) -> Histogram<LinearOrExponential> {
181 Histogram {
182 values: HashMap::new(),
183 count: 0,
184 sum: 0,
185 bucketing: LinearOrExponential::Exponential(PrecomputedExponential {
186 bucket_ranges: OnceCell::new(),
187 min,
188 max,
189 bucket_count,
190 }),
191 }
192 }
193}
194
195impl Bucketing for LinearOrExponential {
196 fn sample_to_bucket_minimum(&self, sample: u64) -> u64 {
197 use LinearOrExponential::*;
198 match self {
199 Linear(lin) => lin.sample_to_bucket_minimum(sample),
200 Exponential(exp) => exp.sample_to_bucket_minimum(sample),
201 }
202 }
203
204 fn ranges(&self) -> &[u64] {
205 use LinearOrExponential::*;
206 match self {
207 Linear(lin) => lin.ranges(),
208 Exponential(exp) => exp.ranges(),
209 }
210 }
211}
212
213impl<B> Histogram<B>
214where
215 B: Bucketing,
216 B: std::fmt::Debug,
217 B: PartialEq,
218{
219 pub fn merge(&mut self, other: &Self) {
225 assert_eq!(self.bucketing, other.bucketing);
226
227 self.sum += other.sum;
228 self.count += other.count;
229 for (&bucket, &count) in &other.values {
230 *self.values.entry(bucket).or_insert(0) += count;
231 }
232 }
233}
234
235impl<B> Histogram<B>
236where
237 B: Bucketing + 'static,
238 B: std::fmt::Debug,
239 B: PartialEq,
240{
241 pub fn _merge(&mut self, other: &Histogram<LinearOrExponential>) {
252 #[rustfmt::skip]
253 assert!(
254 (
255 TypeId::of::<B>() == TypeId::of::<PrecomputedLinear>()
256 && matches!(other.bucketing, LinearOrExponential::Linear(_))
257 ) ||
258 (
259 TypeId::of::<B>() == TypeId::of::<PrecomputedExponential>()
260 && matches!(other.bucketing, LinearOrExponential::Exponential(_))
261 )
262 );
263 self.sum += other.sum;
264 self.count += other.count;
265 for (&bucket, &count) in &other.values {
266 *self.values.entry(bucket).or_insert(0) += count;
267 }
268 }
269}