1use crate::encoding::{
6 EncodeLabelSet, EncodeMetric, MetricEncoder, NativeHistogram, NativeHistogramBuckets,
7 NoLabelSet,
8};
9use crate::metrics::exemplar::Exemplar;
10
11use super::{MetricType, TypedMetric};
12use parking_lot::Mutex;
13use std::collections::HashMap;
14use std::iter::{self, once};
15use std::sync::Arc;
16use std::time::{Duration, SystemTime};
17
18pub const NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO: f64 = -1.0;
20
21pub const DEFAULT_NATIVE_HISTOGRAM_BUCKET_FACTOR: f64 = 1.1;
23
24pub const DEFAULT_NATIVE_HISTOGRAM_ZERO_THRESHOLD: f64 = 2.938735877055719e-39;
26
27const SCHEMA_MIN: i8 = -4;
28const SCHEMA_MAX: i8 = 8;
29
30#[derive(Debug)]
55pub struct Histogram {
56 inner: Arc<Mutex<Inner>>,
57}
58
59impl Clone for Histogram {
60 fn clone(&self) -> Self {
61 Histogram {
62 inner: self.inner.clone(),
63 }
64 }
65}
66
67#[derive(Debug)]
68pub(crate) struct Inner {
69 sum: f64,
71 count: u64,
72 buckets: Vec<(f64, u64)>,
74 native: Option<NativeHistogramState>,
75}
76
77#[derive(Clone, Copy, Debug)]
81pub struct NativeHistogramConfig {
82 schema: i8,
83 zero_threshold: f64,
84 max_buckets: usize,
85 min_reset_duration: Option<Duration>,
86 max_zero_threshold: f64,
87}
88
89impl NativeHistogramConfig {
90 pub fn new(bucket_factor: f64) -> Self {
97 Self::with_bucket_factor(bucket_factor)
98 }
99
100 pub fn with_schema(schema: i8) -> Self {
105 assert!((SCHEMA_MIN..=SCHEMA_MAX).contains(&schema));
106
107 Self {
108 schema,
109 zero_threshold: DEFAULT_NATIVE_HISTOGRAM_ZERO_THRESHOLD,
110 max_buckets: 0,
111 min_reset_duration: None,
112 max_zero_threshold: 0.0,
113 }
114 }
115
116 pub fn with_bucket_factor(bucket_factor: f64) -> Self {
119 assert!(bucket_factor > 1.0);
120 Self::with_schema(pick_schema(bucket_factor))
121 }
122
123 pub fn zero_threshold(mut self, zero_threshold: f64) -> Self {
128 assert!(zero_threshold.is_finite());
129 self.zero_threshold = if zero_threshold > 0.0 {
130 zero_threshold
131 } else if zero_threshold == 0.0 {
132 DEFAULT_NATIVE_HISTOGRAM_ZERO_THRESHOLD
133 } else {
134 NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO
135 };
136 self
137 }
138
139 pub fn max_buckets(mut self, max_buckets: usize) -> Self {
145 self.max_buckets = max_buckets;
146 self
147 }
148
149 pub fn min_reset_duration(mut self, min_reset_duration: Duration) -> Self {
153 self.min_reset_duration =
154 (min_reset_duration != Duration::ZERO).then_some(min_reset_duration);
155 self
156 }
157
158 pub fn max_zero_threshold(mut self, max_zero_threshold: f64) -> Self {
163 assert!(max_zero_threshold.is_finite());
164 assert!(max_zero_threshold >= 0.0);
165 self.max_zero_threshold = max_zero_threshold;
166 self
167 }
168}
169
170impl Default for NativeHistogramConfig {
171 fn default() -> Self {
172 Self::new(DEFAULT_NATIVE_HISTOGRAM_BUCKET_FACTOR)
173 }
174}
175
176#[derive(Debug)]
177struct NativeHistogramState {
178 initial_zero_threshold: f64,
179 initial_schema: i8,
180 zero_threshold: f64,
181 zero_count: u64,
182 schema: i8,
183 max_buckets: usize,
184 min_reset_duration: Option<Duration>,
185 max_zero_threshold: f64,
186 created: SystemTime,
187 scheduled_reset: Option<SystemTime>,
188 positive: NativeBuckets,
189 negative: NativeBuckets,
190}
191
192type NativeBuckets = Vec<(i32, u64)>;
193
194#[derive(Debug)]
195struct NativeHistogramSnapshot {
196 schema: i32,
197 zero_threshold: f64,
198 zero_count: u64,
199 negative: NativeBucketEncoding,
200 positive: NativeBucketEncoding,
201 created: SystemTime,
202}
203
204#[derive(Debug)]
205struct NativeBucketEncoding {
206 spans: Vec<(i32, u32)>,
207 deltas: Vec<i64>,
208}
209
210#[derive(Debug)]
211struct HistogramSnapshot {
212 sum: f64,
213 count: u64,
214 buckets: Vec<(f64, u64)>,
215 native: Option<NativeHistogramSnapshot>,
216}
217
218impl NativeHistogramState {
219 fn new(config: NativeHistogramConfig) -> Self {
220 let created = SystemTime::now();
221 Self {
222 initial_zero_threshold: config.zero_threshold,
223 initial_schema: config.schema,
224 zero_threshold: config.zero_threshold,
225 zero_count: 0,
226 schema: config.schema,
227 max_buckets: config.max_buckets,
228 min_reset_duration: config.min_reset_duration,
229 max_zero_threshold: config.max_zero_threshold,
230 created,
231 scheduled_reset: None,
232 positive: Vec::new(),
233 negative: Vec::new(),
234 }
235 }
236
237 fn observe(&mut self, v: f64) -> bool {
238 if in_zero_bucket(self.zero_threshold, v) {
239 self.zero_count += 1;
240 return enforce_bucket_limit(self);
241 }
242
243 let index = bucket_index(self.schema, v.abs(), v.is_infinite());
244 if v.is_sign_negative() {
245 increment_bucket(&mut self.negative, index);
246 } else {
247 increment_bucket(&mut self.positive, index);
248 }
249
250 enforce_bucket_limit(self)
251 }
252
253 fn reset(&mut self, created: SystemTime) {
254 self.zero_threshold = self.initial_zero_threshold;
255 self.zero_count = 0;
256 self.schema = self.initial_schema;
257 self.created = created;
258 self.scheduled_reset = None;
259 self.positive.clear();
260 self.negative.clear();
261 }
262
263 fn reset_is_due(&self, now: SystemTime) -> bool {
264 self.scheduled_reset
265 .and_then(|scheduled_reset| now.duration_since(scheduled_reset).ok())
266 .is_some()
267 }
268
269 fn schedule_reset_after_degradation(&mut self) {
270 if self.scheduled_reset.is_none() {
271 self.scheduled_reset = self
272 .min_reset_duration
273 .and_then(|duration| self.created.checked_add(duration));
274 }
275 }
276
277 fn snapshot(&self) -> Result<NativeHistogramSnapshot, std::fmt::Error> {
278 let mut negative = encode_spans_and_deltas(&self.negative)?;
279 let mut positive = encode_spans_and_deltas(&self.positive)?;
280
281 let exported_zero_threshold = if self.zero_threshold < 0.0 {
282 0.0
283 } else {
284 self.zero_threshold
285 };
286
287 if exported_zero_threshold == 0.0
290 && self.zero_count == 0
291 && positive.spans.is_empty()
292 && negative.spans.is_empty()
293 {
294 positive.spans.push((0, 0));
295 }
296
297 if self.negative.is_empty() {
298 negative.spans.clear();
299 }
300
301 Ok(NativeHistogramSnapshot {
302 schema: i32::from(self.schema),
303 zero_threshold: exported_zero_threshold,
304 zero_count: self.zero_count,
305 negative,
306 positive,
307 created: self.created,
308 })
309 }
310}
311
312impl Histogram {
313 pub fn new(buckets: impl IntoIterator<Item = f64>) -> Self {
320 Self {
321 inner: Arc::new(Mutex::new(Inner {
322 sum: Default::default(),
323 count: Default::default(),
324 buckets: buckets
325 .into_iter()
326 .chain(once(f64::MAX))
327 .map(|upper_bound| (upper_bound, 0))
328 .collect(),
329 native: None,
330 })),
331 }
332 }
333
334 pub fn new_native(native: NativeHistogramConfig) -> Self {
340 Self {
341 inner: Arc::new(Mutex::new(Inner {
342 sum: Default::default(),
343 count: Default::default(),
344 buckets: Vec::new(),
345 native: Some(NativeHistogramState::new(native)),
346 })),
347 }
348 }
349
350 pub fn new_classic_and_native(
356 buckets: impl IntoIterator<Item = f64>,
357 native: NativeHistogramConfig,
358 ) -> Self {
359 let histogram = Self::new(buckets);
360 histogram.inner.lock().native = Some(NativeHistogramState::new(native));
361 histogram
362 }
363
364 pub fn observe(&self, v: f64) {
366 self.observe_and_bucket(v);
367 }
368
369 #[cfg(any(test, feature = "test-util"))]
371 pub fn sum(&self) -> f64 {
372 self.inner.lock().sum
373 }
374
375 #[cfg(any(test, feature = "test-util"))]
377 pub fn count(&self) -> u64 {
378 self.inner.lock().count
379 }
380
381 pub(crate) fn observe_and_bucket(&self, v: f64) -> Option<usize> {
387 let mut inner = self.inner.lock();
388 reset_if_scheduled(&mut inner);
389 let mut bucket = observe_classic(&mut inner, v);
390
391 let reset = if let Some(native) = &mut inner.native {
392 !v.is_nan() && native.observe(v)
393 } else {
394 false
395 };
396
397 if reset {
398 let created = SystemTime::now();
399 reset_observations(&mut inner, created);
400 bucket = observe_classic(&mut inner, v);
401 if let Some(native) = &mut inner.native {
402 if !v.is_nan() {
403 native.observe(v);
404 }
405 }
406 }
407
408 bucket
409 }
410
411 fn snapshot(&self) -> Result<HistogramSnapshot, std::fmt::Error> {
412 let mut inner = self.inner.lock();
413 reset_if_scheduled(&mut inner);
414 let native = inner
415 .native
416 .as_ref()
417 .map(NativeHistogramState::snapshot)
418 .transpose()?;
419
420 Ok(HistogramSnapshot {
421 sum: inner.sum,
422 count: inner.count,
423 buckets: inner.buckets.clone(),
424 native,
425 })
426 }
427
428 pub(crate) fn encode_with_exemplars<S: EncodeLabelSet>(
429 &self,
430 encoder: &mut MetricEncoder,
431 exemplars: Option<&HashMap<usize, Exemplar<S, f64>>>,
432 ) -> Result<(), std::fmt::Error> {
433 let snapshot = self.snapshot()?;
434 match snapshot.native {
435 Some(native) => encoder.encode_histogram_with_native(
436 snapshot.sum,
437 snapshot.count,
438 &snapshot.buckets,
439 exemplars,
440 NativeHistogram {
441 schema: native.schema,
442 zero_threshold: native.zero_threshold,
443 zero_count: native.zero_count,
444 negative: NativeHistogramBuckets {
445 spans: &native.negative.spans,
446 deltas: &native.negative.deltas,
447 },
448 positive: NativeHistogramBuckets {
449 spans: &native.positive.spans,
450 deltas: &native.positive.deltas,
451 },
452 created: Some(native.created),
453 },
454 ),
455 None => {
456 encoder.encode_histogram(snapshot.sum, snapshot.count, &snapshot.buckets, exemplars)
457 }
458 }
459 }
460}
461
462impl TypedMetric for Histogram {
463 const TYPE: MetricType = MetricType::Histogram;
464}
465
466pub fn exponential_buckets(start: f64, factor: f64, length: u16) -> impl Iterator<Item = f64> {
468 iter::repeat(())
469 .enumerate()
470 .map(move |(i, _)| start * factor.powf(i as f64))
471 .take(length.into())
472}
473
474pub fn exponential_buckets_range(min: f64, max: f64, length: u16) -> impl Iterator<Item = f64> {
480 let mut len_observed = length;
481 let mut min_bucket = min;
482 if length < 1 || min <= 0.0 {
486 len_observed = 0;
487 min_bucket = 1.0;
488 }
489 let growth_factor = (max / min_bucket).powf(1.0 / (len_observed as f64 - 1.0));
491
492 iter::repeat(())
493 .enumerate()
494 .map(move |(i, _)| min_bucket * growth_factor.powf(i as f64))
495 .take(len_observed.into())
496}
497
498pub fn linear_buckets(start: f64, width: f64, length: u16) -> impl Iterator<Item = f64> {
500 iter::repeat(())
501 .enumerate()
502 .map(move |(i, _)| start + (width * (i as f64)))
503 .take(length.into())
504}
505
506impl EncodeMetric for Histogram {
507 fn encode(&self, mut encoder: MetricEncoder) -> Result<(), std::fmt::Error> {
508 self.encode_with_exemplars::<NoLabelSet>(&mut encoder, None)
509 }
510
511 fn metric_type(&self) -> MetricType {
512 Self::TYPE
513 }
514}
515
516fn observe_classic(inner: &mut Inner, v: f64) -> Option<usize> {
517 inner.sum += v;
518 inner.count += 1;
519
520 if inner.buckets.is_empty() {
521 return None;
522 }
523
524 let first_bucket = if v.is_nan() {
525 inner.buckets.iter_mut().enumerate().next_back()
526 } else {
527 inner
528 .buckets
529 .iter_mut()
530 .enumerate()
531 .find(|(_i, (upper_bound, _value))| upper_bound >= &v)
532 };
533
534 match first_bucket {
535 Some((i, (_upper_bound, value))) => {
536 *value += 1;
537 Some(i)
538 }
539 None => None,
540 }
541}
542
543fn reset_observations(inner: &mut Inner, created: SystemTime) {
544 inner.sum = 0.0;
545 inner.count = 0;
546 for (_, count) in &mut inner.buckets {
547 *count = 0;
548 }
549 if let Some(native) = &mut inner.native {
550 native.reset(created);
551 }
552}
553
554fn reset_if_scheduled(inner: &mut Inner) {
555 let Some(native) = inner.native.as_ref() else {
556 return;
557 };
558 if native.scheduled_reset.is_none() {
559 return;
560 }
561
562 let now = SystemTime::now();
563 if native.reset_is_due(now) {
564 reset_observations(inner, now);
565 }
566}
567
568fn in_zero_bucket(zero_threshold: f64, v: f64) -> bool {
569 if zero_threshold < 0.0 {
570 v == 0.0
571 } else {
572 v.abs() <= zero_threshold
573 }
574}
575
576fn increment_bucket(buckets: &mut NativeBuckets, index: i32) {
577 match buckets.binary_search_by_key(&index, |(bucket_index, _)| *bucket_index) {
578 Ok(position) => buckets[position].1 += 1,
579 Err(position) => buckets.insert(position, (index, 1)),
580 }
581}
582
583fn pick_schema(bucket_factor: f64) -> i8 {
584 let floor = bucket_factor.log2().log2().floor();
585 if floor <= -f64::from(SCHEMA_MAX) {
586 return SCHEMA_MAX;
587 }
588
589 if floor >= -f64::from(SCHEMA_MIN) {
590 return SCHEMA_MIN;
591 }
592
593 (SCHEMA_MIN..=SCHEMA_MAX)
594 .find(|schema| f64::from(*schema) == -floor)
595 .expect("schema is in range")
596}
597
598const NATIVE_HISTOGRAM_BOUNDS: [&[f64]; 9] = [
600 &[0.5],
601 &[0.5, 0.7071067811865475],
602 &[
603 0.5,
604 0.5946035575013605,
605 0.7071067811865475,
606 0.8408964152537144,
607 ],
608 &[
609 0.5,
610 0.5452538663326288,
611 0.5946035575013605,
612 0.6484197773255048,
613 0.7071067811865475,
614 0.7711054127039704,
615 0.8408964152537144,
616 0.9170040432046711,
617 ],
618 &[
619 0.5,
620 0.5221368912137069,
621 0.5452538663326288,
622 0.5693943173783458,
623 0.5946035575013605,
624 0.620928906036742,
625 0.6484197773255048,
626 0.6771277734684463,
627 0.7071067811865475,
628 0.7384130729697496,
629 0.7711054127039704,
630 0.805245165974627,
631 0.8408964152537144,
632 0.8781260801866495,
633 0.9170040432046711,
634 0.9576032806985735,
635 ],
636 &[
637 0.5,
638 0.5109485743270583,
639 0.5221368912137069,
640 0.5335702003384117,
641 0.5452538663326288,
642 0.5571933712979462,
643 0.5693943173783458,
644 0.5818624293887887,
645 0.5946035575013605,
646 0.6076236799902344,
647 0.620928906036742,
648 0.6345254785958666,
649 0.6484197773255048,
650 0.6626183215798706,
651 0.6771277734684463,
652 0.6919549409819159,
653 0.7071067811865475,
654 0.7225904034885232,
655 0.7384130729697496,
656 0.7545822137967112,
657 0.7711054127039704,
658 0.7879904225539431,
659 0.805245165974627,
660 0.8228777390769823,
661 0.8408964152537144,
662 0.8593096490612387,
663 0.8781260801866495,
664 0.8973545375015533,
665 0.9170040432046711,
666 0.9370838170551498,
667 0.9576032806985735,
668 0.9785720620876999,
669 ],
670 &[
671 0.5,
672 0.5054446430258502,
673 0.5109485743270583,
674 0.5165124395106142,
675 0.5221368912137069,
676 0.5278225891802786,
677 0.5335702003384117,
678 0.5393803988785598,
679 0.5452538663326288,
680 0.5511912916539204,
681 0.5571933712979462,
682 0.5632608093041209,
683 0.5693943173783458,
684 0.5755946149764913,
685 0.5818624293887887,
686 0.5881984958251406,
687 0.5946035575013605,
688 0.6010783657263515,
689 0.6076236799902344,
690 0.6142402680534349,
691 0.620928906036742,
692 0.6276903785123455,
693 0.6345254785958666,
694 0.6414350080393891,
695 0.6484197773255048,
696 0.6554806057623822,
697 0.6626183215798706,
698 0.6698337620266515,
699 0.6771277734684463,
700 0.6845012114872953,
701 0.6919549409819159,
702 0.6994898362691555,
703 0.7071067811865475,
704 0.7148066691959849,
705 0.7225904034885232,
706 0.7304588970903234,
707 0.7384130729697496,
708 0.7464538641456323,
709 0.7545822137967112,
710 0.762799075372269,
711 0.7711054127039704,
712 0.7795022001189185,
713 0.7879904225539431,
714 0.7965710756711334,
715 0.805245165974627,
716 0.8140137109286738,
717 0.8228777390769823,
718 0.8318382901633681,
719 0.8408964152537144,
720 0.8500531768592616,
721 0.8593096490612387,
722 0.8686669176368529,
723 0.8781260801866495,
724 0.8876882462632604,
725 0.8973545375015533,
726 0.9071260877501991,
727 0.9170040432046711,
728 0.9269895625416926,
729 0.9370838170551498,
730 0.9472879907934827,
731 0.9576032806985735,
732 0.9680308967461471,
733 0.9785720620876999,
734 0.9892280131939752,
735 ],
736 &[
737 0.5,
738 0.5027149505564014,
739 0.5054446430258502,
740 0.5081891574554764,
741 0.5109485743270583,
742 0.5137229745593818,
743 0.5165124395106142,
744 0.5193170509806894,
745 0.5221368912137069,
746 0.5249720429003435,
747 0.5278225891802786,
748 0.5306886136446309,
749 0.5335702003384117,
750 0.5364674337629877,
751 0.5393803988785598,
752 0.5423091811066545,
753 0.5452538663326288,
754 0.5482145409081883,
755 0.5511912916539204,
756 0.5541842058618393,
757 0.5571933712979462,
758 0.5602188762048033,
759 0.5632608093041209,
760 0.5663192597993595,
761 0.5693943173783458,
762 0.572486072215902,
763 0.5755946149764913,
764 0.5787200368168754,
765 0.5818624293887887,
766 0.585021884841625,
767 0.5881984958251406,
768 0.5913923554921704,
769 0.5946035575013605,
770 0.5978321960199137,
771 0.6010783657263515,
772 0.6043421618132907,
773 0.6076236799902344,
774 0.6109230164863786,
775 0.6142402680534349,
776 0.6175755319684665,
777 0.620928906036742,
778 0.6243004885946023,
779 0.6276903785123455,
780 0.6310986751971253,
781 0.6345254785958666,
782 0.637970889198196,
783 0.6414350080393891,
784 0.6449179367033329,
785 0.6484197773255048,
786 0.6519406325959679,
787 0.6554806057623822,
788 0.659039800633032,
789 0.6626183215798706,
790 0.6662162735415805,
791 0.6698337620266515,
792 0.6734708931164728,
793 0.6771277734684463,
794 0.6808045103191123,
795 0.6845012114872953,
796 0.688217985377265,
797 0.6919549409819159,
798 0.6957121878859629,
799 0.6994898362691555,
800 0.7032879969095076,
801 0.7071067811865475,
802 0.7109463010845827,
803 0.7148066691959849,
804 0.718687998724491,
805 0.7225904034885232,
806 0.7265139979245261,
807 0.7304588970903234,
808 0.7344252166684908,
809 0.7384130729697496,
810 0.7424225829363761,
811 0.7464538641456323,
812 0.7505070348132126,
813 0.7545822137967112,
814 0.7586795205991071,
815 0.762799075372269,
816 0.7669409989204777,
817 0.7711054127039704,
818 0.7752924388424999,
819 0.7795022001189185,
820 0.7837348199827764,
821 0.7879904225539431,
822 0.7922691326262467,
823 0.7965710756711334,
824 0.8008963778413465,
825 0.805245165974627,
826 0.8096175675974316,
827 0.8140137109286738,
828 0.8184337248834821,
829 0.8228777390769823,
830 0.8273458838280969,
831 0.8318382901633681,
832 0.8363550898207981,
833 0.8408964152537144,
834 0.8454623996346523,
835 0.8500531768592616,
836 0.8546688815502312,
837 0.8593096490612387,
838 0.8639756154809185,
839 0.8686669176368529,
840 0.8733836930995842,
841 0.8781260801866495,
842 0.8828942179666361,
843 0.8876882462632604,
844 0.8925083056594671,
845 0.8973545375015533,
846 0.9022270839033115,
847 0.9071260877501991,
848 0.9120516927035263,
849 0.9170040432046711,
850 0.9219832844793128,
851 0.9269895625416926,
852 0.9320230241988943,
853 0.9370838170551498,
854 0.9421720895161669,
855 0.9472879907934827,
856 0.9524316709088368,
857 0.9576032806985735,
858 0.9628029718180622,
859 0.9680308967461471,
860 0.9732872087896164,
861 0.9785720620876999,
862 0.9838856116165875,
863 0.9892280131939752,
864 0.9945994234836328,
865 ],
866 &[
867 0.5,
868 0.5013556375251013,
869 0.5027149505564014,
870 0.5040779490592088,
871 0.5054446430258502,
872 0.5068150424757447,
873 0.5081891574554764,
874 0.509566998038869,
875 0.5109485743270583,
876 0.5123338964485679,
877 0.5137229745593818,
878 0.5151158188430205,
879 0.5165124395106142,
880 0.5179128468009786,
881 0.5193170509806894,
882 0.520725062344158,
883 0.5221368912137069,
884 0.5235525479396449,
885 0.5249720429003435,
886 0.526395386502313,
887 0.5278225891802786,
888 0.5292536613972564,
889 0.5306886136446309,
890 0.5321274564422321,
891 0.5335702003384117,
892 0.5350168559101208,
893 0.5364674337629877,
894 0.5379219445313954,
895 0.5393803988785598,
896 0.5408428074966075,
897 0.5423091811066545,
898 0.5437795304588847,
899 0.5452538663326288,
900 0.5467321995364429,
901 0.5482145409081883,
902 0.549700901315111,
903 0.5511912916539204,
904 0.5526857228508706,
905 0.5541842058618393,
906 0.5556867516724088,
907 0.5571933712979462,
908 0.5587040757836845,
909 0.5602188762048033,
910 0.5617377836665098,
911 0.5632608093041209,
912 0.564787964283144,
913 0.5663192597993595,
914 0.5678547070789026,
915 0.5693943173783458,
916 0.5709381019847808,
917 0.572486072215902,
918 0.5740382394200894,
919 0.5755946149764913,
920 0.5771552102951081,
921 0.5787200368168754,
922 0.5802891060137493,
923 0.5818624293887887,
924 0.5834400184762408,
925 0.585021884841625,
926 0.5866080400818185,
927 0.5881984958251406,
928 0.5897932637314379,
929 0.5913923554921704,
930 0.5929957828304968,
931 0.5946035575013605,
932 0.5962156912915756,
933 0.5978321960199137,
934 0.5994530835371903,
935 0.6010783657263515,
936 0.6027080545025619,
937 0.6043421618132907,
938 0.6059806996384005,
939 0.6076236799902344,
940 0.6092711149137041,
941 0.6109230164863786,
942 0.6125793968185725,
943 0.6142402680534349,
944 0.6159056423670379,
945 0.6175755319684665,
946 0.6192499490999082,
947 0.620928906036742,
948 0.622612415087629,
949 0.6243004885946023,
950 0.6259931389331581,
951 0.6276903785123455,
952 0.6293922197748583,
953 0.6310986751971253,
954 0.6328097572894031,
955 0.6345254785958666,
956 0.6362458516947014,
957 0.637970889198196,
958 0.6397006037528346,
959 0.6414350080393891,
960 0.6431741147730128,
961 0.6449179367033329,
962 0.6466664866145447,
963 0.6484197773255048,
964 0.6501778216898253,
965 0.6519406325959679,
966 0.6537082229673385,
967 0.6554806057623822,
968 0.6572577939746774,
969 0.659039800633032,
970 0.6608266388015788,
971 0.6626183215798706,
972 0.6644148621029772,
973 0.6662162735415805,
974 0.6680225691020727,
975 0.6698337620266515,
976 0.6716498655934177,
977 0.6734708931164728,
978 0.6752968579460171,
979 0.6771277734684463,
980 0.6789636531064505,
981 0.6808045103191123,
982 0.6826503586020058,
983 0.6845012114872953,
984 0.6863570825438342,
985 0.688217985377265,
986 0.690083933630119,
987 0.6919549409819159,
988 0.6938310211492645,
989 0.6957121878859629,
990 0.6975984549830999,
991 0.6994898362691555,
992 0.7013863456101023,
993 0.7032879969095076,
994 0.7051948041086352,
995 0.7071067811865475,
996 0.7090239421602076,
997 0.7109463010845827,
998 0.7128738720527471,
999 0.7148066691959849,
1000 0.7167447066838943,
1001 0.718687998724491,
1002 0.7206365595643126,
1003 0.7225904034885232,
1004 0.7245495448210174,
1005 0.7265139979245261,
1006 0.7284837772007218,
1007 0.7304588970903234,
1008 0.7324393720732029,
1009 0.7344252166684908,
1010 0.7364164454346837,
1011 0.7384130729697496,
1012 0.7404151139112358,
1013 0.7424225829363761,
1014 0.7444354947621984,
1015 0.7464538641456323,
1016 0.7484777058836176,
1017 0.7505070348132126,
1018 0.7525418658117031,
1019 0.7545822137967112,
1020 0.7566280937263048,
1021 0.7586795205991071,
1022 0.7607365094544071,
1023 0.762799075372269,
1024 0.7648672334736434,
1025 0.7669409989204777,
1026 0.7690203869158282,
1027 0.7711054127039704,
1028 0.7731960915705107,
1029 0.7752924388424999,
1030 0.7773944698885442,
1031 0.7795022001189185,
1032 0.7816156449856788,
1033 0.7837348199827764,
1034 0.7858597406461707,
1035 0.7879904225539431,
1036 0.7901268813264122,
1037 0.7922691326262467,
1038 0.7944171921585818,
1039 0.7965710756711334,
1040 0.7987307989543135,
1041 0.8008963778413465,
1042 0.8030678282083853,
1043 0.805245165974627,
1044 0.8074284071024302,
1045 0.8096175675974316,
1046 0.8118126635086642,
1047 0.8140137109286738,
1048 0.8162207259936375,
1049 0.8184337248834821,
1050 0.820652723822003,
1051 0.8228777390769823,
1052 0.8251087869603088,
1053 0.8273458838280969,
1054 0.8295890460808079,
1055 0.8318382901633681,
1056 0.8340936325652911,
1057 0.8363550898207981,
1058 0.8386226785089391,
1059 0.8408964152537144,
1060 0.8431763167241966,
1061 0.8454623996346523,
1062 0.8477546807446661,
1063 0.8500531768592616,
1064 0.8523579048290255,
1065 0.8546688815502312,
1066 0.8569861239649629,
1067 0.8593096490612387,
1068 0.8616394738731368,
1069 0.8639756154809185,
1070 0.8663180910111553,
1071 0.8686669176368529,
1072 0.871022112577578,
1073 0.8733836930995842,
1074 0.8757516765159389,
1075 0.8781260801866495,
1076 0.8805069215187917,
1077 0.8828942179666361,
1078 0.8852879870317771,
1079 0.8876882462632604,
1080 0.890095013257712,
1081 0.8925083056594671,
1082 0.8949281411607002,
1083 0.8973545375015533,
1084 0.8997875124702672,
1085 0.9022270839033115,
1086 0.9046732696855155,
1087 0.9071260877501991,
1088 0.909585556079304,
1089 0.9120516927035263,
1090 0.9145245157024483,
1091 0.9170040432046711,
1092 0.9194902933879467,
1093 0.9219832844793128,
1094 0.9244830347552253,
1095 0.9269895625416926,
1096 0.92950288621441,
1097 0.9320230241988943,
1098 0.9345499949706191,
1099 0.9370838170551498,
1100 0.93962450902828,
1101 0.9421720895161669,
1102 0.9447265771954693,
1103 0.9472879907934827,
1104 0.9498563490882775,
1105 0.9524316709088368,
1106 0.9550139751351947,
1107 0.9576032806985735,
1108 0.9601996065815236,
1109 0.9628029718180622,
1110 0.9654133954938133,
1111 0.9680308967461471,
1112 0.9706554947643201,
1113 0.9732872087896164,
1114 0.9759260581154889,
1115 0.9785720620876999,
1116 0.9812252401044634,
1117 0.9838856116165875,
1118 0.9865531961276168,
1119 0.9892280131939752,
1120 0.9919100824251095,
1121 0.9945994234836328,
1122 0.9972960560854698,
1123 ],
1124];
1125
1126fn bucket_index(schema: i8, v: f64, is_infinite: bool) -> i32 {
1127 let v = if is_infinite { f64::MAX } else { v };
1128 let (frac, exp) = frexp(v);
1129 let mut index = if schema > 0 {
1130 let bounds = NATIVE_HISTOGRAM_BOUNDS
1131 [usize::try_from(schema).expect("positive schema can be indexed")];
1132 let bucket =
1133 i32::try_from(bounds.partition_point(|bound| *bound < frac)).expect("bounds fit i32");
1134 bucket + (exp - 1) * i32::try_from(bounds.len()).expect("bounds length fits i32")
1135 } else {
1136 let mut key = exp;
1137 if frac == 0.5 {
1138 key -= 1;
1139 }
1140 let shift = u32::try_from(-i32::from(schema)).expect("non-positive schema shift");
1141 let offset = (1_i32 << shift) - 1;
1142 (key + offset) >> shift
1143 };
1144
1145 if is_infinite {
1146 index = index.saturating_add(1);
1147 }
1148 index
1149}
1150
1151fn frexp(v: f64) -> (f64, i32) {
1152 debug_assert!(v >= 0.0);
1153 debug_assert!(!v.is_nan());
1154
1155 if v == 0.0 {
1156 return (0.0, 0);
1157 }
1158
1159 let bits = v.to_bits();
1160 let exponent = i32::try_from((bits >> 52) & 0x7ff).expect("f64 exponent fits i32");
1161 let mantissa = bits & ((1_u64 << 52) - 1);
1162
1163 if exponent == 0 {
1164 let p = 63 - i32::try_from(mantissa.leading_zeros()).expect("leading zeros fit i32");
1165 let frac = mantissa as f64 / 2f64.powi(p + 1);
1166 (frac, p - 1073)
1167 } else {
1168 let frac = ((1_u64 << 52) | mantissa) as f64 / 2f64.powi(53);
1169 (frac, exponent - 1022)
1170 }
1171}
1172
1173fn enforce_bucket_limit(inner: &mut NativeHistogramState) -> bool {
1174 if inner.max_buckets == 0 {
1175 return false;
1176 }
1177
1178 if inner.positive.len() + inner.negative.len() <= inner.max_buckets {
1179 return false;
1180 }
1181
1182 if inner.scheduled_reset.is_none()
1183 && inner
1184 .min_reset_duration
1185 .and_then(|min_reset_duration| {
1186 inner
1187 .created
1188 .elapsed()
1189 .ok()
1190 .map(|e| e >= min_reset_duration)
1191 })
1192 .unwrap_or(false)
1193 {
1194 return true;
1195 }
1196
1197 if widen_zero_bucket(inner) {
1198 inner.schedule_reset_after_degradation();
1199 return false;
1200 }
1201
1202 if inner.schema > SCHEMA_MIN {
1203 inner.schema -= 1;
1204 inner.positive = downsample_buckets(&inner.positive);
1205 inner.negative = downsample_buckets(&inner.negative);
1206 inner.schedule_reset_after_degradation();
1207 }
1208
1209 false
1210}
1211
1212fn widen_zero_bucket(inner: &mut NativeHistogramState) -> bool {
1213 let smallest_key = match (
1214 inner.positive.first().map(|(index, _)| *index),
1215 inner.negative.first().map(|(index, _)| *index),
1216 ) {
1217 (Some(positive), Some(negative)) => positive.min(negative),
1218 (Some(positive), None) => positive,
1219 (None, Some(negative)) => negative,
1220 (None, None) => return false,
1221 };
1222
1223 let new_threshold = positive_upper_bound(inner.schema, smallest_key);
1224 let current_threshold = if inner.zero_threshold < 0.0 {
1225 0.0
1226 } else {
1227 inner.zero_threshold
1228 };
1229 if new_threshold <= current_threshold || new_threshold > inner.max_zero_threshold {
1230 return false;
1231 }
1232
1233 let moved_positive = move_to_zero_bucket(inner.schema, new_threshold, &mut inner.positive);
1234 let moved_negative = move_to_zero_bucket(inner.schema, new_threshold, &mut inner.negative);
1235
1236 let moved = moved_positive + moved_negative;
1237 if moved == 0 {
1238 return false;
1239 }
1240
1241 inner.zero_count += moved;
1242 inner.zero_threshold = new_threshold;
1243 true
1244}
1245
1246fn move_to_zero_bucket(schema: i8, threshold: f64, buckets: &mut NativeBuckets) -> u64 {
1247 if buckets.is_empty() {
1248 return 0;
1249 }
1250
1251 let mut split = 0;
1252 for (index, _count) in buckets.iter() {
1253 let upper = positive_upper_bound(schema, *index);
1254 if upper <= threshold {
1255 split += 1;
1256 } else {
1257 break;
1258 }
1259 }
1260
1261 let moved = buckets[..split].iter().map(|(_, count)| *count).sum();
1262 buckets.drain(..split);
1263
1264 moved
1265}
1266
1267fn positive_upper_bound(schema: i8, index: i32) -> f64 {
1268 if schema < 0 {
1269 let exp = index << u32::try_from(-i32::from(schema)).expect("negative schema shift");
1270 if exp == 1024 {
1271 return f64::MAX;
1272 }
1273 return 2f64.powi(exp);
1274 }
1275
1276 let schema = u32::try_from(schema).expect("non-negative schema shift");
1277 let bounds = NATIVE_HISTOGRAM_BOUNDS[usize::try_from(schema).expect("schema can be indexed")];
1278 let frac = bounds[usize::try_from(index & ((1 << schema) - 1)).expect("index can be indexed")];
1279 let exp = (index >> schema) + 1;
1280 if frac == 0.5 && exp == 1025 {
1281 return f64::MAX;
1282 }
1283 frac * 2f64.powi(exp)
1284}
1285
1286fn downsample_buckets(buckets: &NativeBuckets) -> NativeBuckets {
1287 let mut downsampled: NativeBuckets = Vec::with_capacity(buckets.len());
1288 for (index, count) in buckets {
1289 let mut key = *index;
1290 if key > 0 {
1291 key += 1;
1292 }
1293 key /= 2;
1294 if let Some((last_key, last_count)) = downsampled.last_mut() {
1295 if *last_key == key {
1296 *last_count += *count;
1297 continue;
1298 }
1299 }
1300 downsampled.push((key, *count));
1301 }
1302 downsampled
1303}
1304
1305fn encode_spans_and_deltas(
1306 buckets: &NativeBuckets,
1307) -> Result<NativeBucketEncoding, std::fmt::Error> {
1308 let mut deltas = Vec::with_capacity(buckets.len());
1309 let mut previous_count = 0i64;
1310 let mut next_index = 0i32;
1311 let mut spans: Vec<(i32, u32)> = Vec::new();
1312
1313 let mut append_delta = |count: i64, spans: &mut Vec<(i32, u32)>| {
1314 if let Some((_, len)) = spans.last_mut() {
1315 *len += 1;
1316 }
1317 deltas.push(count - previous_count);
1318 previous_count = count;
1319 };
1320
1321 for (n, &(index, count)) in buckets.iter().enumerate() {
1322 let count = i64::try_from(count).map_err(|_| std::fmt::Error)?;
1323 let index_delta = index - next_index;
1324
1325 if n == 0 || index_delta > 2 {
1326 spans.push((index_delta, 0));
1327 } else {
1328 for _ in 0..index_delta {
1329 append_delta(0, &mut spans);
1330 }
1331 }
1332
1333 append_delta(count, &mut spans);
1334 next_index = index + 1;
1335 }
1336
1337 Ok(NativeBucketEncoding { spans, deltas })
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342 use super::*;
1343
1344 #[test]
1345 fn histogram() {
1346 let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10));
1347 histogram.observe(1.0);
1348 }
1349
1350 #[test]
1351 fn exponential() {
1352 assert_eq!(
1353 vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0],
1354 exponential_buckets(1.0, 2.0, 10).collect::<Vec<_>>()
1355 );
1356 }
1357
1358 #[test]
1359 fn linear() {
1360 assert_eq!(
1361 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
1362 linear_buckets(0.0, 1.0, 10).collect::<Vec<_>>()
1363 );
1364 }
1365
1366 #[test]
1367 fn exponential_range() {
1368 assert_eq!(
1369 vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0],
1370 exponential_buckets_range(1.0, 32.0, 6).collect::<Vec<_>>()
1371 );
1372 }
1373
1374 #[test]
1375 fn exponential_range_incorrect() {
1376 let res = exponential_buckets_range(1.0, 32.0, 0).collect::<Vec<_>>();
1377 assert!(res.is_empty());
1378
1379 let res = exponential_buckets_range(0.0, 32.0, 6).collect::<Vec<_>>();
1380 assert!(res.is_empty());
1381 }
1382
1383 #[test]
1385 fn count() {
1386 let histogram = Histogram::new([1.0_f64, 2.0, 3.0, 4.0, 5.0]);
1387 assert_eq!(
1388 histogram.count(),
1389 0,
1390 "histogram has zero observations when instantiated"
1391 );
1392
1393 histogram.observe(1.0);
1394 assert_eq!(histogram.count(), 1, "histogram has one observation");
1395
1396 histogram.observe(2.5);
1397 assert_eq!(histogram.count(), 2, "histogram has two observations");
1398
1399 histogram.observe(6.0);
1400 assert_eq!(histogram.count(), 3, "histogram has three observations");
1401 }
1402
1403 #[test]
1405 fn sum() {
1406 const BUCKETS: [f64; 3] = [10.0, 100.0, 1000.0];
1407 let histogram = Histogram::new(BUCKETS);
1408 assert_eq!(
1409 histogram.sum(),
1410 0.0,
1411 "histogram sum is zero when instantiated"
1412 );
1413
1414 histogram.observe(3.0); histogram.observe(4.0);
1416 histogram.observe(15.0);
1417 histogram.observe(101.0);
1418 assert_eq!(
1419 histogram.sum(),
1420 123.0,
1421 "histogram sum records accurate sum of observations"
1422 );
1423
1424 histogram.observe(1111.0);
1425 assert_eq!(
1426 histogram.sum(),
1427 1234.0,
1428 "histogram sum records accurate sum of observations"
1429 );
1430 }
1431
1432 #[test]
1433 fn native_histogram_stores_sparse_buckets() {
1434 let h = Histogram::new_native(NativeHistogramConfig::with_schema(0));
1435 h.observe(1.0);
1436 h.observe(4.0);
1437 h.observe(-2.0);
1438 h.observe(0.0);
1439
1440 let inner = h.inner.lock();
1441 let native = inner.native.as_ref().unwrap();
1442 assert_eq!(4, inner.count);
1443 assert_eq!(1, native.zero_count);
1444 assert_eq!(2, native.positive.len());
1445 assert_eq!(1, native.negative.len());
1446 }
1447
1448 #[test]
1449 fn native_histogram_counts_nan_without_sparse_bucket() {
1450 let h = Histogram::new_native(NativeHistogramConfig::with_schema(2));
1451 h.observe(f64::NAN);
1452 assert_eq!(1, h.count());
1453 assert!(h.sum().is_nan());
1454
1455 let inner = h.inner.lock();
1456 let native = inner.native.as_ref().unwrap();
1457 assert_eq!(0, native.zero_count);
1458 assert!(native.positive.is_empty());
1459 assert!(native.negative.is_empty());
1460 }
1461
1462 #[test]
1463 fn classic_histogram_counts_nan_in_infinity_bucket() {
1464 let h = Histogram::new([1.0, 2.0]);
1465 h.observe(f64::NAN);
1466
1467 let inner = h.inner.lock();
1468 assert_eq!(1, inner.count);
1469 assert!(inner.sum.is_nan());
1470 assert_eq!(1, inner.buckets[2].1);
1471 }
1472
1473 #[test]
1474 fn native_histogram_supports_zero_width_zero_bucket() {
1475 let h = Histogram::new_native(
1476 NativeHistogramConfig::with_schema(0)
1477 .zero_threshold(NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO),
1478 );
1479 h.observe(0.0);
1480 h.observe(0.01);
1481
1482 let inner = h.inner.lock();
1483 let native = inner.native.as_ref().unwrap();
1484 assert_eq!(1, native.zero_count);
1485 assert_eq!(
1486 1,
1487 native.positive.iter().map(|(_, count)| *count).sum::<u64>()
1488 );
1489 }
1490
1491 #[test]
1492 fn native_histogram_zero_threshold_zero_uses_default() {
1493 let h = Histogram::new_native(NativeHistogramConfig::with_schema(0).zero_threshold(0.0));
1494 let inner = h.inner.lock();
1495 let native = inner.native.as_ref().unwrap();
1496 assert_eq!(
1497 DEFAULT_NATIVE_HISTOGRAM_ZERO_THRESHOLD,
1498 native.zero_threshold
1499 );
1500 }
1501
1502 #[test]
1503 fn native_histogram_negative_zero_threshold_uses_zero_width_zero_bucket() {
1504 let h = Histogram::new_native(NativeHistogramConfig::with_schema(0).zero_threshold(-2.0));
1505 h.observe(0.0);
1506 h.observe(0.01);
1507
1508 let inner = h.inner.lock();
1509 let native = inner.native.as_ref().unwrap();
1510 assert_eq!(NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO, native.zero_threshold);
1511 assert_eq!(1, native.zero_count);
1512 assert_eq!(
1513 1,
1514 native.positive.iter().map(|(_, count)| *count).sum::<u64>()
1515 );
1516 }
1517
1518 #[test]
1519 fn native_histogram_reduces_resolution_when_max_bucket_limit_is_hit() {
1520 let h = Histogram::new_native(NativeHistogramConfig::with_schema(8).max_buckets(1));
1521 h.observe(1.0);
1522 h.observe(1.1);
1523
1524 let inner = h.inner.lock();
1525 let native = inner.native.as_ref().unwrap();
1526 assert!(native.schema < 8);
1527 }
1528
1529 #[test]
1530 fn native_histogram_widens_zero_bucket_before_reducing_resolution() {
1531 let h = Histogram::new_native(
1532 NativeHistogramConfig::with_schema(8)
1533 .max_buckets(1)
1534 .max_zero_threshold(1.0),
1535 );
1536 h.observe(2f64.powi(-100));
1537 h.observe(1.0);
1538
1539 let inner = h.inner.lock();
1540 let native = inner.native.as_ref().unwrap();
1541 assert_eq!(8, native.schema);
1542 assert_eq!(1, native.zero_count);
1543 assert_eq!(1, native.positive.len());
1544 }
1545
1546 #[test]
1547 fn native_histogram_schedules_reset_after_degradation() {
1548 let h = Histogram::new_native(
1549 NativeHistogramConfig::with_schema(8)
1550 .max_buckets(1)
1551 .min_reset_duration(Duration::from_secs(60)),
1552 );
1553 h.observe(1.0);
1554 h.observe(1.1);
1555
1556 {
1557 let mut inner = h.inner.lock();
1558 let native = inner.native.as_mut().unwrap();
1559 assert!(native.schema < 8);
1560 assert!(native.scheduled_reset.is_some());
1561 native.scheduled_reset = Some(SystemTime::now() - Duration::from_secs(1));
1562 }
1563
1564 let snapshot = h.snapshot().unwrap();
1565 let native = snapshot.native.unwrap();
1566
1567 assert_eq!(0, snapshot.count);
1568 assert_eq!(8, native.schema);
1569 assert_eq!(0, native.zero_count);
1570 assert!(native.positive.spans.is_empty());
1571 assert!(native.negative.spans.is_empty());
1572 }
1573
1574 #[test]
1575 fn native_histogram_scheduled_reset_is_triggered_by_observe() {
1576 let h = Histogram::new_native(
1577 NativeHistogramConfig::with_schema(8)
1578 .max_buckets(1)
1579 .min_reset_duration(Duration::from_secs(60)),
1580 );
1581 h.observe(1.0);
1582 h.observe(1.1);
1583
1584 {
1585 let mut inner = h.inner.lock();
1586 let native = inner.native.as_mut().unwrap();
1587 assert!(native.schema < 8);
1588 assert!(native.scheduled_reset.is_some());
1589 native.scheduled_reset = Some(SystemTime::now() - Duration::from_secs(1));
1590 }
1591
1592 h.observe(2.0);
1593
1594 let inner = h.inner.lock();
1595 let native = inner.native.as_ref().unwrap();
1596 assert_eq!(1, inner.count);
1597 assert_eq!(2.0, inner.sum);
1598 assert_eq!(8, native.schema);
1599 assert_eq!(0, native.zero_count);
1600 assert_eq!(1, native.positive.len());
1601 assert_eq!(1, native.positive[0].1);
1602 assert!(native.negative.is_empty());
1603 }
1604
1605 #[test]
1606 fn native_histogram_resets_when_limit_is_hit_after_min_reset_duration() {
1607 let h = Histogram::new_native(
1608 NativeHistogramConfig::with_schema(8)
1609 .max_buckets(1)
1610 .min_reset_duration(Duration::from_secs(1)),
1611 );
1612 h.observe(1.0);
1613 {
1614 let mut inner = h.inner.lock();
1615 inner.native.as_mut().unwrap().created = SystemTime::now() - Duration::from_secs(2);
1616 }
1617 h.observe(2.0);
1618
1619 let inner = h.inner.lock();
1620 let native = inner.native.as_ref().unwrap();
1621 assert_eq!(1, inner.count);
1622 assert_eq!(2.0, inner.sum);
1623 assert_eq!(8, native.schema);
1624 assert_eq!(
1625 1,
1626 native.positive.iter().map(|(_, count)| *count).sum::<u64>()
1627 );
1628 }
1629
1630 #[test]
1631 fn native_histogram_zero_min_reset_duration_does_not_reset() {
1632 let h = Histogram::new_native(
1633 NativeHistogramConfig::with_schema(8)
1634 .max_buckets(1)
1635 .min_reset_duration(Duration::ZERO),
1636 );
1637 h.observe(1.0);
1638 h.observe(1.1);
1639
1640 let inner = h.inner.lock();
1641 let native = inner.native.as_ref().unwrap();
1642 assert_eq!(2, inner.count);
1643 assert!(native.schema < 8);
1644 assert!(native.scheduled_reset.is_none());
1645 }
1646
1647 #[test]
1648 fn native_histogram_supports_bucket_factor_constructor() {
1649 let h = Histogram::new_native(NativeHistogramConfig::new(1.1));
1650 let inner = h.inner.lock();
1651 assert_eq!(3, inner.native.as_ref().unwrap().schema);
1652 }
1653
1654 #[test]
1655 fn native_histogram_default_uses_recommended_bucket_factor() {
1656 let h = Histogram::new_native(NativeHistogramConfig::default());
1657 let inner = h.inner.lock();
1658 assert_eq!(3, inner.native.as_ref().unwrap().schema);
1659 }
1660
1661 #[derive(Debug)]
1664 struct NativeHistogramParityCase {
1665 name: &'static str,
1666 observations: &'static [f64],
1667 bucket_factor: f64,
1668 zero_threshold: Option<f64>,
1669 max_buckets: usize,
1670 max_zero_threshold: f64,
1671 sample_count: u64,
1672 sample_sum: f64,
1673 schema: i32,
1674 expected_zero_threshold: f64,
1675 zero_count: u64,
1676 negative_spans: &'static [(i32, u32)],
1677 negative_deltas: &'static [i64],
1678 positive_spans: &'static [(i32, u32)],
1679 positive_deltas: &'static [i64],
1680 }
1681
1682 #[test]
1683 fn native_histogram_matches_client_golang_scenarios() {
1684 const DEFAULT_ZERO_THRESHOLD: f64 = 2.938735877055719e-39;
1685 const ZERO_WIDTH_ZERO_BUCKET: f64 = NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO;
1686 const NAN: f64 = f64::NAN;
1687 const INF: f64 = f64::INFINITY;
1688 const NEG_INF: f64 = f64::NEG_INFINITY;
1689
1690 let cases = [
1691 NativeHistogramParityCase {
1692 name: "no observations",
1693 observations: &[],
1694 bucket_factor: 1.1,
1695 zero_threshold: None,
1696 max_buckets: 0,
1697 max_zero_threshold: 0.0,
1698 sample_count: 0,
1699 sample_sum: 0.0,
1700 schema: 3,
1701 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1702 zero_count: 0,
1703 negative_spans: &[],
1704 negative_deltas: &[],
1705 positive_spans: &[],
1706 positive_deltas: &[],
1707 },
1708 NativeHistogramParityCase {
1709 name: "no observations and zero threshold of zero resulting in no-op span",
1710 observations: &[],
1711 bucket_factor: 1.1,
1712 zero_threshold: Some(ZERO_WIDTH_ZERO_BUCKET),
1713 max_buckets: 0,
1714 max_zero_threshold: 0.0,
1715 sample_count: 0,
1716 sample_sum: 0.0,
1717 schema: 3,
1718 expected_zero_threshold: 0.0,
1719 zero_count: 0,
1720 negative_spans: &[],
1721 negative_deltas: &[],
1722 positive_spans: &[(0, 0)],
1723 positive_deltas: &[],
1724 },
1725 NativeHistogramParityCase {
1726 name: "factor 1.1 results in schema 3",
1727 observations: &[0.0, 1.0, 2.0, 3.0],
1728 bucket_factor: 1.1,
1729 zero_threshold: None,
1730 max_buckets: 0,
1731 max_zero_threshold: 0.0,
1732 sample_count: 4,
1733 sample_sum: 6.0,
1734 schema: 3,
1735 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1736 zero_count: 1,
1737 negative_spans: &[],
1738 negative_deltas: &[],
1739 positive_spans: &[(0, 1), (7, 1), (4, 1)],
1740 positive_deltas: &[1, 0, 0],
1741 },
1742 NativeHistogramParityCase {
1743 name: "factor 1.2 results in schema 2",
1744 observations: &[0.0, 1.0, 1.2, 1.4, 1.8, 2.0],
1745 bucket_factor: 1.2,
1746 zero_threshold: None,
1747 max_buckets: 0,
1748 max_zero_threshold: 0.0,
1749 sample_count: 6,
1750 sample_sum: 7.4,
1751 schema: 2,
1752 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1753 zero_count: 1,
1754 negative_spans: &[],
1755 negative_deltas: &[],
1756 positive_spans: &[(0, 5)],
1757 positive_deltas: &[1, -1, 2, -2, 2],
1758 },
1759 NativeHistogramParityCase {
1760 name: "factor 4 results in schema -1",
1761 observations: &[
1762 0.0156251, 0.0625, 0.1, 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 3.5, 5.0, 6.0, 7.0,
1763 33.33,
1764 ],
1765 bucket_factor: 4.0,
1766 zero_threshold: None,
1767 max_buckets: 0,
1768 max_zero_threshold: 0.0,
1769 sample_count: 14,
1770 sample_sum: 63.2581251,
1771 schema: -1,
1772 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1773 zero_count: 0,
1774 negative_spans: &[],
1775 negative_deltas: &[],
1776 positive_spans: &[(-2, 6)],
1777 positive_deltas: &[2, 0, 0, 2, -1, -2],
1778 },
1779 NativeHistogramParityCase {
1780 name: "factor 17 results in schema -2",
1781 observations: &[
1782 0.0156251, 0.0625, 0.1, 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 3.5, 5.0, 6.0, 7.0,
1783 33.33,
1784 ],
1785 bucket_factor: 17.0,
1786 zero_threshold: None,
1787 max_buckets: 0,
1788 max_zero_threshold: 0.0,
1789 sample_count: 14,
1790 sample_sum: 63.2581251,
1791 schema: -2,
1792 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1793 zero_count: 0,
1794 negative_spans: &[],
1795 negative_deltas: &[],
1796 positive_spans: &[(-1, 4)],
1797 positive_deltas: &[2, 2, 3, -6],
1798 },
1799 NativeHistogramParityCase {
1800 name: "negative buckets",
1801 observations: &[0.0, -1.0, -1.2, -1.4, -1.8, -2.0],
1802 bucket_factor: 1.2,
1803 zero_threshold: None,
1804 max_buckets: 0,
1805 max_zero_threshold: 0.0,
1806 sample_count: 6,
1807 sample_sum: -7.4,
1808 schema: 2,
1809 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1810 zero_count: 1,
1811 negative_spans: &[(0, 5)],
1812 negative_deltas: &[1, -1, 2, -2, 2],
1813 positive_spans: &[],
1814 positive_deltas: &[],
1815 },
1816 NativeHistogramParityCase {
1817 name: "negative and positive buckets",
1818 observations: &[0.0, -1.0, -1.2, -1.4, -1.8, -2.0, 1.0, 1.2, 1.4, 1.8, 2.0],
1819 bucket_factor: 1.2,
1820 zero_threshold: None,
1821 max_buckets: 0,
1822 max_zero_threshold: 0.0,
1823 sample_count: 11,
1824 sample_sum: 0.0,
1825 schema: 2,
1826 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1827 zero_count: 1,
1828 negative_spans: &[(0, 5)],
1829 negative_deltas: &[1, -1, 2, -2, 2],
1830 positive_spans: &[(0, 5)],
1831 positive_deltas: &[1, -1, 2, -2, 2],
1832 },
1833 NativeHistogramParityCase {
1834 name: "wide zero bucket",
1835 observations: &[0.0, -1.0, -1.2, -1.4, -1.8, -2.0, 1.0, 1.2, 1.4, 1.8, 2.0],
1836 bucket_factor: 1.2,
1837 zero_threshold: Some(1.4),
1838 max_buckets: 0,
1839 max_zero_threshold: 0.0,
1840 sample_count: 11,
1841 sample_sum: 0.0,
1842 schema: 2,
1843 expected_zero_threshold: 1.4,
1844 zero_count: 7,
1845 negative_spans: &[(4, 1)],
1846 negative_deltas: &[2],
1847 positive_spans: &[(4, 1)],
1848 positive_deltas: &[2],
1849 },
1850 NativeHistogramParityCase {
1851 name: "NaN observation",
1852 observations: &[0.0, 1.0, 1.2, 1.4, 1.8, 2.0, NAN],
1853 bucket_factor: 1.2,
1854 zero_threshold: None,
1855 max_buckets: 0,
1856 max_zero_threshold: 0.0,
1857 sample_count: 7,
1858 sample_sum: NAN,
1859 schema: 2,
1860 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1861 zero_count: 1,
1862 negative_spans: &[],
1863 negative_deltas: &[],
1864 positive_spans: &[(0, 5)],
1865 positive_deltas: &[1, -1, 2, -2, 2],
1866 },
1867 NativeHistogramParityCase {
1868 name: "+Inf observation",
1869 observations: &[0.0, 1.0, 1.2, 1.4, 1.8, 2.0, INF],
1870 bucket_factor: 1.2,
1871 zero_threshold: None,
1872 max_buckets: 0,
1873 max_zero_threshold: 0.0,
1874 sample_count: 7,
1875 sample_sum: INF,
1876 schema: 2,
1877 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1878 zero_count: 1,
1879 negative_spans: &[],
1880 negative_deltas: &[],
1881 positive_spans: &[(0, 5), (4092, 1)],
1882 positive_deltas: &[1, -1, 2, -2, 2, -1],
1883 },
1884 NativeHistogramParityCase {
1885 name: "-Inf observation",
1886 observations: &[0.0, 1.0, 1.2, 1.4, 1.8, 2.0, NEG_INF],
1887 bucket_factor: 1.2,
1888 zero_threshold: None,
1889 max_buckets: 0,
1890 max_zero_threshold: 0.0,
1891 sample_count: 7,
1892 sample_sum: NEG_INF,
1893 schema: 2,
1894 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1895 zero_count: 1,
1896 negative_spans: &[(4097, 1)],
1897 negative_deltas: &[1],
1898 positive_spans: &[(0, 5)],
1899 positive_deltas: &[1, -1, 2, -2, 2],
1900 },
1901 NativeHistogramParityCase {
1902 name: "limited buckets but nothing triggered",
1903 observations: &[0.0, 1.0, 1.2, 1.4, 1.8, 2.0],
1904 bucket_factor: 1.2,
1905 zero_threshold: None,
1906 max_buckets: 4,
1907 max_zero_threshold: 0.0,
1908 sample_count: 6,
1909 sample_sum: 7.4,
1910 schema: 2,
1911 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1912 zero_count: 1,
1913 negative_spans: &[],
1914 negative_deltas: &[],
1915 positive_spans: &[(0, 5)],
1916 positive_deltas: &[1, -1, 2, -2, 2],
1917 },
1918 NativeHistogramParityCase {
1919 name: "buckets limited by halving resolution",
1920 observations: &[0.0, 1.0, 1.1, 1.2, 1.4, 1.8, 2.0, 3.0],
1921 bucket_factor: 1.2,
1922 zero_threshold: None,
1923 max_buckets: 4,
1924 max_zero_threshold: 0.0,
1925 sample_count: 8,
1926 sample_sum: 11.5,
1927 schema: 1,
1928 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1929 zero_count: 1,
1930 negative_spans: &[],
1931 negative_deltas: &[],
1932 positive_spans: &[(0, 5)],
1933 positive_deltas: &[1, 2, -1, -2, 1],
1934 },
1935 NativeHistogramParityCase {
1936 name: "buckets limited by widening the zero bucket",
1937 observations: &[0.0, 1.0, 1.1, 1.2, 1.4, 1.8, 2.0, 3.0],
1938 bucket_factor: 1.2,
1939 zero_threshold: None,
1940 max_buckets: 4,
1941 max_zero_threshold: 1.2,
1942 sample_count: 8,
1943 sample_sum: 11.5,
1944 schema: 2,
1945 expected_zero_threshold: 1.0,
1946 zero_count: 2,
1947 negative_spans: &[],
1948 negative_deltas: &[],
1949 positive_spans: &[(1, 7)],
1950 positive_deltas: &[1, 1, -2, 2, -2, 0, 1],
1951 },
1952 NativeHistogramParityCase {
1953 name: "buckets limited by widening the zero bucket twice",
1954 observations: &[0.0, 1.0, 1.1, 1.2, 1.4, 1.8, 2.0, 3.0, 4.0],
1955 bucket_factor: 1.2,
1956 zero_threshold: None,
1957 max_buckets: 4,
1958 max_zero_threshold: 1.2,
1959 sample_count: 9,
1960 sample_sum: 15.5,
1961 schema: 2,
1962 expected_zero_threshold: 1.189207115002721,
1963 zero_count: 3,
1964 negative_spans: &[],
1965 negative_deltas: &[],
1966 positive_spans: &[(2, 7)],
1967 positive_deltas: &[2, -2, 2, -2, 0, 1, 0],
1968 },
1969 NativeHistogramParityCase {
1970 name: "limited buckets but nothing triggered, negative observations",
1971 observations: &[0.0, -1.0, -1.2, -1.4, -1.8, -2.0],
1972 bucket_factor: 1.2,
1973 zero_threshold: None,
1974 max_buckets: 4,
1975 max_zero_threshold: 0.0,
1976 sample_count: 6,
1977 sample_sum: -7.4,
1978 schema: 2,
1979 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1980 zero_count: 1,
1981 negative_spans: &[(0, 5)],
1982 negative_deltas: &[1, -1, 2, -2, 2],
1983 positive_spans: &[],
1984 positive_deltas: &[],
1985 },
1986 NativeHistogramParityCase {
1987 name: "buckets limited by halving resolution, negative observations",
1988 observations: &[0.0, -1.0, -1.1, -1.2, -1.4, -1.8, -2.0, -3.0],
1989 bucket_factor: 1.2,
1990 zero_threshold: None,
1991 max_buckets: 4,
1992 max_zero_threshold: 0.0,
1993 sample_count: 8,
1994 sample_sum: -11.5,
1995 schema: 1,
1996 expected_zero_threshold: DEFAULT_ZERO_THRESHOLD,
1997 zero_count: 1,
1998 negative_spans: &[(0, 5)],
1999 negative_deltas: &[1, 2, -1, -2, 1],
2000 positive_spans: &[],
2001 positive_deltas: &[],
2002 },
2003 NativeHistogramParityCase {
2004 name: "buckets limited by widening the zero bucket, negative observations",
2005 observations: &[0.0, -1.0, -1.1, -1.2, -1.4, -1.8, -2.0, -3.0],
2006 bucket_factor: 1.2,
2007 zero_threshold: None,
2008 max_buckets: 4,
2009 max_zero_threshold: 1.2,
2010 sample_count: 8,
2011 sample_sum: -11.5,
2012 schema: 2,
2013 expected_zero_threshold: 1.0,
2014 zero_count: 2,
2015 negative_spans: &[(1, 7)],
2016 negative_deltas: &[1, 1, -2, 2, -2, 0, 1],
2017 positive_spans: &[],
2018 positive_deltas: &[],
2019 },
2020 NativeHistogramParityCase {
2021 name: "buckets limited by widening the zero bucket twice, negative observations",
2022 observations: &[0.0, -1.0, -1.1, -1.2, -1.4, -1.8, -2.0, -3.0, -4.0],
2023 bucket_factor: 1.2,
2024 zero_threshold: None,
2025 max_buckets: 4,
2026 max_zero_threshold: 1.2,
2027 sample_count: 9,
2028 sample_sum: -15.5,
2029 schema: 2,
2030 expected_zero_threshold: 1.189207115002721,
2031 zero_count: 3,
2032 negative_spans: &[(2, 7)],
2033 negative_deltas: &[2, -2, 2, -2, 0, 1, 0],
2034 positive_spans: &[],
2035 positive_deltas: &[],
2036 },
2037 ];
2038
2039 for case in cases {
2040 let mut config = NativeHistogramConfig::new(case.bucket_factor)
2041 .max_buckets(case.max_buckets)
2042 .max_zero_threshold(case.max_zero_threshold);
2043 if let Some(zero_threshold) = case.zero_threshold {
2044 config = config.zero_threshold(zero_threshold);
2045 }
2046 let h = Histogram::new_native(config);
2047 for observation in case.observations {
2048 h.observe(*observation);
2049 }
2050
2051 let snapshot = h.snapshot().unwrap();
2052 let native = snapshot.native.unwrap();
2053
2054 assert_eq!(case.sample_count, snapshot.count, "{}", case.name);
2055 if case.sample_sum.is_nan() {
2056 assert!(snapshot.sum.is_nan(), "{}", case.name);
2057 } else {
2058 assert_eq!(case.sample_sum, snapshot.sum, "{}", case.name);
2059 }
2060 assert_eq!(case.schema, native.schema, "{}", case.name);
2061 assert_eq!(
2062 case.expected_zero_threshold, native.zero_threshold,
2063 "{}",
2064 case.name
2065 );
2066 assert_eq!(case.zero_count, native.zero_count, "{}", case.name);
2067 assert_eq!(case.negative_spans, native.negative.spans, "{}", case.name);
2068 assert_eq!(
2069 case.negative_deltas, native.negative.deltas,
2070 "{}",
2071 case.name
2072 );
2073 assert_eq!(case.positive_spans, native.positive.spans, "{}", case.name);
2074 assert_eq!(
2075 case.positive_deltas, native.positive.deltas,
2076 "{}",
2077 case.name
2078 );
2079 }
2080 }
2081
2082 #[test]
2083 fn native_histogram_maps_positive_infinity_into_sparse_bucket() {
2084 let h = Histogram::new_native(NativeHistogramConfig::with_schema(4));
2085 h.observe(f64::INFINITY);
2086 let inner = h.inner.lock();
2087 let native = inner.native.as_ref().unwrap();
2088 assert_eq!(
2089 Some(1),
2090 native
2091 .positive
2092 .iter()
2093 .find(|(index, _)| *index == 16385)
2094 .map(|(_, count)| *count)
2095 );
2096 assert!(!native.positive.iter().any(|(index, _)| *index == i32::MAX));
2097 }
2098
2099 #[test]
2100 fn native_histogram_widens_zero_bucket_at_min_schema_when_limited() {
2101 let mut inner = NativeHistogramState {
2102 initial_zero_threshold: 0.0,
2103 initial_schema: SCHEMA_MIN,
2104 zero_threshold: 0.0,
2105 zero_count: 0,
2106 schema: SCHEMA_MIN,
2107 max_buckets: 1,
2108 min_reset_duration: None,
2109 max_zero_threshold: 1.0,
2110 created: SystemTime::now(),
2111 scheduled_reset: None,
2112 positive: vec![(-10, 2), (0, 1)],
2113 negative: Vec::new(),
2114 };
2115
2116 assert!(widen_zero_bucket(&mut inner));
2117 assert!(inner.zero_threshold > 0.0);
2118 assert!(inner.zero_count >= 2);
2119 }
2120
2121 #[test]
2122 fn native_histogram_encodes_spans_and_deltas() {
2123 let buckets = vec![(-10, 1), (-7, 7), (-5, 7), (2, 8)];
2124
2125 let encoding = encode_spans_and_deltas(&buckets).unwrap();
2126
2127 assert_eq!(encoding.spans, vec![(-10, 6), (6, 1)]);
2128 assert_eq!(encoding.deltas, vec![1, -1, 0, 7, -7, 7, 1]);
2129 }
2130
2131 #[test]
2132 fn native_histogram_downsamples_like_go_client() {
2133 let buckets = vec![(-2, 1), (-1, 2), (0, 4), (1, 8)];
2134
2135 let downsampled = downsample_buckets(&buckets);
2136
2137 assert_eq!(downsampled, vec![(-1, 1), (0, 6), (1, 8)]);
2138 }
2139
2140 #[test]
2141 fn native_histogram_positive_upper_bound_uses_bucket_index() {
2142 assert_eq!(1.0, positive_upper_bound(0, 0));
2143 assert_eq!(2.0, positive_upper_bound(0, 1));
2144 assert!((positive_upper_bound(3, 8) - 2.0).abs() <= 4.0 * f64::EPSILON);
2145 assert_eq!(f64::MAX, positive_upper_bound(0, 1024));
2146 assert_eq!(f64::INFINITY, positive_upper_bound(0, 1025));
2147 assert_eq!(f64::MAX, positive_upper_bound(8, 262144));
2148 assert_eq!(f64::INFINITY, positive_upper_bound(8, 262145));
2149 }
2150
2151 #[test]
2152 fn native_histogram_bucket_index_matches_standard_boundaries() {
2153 assert_eq!(0, bucket_index(0, 1.0, false));
2154 assert_eq!(
2155 1,
2156 bucket_index(0, f64::from_bits(1.0f64.to_bits() + 1), false)
2157 );
2158 assert_eq!(8, bucket_index(3, 2.0, false));
2159 assert_eq!(
2160 -1074,
2161 bucket_index(0, f64::MIN_POSITIVE / 2f64.powi(52), false)
2162 );
2163 assert_eq!(1024, bucket_index(0, f64::MAX, false));
2164 assert_eq!(1025, bucket_index(0, f64::INFINITY, true));
2165 assert_eq!(262144, bucket_index(8, f64::MAX, false));
2166 assert_eq!(262145, bucket_index(8, f64::INFINITY, true));
2167 }
2168
2169 #[test]
2170 fn native_histogram_bounds_match_standard_formula() {
2171 for (schema, bounds) in NATIVE_HISTOGRAM_BOUNDS.iter().enumerate() {
2172 assert_eq!(1 << schema, bounds.len());
2173
2174 for (i, bound) in bounds.iter().enumerate() {
2175 let expected = 2f64.powf(i as f64 / (1 << schema) as f64) / 2.0;
2176 assert!(
2177 (*bound - expected).abs() <= 4.0 * f64::EPSILON,
2178 "schema {schema} index {i}: expected {expected}, got {bound}"
2179 );
2180 }
2181 }
2182 }
2183
2184 #[test]
2185 fn native_histogram_no_op_span_marks_zero_width_nan_only_histogram() {
2186 let h = Histogram::new_native(
2187 NativeHistogramConfig::with_schema(0)
2188 .zero_threshold(NATIVE_HISTOGRAM_ZERO_THRESHOLD_ZERO),
2189 );
2190 h.observe(f64::NAN);
2191
2192 let snapshot = h.snapshot().unwrap();
2193 let native = snapshot.native.unwrap();
2194
2195 assert_eq!(1, snapshot.count);
2196 assert_eq!(0.0, native.zero_threshold);
2197 assert_eq!(0, native.zero_count);
2198 assert_eq!(vec![(0, 0)], native.positive.spans);
2199 assert!(native.positive.deltas.is_empty());
2200 assert!(native.negative.spans.is_empty());
2201 assert!(native.negative.deltas.is_empty());
2202 }
2203
2204 #[test]
2205 fn native_histogram_fails_for_counts_not_fitting_delta_wire_type() {
2206 let buckets = vec![(0, i64::MAX as u64 + 1)];
2207
2208 assert!(encode_spans_and_deltas(&buckets).is_err());
2209 }
2210
2211 #[test]
2212 fn classic_and_native_histogram_updates_both_representations() {
2213 let h =
2214 Histogram::new_classic_and_native([1.0, 2.0], NativeHistogramConfig::with_schema(0));
2215 h.observe(1.0);
2216 h.observe(4.0);
2217
2218 let inner = h.inner.lock();
2219 let native = inner.native.as_ref().unwrap();
2220 assert_eq!(2, inner.count);
2221 assert_eq!(5.0, inner.sum);
2222 assert_eq!(1, inner.buckets[0].1);
2223 assert_eq!(1, inner.buckets[2].1);
2224 assert_eq!(
2225 2,
2226 native.positive.iter().map(|(_, count)| *count).sum::<u64>()
2227 );
2228 }
2229}