1use super::{histogram::HistogramSnapshot, Percentile};
2use std::{collections::HashMap, fmt::Display};
3
4#[derive(Debug, PartialEq, Eq)]
10pub enum TypedMeasurement {
11 Counter(String, i64),
12 Gauge(String, u64),
13 TimingHistogram(String, SummarizedHistogram),
14 ValueHistogram(String, SummarizedHistogram),
15}
16
17#[derive(Default, Debug)]
19pub struct Snapshot {
20 measurements: Vec<TypedMeasurement>,
21}
22
23impl Snapshot {
24 pub(crate) fn set_count<T>(&mut self, key: T, value: i64)
26 where
27 T: Display,
28 {
29 self.measurements
30 .push(TypedMeasurement::Counter(key.to_string(), value));
31 }
32
33 pub(crate) fn set_gauge<T>(&mut self, key: T, value: u64)
35 where
36 T: Display,
37 {
38 self.measurements.push(TypedMeasurement::Gauge(key.to_string(), value));
39 }
40
41 pub(crate) fn set_timing_histogram<T>(&mut self, key: T, h: HistogramSnapshot, percentiles: &[Percentile])
45 where
46 T: Display,
47 {
48 let summarized = SummarizedHistogram::from_histogram(h, percentiles);
49 self.measurements
50 .push(TypedMeasurement::TimingHistogram(key.to_string(), summarized));
51 }
52
53 pub(crate) fn set_value_histogram<T>(&mut self, key: T, h: HistogramSnapshot, percentiles: &[Percentile])
57 where
58 T: Display,
59 {
60 let summarized = SummarizedHistogram::from_histogram(h, percentiles);
61 self.measurements
62 .push(TypedMeasurement::ValueHistogram(key.to_string(), summarized));
63 }
64
65 pub fn into_simple(self) -> SimpleSnapshot { SimpleSnapshot::from_snapshot(self) }
70
71 pub fn into_vec(self) -> Vec<TypedMeasurement> { self.measurements }
73}
74
75#[derive(Default)]
80pub struct SimpleSnapshot {
81 pub(crate) counters: HashMap<String, i64>,
82 pub(crate) gauges: HashMap<String, u64>,
83 pub(crate) timings: HashMap<String, SummarizedHistogram>,
84 pub(crate) values: HashMap<String, SummarizedHistogram>,
85}
86
87impl SimpleSnapshot {
88 pub(crate) fn from_snapshot(s: Snapshot) -> Self {
89 let mut ss = SimpleSnapshot::default();
90 for metric in s.into_vec() {
91 match metric {
92 TypedMeasurement::Counter(key, value) => {
93 ss.counters.insert(key, value);
94 },
95 TypedMeasurement::Gauge(key, value) => {
96 ss.gauges.insert(key, value);
97 },
98 TypedMeasurement::TimingHistogram(key, value) => {
99 ss.timings.insert(key, value);
100 },
101 TypedMeasurement::ValueHistogram(key, value) => {
102 ss.values.insert(key, value);
103 },
104 }
105 }
106 ss
107 }
108
109 pub fn count(&self, key: &str) -> Option<i64> { self.counters.get(key).cloned() }
113
114 pub fn gauge(&self, key: &str) -> Option<u64> { self.gauges.get(key).cloned() }
118
119 pub fn timing_histogram(&self, key: &str, percentile: f64) -> Option<u64> {
123 let p = Percentile::from(percentile);
124 self.timings.get(key).and_then(|s| s.measurements().get(&p)).cloned()
125 }
126
127 pub fn value_histogram(&self, key: &str, percentile: f64) -> Option<u64> {
131 let p = Percentile::from(percentile);
132 self.values.get(key).and_then(|s| s.measurements().get(&p)).cloned()
133 }
134}
135
136#[derive(Debug, PartialEq, Eq)]
142pub struct SummarizedHistogram {
143 count: u64,
144 sum: u64,
145 measurements: HashMap<Percentile, u64>,
146}
147
148impl SummarizedHistogram {
149 pub(crate) fn from_histogram(histogram: HistogramSnapshot, percentiles: &[Percentile]) -> Self {
150 let mut measurements = HashMap::default();
151 let count = histogram.count();
152 let sum = histogram.sum();
153
154 for percentile in percentiles {
155 let value = histogram.histogram().value_at_percentile(percentile.value);
156 measurements.insert(percentile.clone(), value);
157 }
158
159 SummarizedHistogram {
160 count,
161 sum,
162 measurements,
163 }
164 }
165
166 pub fn count(&self) -> u64 { self.count }
168
169 pub fn sum(&self) -> u64 { self.sum }
171
172 pub fn measurements(&self) -> &HashMap<Percentile, u64> { &self.measurements }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::{HistogramSnapshot, Percentile, Snapshot, TypedMeasurement};
179 use hdrhistogram::Histogram;
180
181 #[test]
182 fn test_snapshot_simple_set_and_get() {
183 let key = "ok".to_owned();
184 let mut snapshot = Snapshot::default();
185 snapshot.set_count(key.clone(), 1);
186 snapshot.set_gauge(key.clone(), 42);
187
188 let values = snapshot.into_vec();
189
190 assert_eq!(values[0], TypedMeasurement::Counter("ok".to_owned(), 1));
191 assert_eq!(values[1], TypedMeasurement::Gauge("ok".to_owned(), 42));
192 }
193
194 #[test]
195 fn test_snapshot_percentiles() {
196 {
197 let mut snapshot = Snapshot::default();
198 let mut h1 = Histogram::<u64>::new_with_bounds(1, u64::max_value(), 3).unwrap();
199 let mut sum = 0;
200 h1.saturating_record(500_000);
201 sum += 500_000;
202 h1.saturating_record(750_000);
203 sum += 750_000;
204 h1.saturating_record(1_000_000);
205 sum += 1_000_000;
206 h1.saturating_record(1_250_000);
207 sum += 1_250_000;
208
209 let tkey = "ok".to_owned();
210 let mut tpercentiles = Vec::new();
211 tpercentiles.push(Percentile::from(0.0));
212 tpercentiles.push(Percentile::from(50.0));
213 tpercentiles.push(Percentile::from(99.0));
214 tpercentiles.push(Percentile::from(100.0));
215 let fake = Percentile::from(63.0);
216
217 snapshot.set_timing_histogram(tkey.clone(), HistogramSnapshot::new(h1, sum), &tpercentiles);
218
219 let values = snapshot.into_vec();
220 match values.get(0) {
221 Some(TypedMeasurement::TimingHistogram(key, summary)) => {
222 assert_eq!(key, "ok");
223 assert_eq!(summary.count(), 4);
224 assert_eq!(summary.sum(), 3_500_000);
225
226 let min_tpercentile = summary.measurements().get(&tpercentiles[0]);
227 let p50_tpercentile = summary.measurements().get(&tpercentiles[1]);
228 let p99_tpercentile = summary.measurements().get(&tpercentiles[2]);
229 let max_tpercentile = summary.measurements().get(&tpercentiles[3]);
230 let fake_tpercentile = summary.measurements().get(&fake);
231
232 assert!(min_tpercentile.is_some());
233 assert!(p50_tpercentile.is_some());
234 assert!(p99_tpercentile.is_some());
235 assert!(max_tpercentile.is_some());
236 assert!(fake_tpercentile.is_none());
237 },
238 _ => panic!("expected timing histogram value! actual: {:?}", values[0]),
239 }
240 }
241
242 {
243 let mut snapshot = Snapshot::default();
244 let mut h1 = Histogram::<u64>::new_with_bounds(1, u64::max_value(), 3).unwrap();
245 let mut sum = 0;
246 h1.saturating_record(500_000);
247 sum += 500_000;
248 h1.saturating_record(750_000);
249 sum += 750_000;
250 h1.saturating_record(1_000_000);
251 sum += 1_000_000;
252 h1.saturating_record(1_250_000);
253 sum += 1_250_000;
254
255 let tkey = "ok".to_owned();
256 let mut tpercentiles = Vec::new();
257 tpercentiles.push(Percentile::from(0.0));
258 tpercentiles.push(Percentile::from(50.0));
259 tpercentiles.push(Percentile::from(99.0));
260 tpercentiles.push(Percentile::from(100.0));
261 let fake = Percentile::from(63.0);
262
263 snapshot.set_value_histogram(tkey.clone(), HistogramSnapshot::new(h1, sum), &tpercentiles);
264
265 let values = snapshot.into_vec();
266 match values.get(0) {
267 Some(TypedMeasurement::ValueHistogram(key, summary)) => {
268 assert_eq!(key, "ok");
269 assert_eq!(summary.count(), 4);
270 assert_eq!(summary.sum(), 3_500_000);
271
272 let min_tpercentile = summary.measurements().get(&tpercentiles[0]);
273 let p50_tpercentile = summary.measurements().get(&tpercentiles[1]);
274 let p99_tpercentile = summary.measurements().get(&tpercentiles[2]);
275 let max_tpercentile = summary.measurements().get(&tpercentiles[3]);
276 let fake_tpercentile = summary.measurements().get(&fake);
277
278 assert!(min_tpercentile.is_some());
279 assert!(p50_tpercentile.is_some());
280 assert!(p99_tpercentile.is_some());
281 assert!(max_tpercentile.is_some());
282 assert!(fake_tpercentile.is_none());
283 },
284 _ => panic!("expected value histogram value! actual: {:?}", values[0]),
285 }
286 }
287 }
288
289 #[test]
290 fn test_percentiles() {
291 let min_p = Percentile::from(0.0);
292 assert_eq!(min_p.label(), "min");
293
294 let max_p = Percentile::from(100.0);
295 assert_eq!(max_p.label(), "max");
296
297 let clamped_min_p = Percentile::from(-20.0);
298 assert_eq!(clamped_min_p.label(), "min");
299 assert_eq!(clamped_min_p.percentile(), 0.0);
300
301 let clamped_max_p = Percentile::from(1442.0);
302 assert_eq!(clamped_max_p.label(), "max");
303 assert_eq!(clamped_max_p.percentile(), 100.0);
304
305 let p99_p = Percentile::from(99.0);
306 assert_eq!(p99_p.label(), "p99");
307
308 let p999_p = Percentile::from(99.9);
309 assert_eq!(p999_p.label(), "p999");
310
311 let p9999_p = Percentile::from(99.99);
312 assert_eq!(p9999_p.label(), "p9999");
313 }
314}