glean_core/histogram/
mod.rs1use std::any::TypeId;
8use std::collections::HashMap;
9
10use malloc_size_of_derive::MallocSizeOf;
11use once_cell::sync::OnceCell;
12use serde::{Deserialize, Serialize};
13
14use crate::error::{Error, ErrorKind};
15
16pub use exponential::PrecomputedExponential;
17pub use functional::Functional;
18pub use linear::PrecomputedLinear;
19
20mod exponential;
21mod functional;
22mod linear;
23
24#[derive(Debug, Clone, Copy, Serialize, Deserialize, MallocSizeOf)]
26#[serde(rename_all = "lowercase")]
27pub enum HistogramType {
28 Linear,
30 Exponential,
32}
33
34impl TryFrom<i32> for HistogramType {
35 type Error = Error;
36
37 fn try_from(value: i32) -> Result<HistogramType, Self::Error> {
38 match value {
39 0 => Ok(HistogramType::Linear),
40 1 => Ok(HistogramType::Exponential),
41 e => Err(ErrorKind::HistogramType(e).into()),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, MallocSizeOf)]
64pub struct Histogram<B> {
65 values: HashMap<u64, u64>,
67
68 count: u64,
70 sum: u64,
72
73 bucketing: B,
75}
76
77pub trait Bucketing {
82 fn sample_to_bucket_minimum(&self, sample: u64) -> u64;
84
85 fn ranges(&self) -> &[u64];
87}
88
89impl<B: Bucketing> Histogram<B> {
90 pub fn bucket_count(&self) -> usize {
92 self.values.len()
93 }
94
95 pub fn accumulate(&mut self, sample: u64) {
97 let bucket_min = self.bucketing.sample_to_bucket_minimum(sample);
98 let entry = self.values.entry(bucket_min).or_insert(0);
99 *entry += 1;
100 self.sum = self.sum.saturating_add(sample);
101 self.count += 1;
102 }
103
104 pub fn sum(&self) -> u64 {
106 self.sum
107 }
108
109 pub fn count(&self) -> u64 {
111 self.count
112 }
113
114 pub fn values(&self) -> &HashMap<u64, u64> {
116 &self.values
117 }
118
119 pub fn is_empty(&self) -> bool {
121 self.count() == 0
122 }
123
124 pub fn snapshot_values(&self) -> HashMap<u64, u64> {
127 self.values.clone()
128 }
129
130 pub fn clear(&mut self) {
132 self.sum = 0;
133 self.count = 0;
134 self.values.clear();
135 }
136}
137
138pub enum LinearOrExponential {
142 Linear(PrecomputedLinear),
143 Exponential(PrecomputedExponential),
144}
145
146impl Histogram<LinearOrExponential> {
147 pub fn _linear(min: u64, max: u64, bucket_count: usize) -> Histogram<LinearOrExponential> {
152 Histogram {
153 values: HashMap::new(),
154 count: 0,
155 sum: 0,
156 bucketing: LinearOrExponential::Linear(PrecomputedLinear {
157 bucket_ranges: OnceCell::new(),
158 min,
159 max,
160 bucket_count,
161 }),
162 }
163 }
164
165 pub fn _exponential(min: u64, max: u64, bucket_count: usize) -> Histogram<LinearOrExponential> {
170 Histogram {
171 values: HashMap::new(),
172 count: 0,
173 sum: 0,
174 bucketing: LinearOrExponential::Exponential(PrecomputedExponential {
175 bucket_ranges: OnceCell::new(),
176 min,
177 max,
178 bucket_count,
179 }),
180 }
181 }
182}
183
184impl Bucketing for LinearOrExponential {
185 fn sample_to_bucket_minimum(&self, sample: u64) -> u64 {
186 use LinearOrExponential::*;
187 match self {
188 Linear(lin) => lin.sample_to_bucket_minimum(sample),
189 Exponential(exp) => exp.sample_to_bucket_minimum(sample),
190 }
191 }
192
193 fn ranges(&self) -> &[u64] {
194 use LinearOrExponential::*;
195 match self {
196 Linear(lin) => lin.ranges(),
197 Exponential(exp) => exp.ranges(),
198 }
199 }
200}
201
202impl<B> Histogram<B>
203where
204 B: Bucketing,
205 B: std::fmt::Debug,
206 B: PartialEq,
207{
208 pub fn merge(&mut self, other: &Self) {
214 assert_eq!(self.bucketing, other.bucketing);
215
216 self.sum = self.sum.saturating_add(other.sum);
217 self.count = self.count.saturating_add(other.count);
218 for (&bucket, &count) in &other.values {
219 let entry = self.values.entry(bucket).or_insert(0);
220 *entry = entry.saturating_add(count)
221 }
222 }
223}
224
225impl<B> Histogram<B>
226where
227 B: Bucketing + 'static,
228 B: std::fmt::Debug,
229 B: PartialEq,
230{
231 pub fn _merge(&mut self, other: &Histogram<LinearOrExponential>) {
242 #[rustfmt::skip]
243 assert!(
244 (
245 TypeId::of::<B>() == TypeId::of::<PrecomputedLinear>()
246 && matches!(other.bucketing, LinearOrExponential::Linear(_))
247 ) ||
248 (
249 TypeId::of::<B>() == TypeId::of::<PrecomputedExponential>()
250 && matches!(other.bucketing, LinearOrExponential::Exponential(_))
251 )
252 );
253 self.sum = self.sum.saturating_add(other.sum);
254 self.count = self.count.saturating_add(other.count);
255 for (&bucket, &count) in &other.values {
256 let entry = self.values.entry(bucket).or_insert(0);
257 *entry = entry.saturating_add(count);
258 }
259 }
260}