1use std::collections::btree_map::Entry;
7use std::sync::atomic::{AtomicI32, AtomicU64, Ordering};
8
9fn frexp(f: f64) -> (f64, i32) {
13 if f == 0.0 || !f.is_finite() {
14 return (f, 0);
15 }
16
17 let bits = f.to_bits();
18 let sign_bit = bits & (1u64 << 63);
19 let mut exp_bits = (bits >> 52) & 0x7FF;
20 let mut mantissa_bits = bits & 0x000F_FFFF_FFFF_FFFF;
21
22 if exp_bits == 0 {
23 let shift = mantissa_bits.leading_zeros() - 12; mantissa_bits <<= shift + 1;
26 mantissa_bits &= 0x000F_FFFF_FFFF_FFFF; #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
28 {
29 exp_bits = (1022_i32 - shift as i32) as u64; }
31 let mantissa_f64_bits = sign_bit | (1022u64 << 52) | mantissa_bits;
32 let mantissa = f64::from_bits(mantissa_f64_bits);
33 #[allow(clippy::cast_possible_truncation)]
34 let exponent = exp_bits as i32 - 1022;
35 (mantissa, exponent)
36 } else {
37 #[allow(clippy::cast_possible_truncation)]
39 let exponent = exp_bits as i32 - 1023; let mantissa_f64_bits = sign_bit | (1022u64 << 52) | mantissa_bits; let mantissa = f64::from_bits(mantissa_f64_bits);
42 (mantissa, exponent + 1)
43 }
44}
45
46const MIN_SCHEMA: i32 = -4;
48const MAX_SCHEMA: i32 = 8;
49
50#[allow(clippy::unreadable_literal)]
52const NATIVE_HISTOGRAM_BOUNDS: &[&[f64]] = &[
53 &[0.5],
55 &[0.5, 0.7071067811865475],
57 &[0.5, 0.5946035575013605, 0.7071067811865475, 0.8408964152537144],
59 &[
61 0.5,
62 0.5452538663326288,
63 0.5946035575013605,
64 0.6484197773255048,
65 0.7071067811865475,
66 0.7711054127039704,
67 0.8408964152537144,
68 0.9170040432046711,
69 ],
70 &[
72 0.5,
73 0.5221368912137069,
74 0.5452538663326288,
75 0.5693943173783458,
76 0.5946035575013605,
77 0.620928906036742,
78 0.6484197773255048,
79 0.6771277734684463,
80 0.7071067811865475,
81 0.7384130729697496,
82 0.7711054127039704,
83 0.805245165974627,
84 0.8408964152537144,
85 0.8781260801866495,
86 0.9170040432046711,
87 0.9576032806985735,
88 ],
89 &[
91 0.5,
92 0.5109485743270583,
93 0.5221368912137069,
94 0.5335702003384117,
95 0.5452538663326288,
96 0.5571933712979462,
97 0.5693943173783458,
98 0.5818624293887887,
99 0.5946035575013605,
100 0.6076236799902344,
101 0.620928906036742,
102 0.6345254785958666,
103 0.6484197773255048,
104 0.6626183215798706,
105 0.6771277734684463,
106 0.6919549409819159,
107 0.7071067811865475,
108 0.7225904034885232,
109 0.7384130729697496,
110 0.7545822137967112,
111 0.7711054127039704,
112 0.7879904225539431,
113 0.805245165974627,
114 0.8228777390769823,
115 0.8408964152537144,
116 0.8593096490612387,
117 0.8781260801866495,
118 0.8973545375015533,
119 0.9170040432046711,
120 0.9370838170551498,
121 0.9576032806985735,
122 0.9785720620876999,
123 ],
124 &[
126 0.5,
127 0.5054446430258502,
128 0.5109485743270583,
129 0.5165124395106142,
130 0.5221368912137069,
131 0.5278225891802786,
132 0.5335702003384117,
133 0.5393803988785598,
134 0.5452538663326288,
135 0.5511912916539204,
136 0.5571933712979462,
137 0.5632608093041209,
138 0.5693943173783458,
139 0.5755946149764913,
140 0.5818624293887887,
141 0.5881984958251406,
142 0.5946035575013605,
143 0.6010783657263515,
144 0.6076236799902344,
145 0.6142402680534349,
146 0.620928906036742,
147 0.6276903785123455,
148 0.6345254785958666,
149 0.6414350080393891,
150 0.6484197773255048,
151 0.6554806057623822,
152 0.6626183215798706,
153 0.6698337620266515,
154 0.6771277734684463,
155 0.6845012114872953,
156 0.6919549409819159,
157 0.6994898362691555,
158 0.7071067811865475,
159 0.7148066691959849,
160 0.7225904034885232,
161 0.7304588970903234,
162 0.7384130729697496,
163 0.7464538641456323,
164 0.7545822137967112,
165 0.762799075372269,
166 0.7711054127039704,
167 0.7795022001189185,
168 0.7879904225539431,
169 0.7965710756711334,
170 0.805245165974627,
171 0.8140137109286738,
172 0.8228777390769823,
173 0.8318382901633681,
174 0.8408964152537144,
175 0.8500531768592616,
176 0.8593096490612387,
177 0.8686669176368529,
178 0.8781260801866495,
179 0.8876882462632604,
180 0.8973545375015533,
181 0.9071260877501991,
182 0.9170040432046711,
183 0.9269895625416926,
184 0.9370838170551498,
185 0.9472879907934827,
186 0.9576032806985735,
187 0.9680308967461471,
188 0.9785720620876999,
189 0.9892280131939752,
190 ],
191 &[
193 0.5,
194 0.5027149505564014,
195 0.5054446430258502,
196 0.5081891574554764,
197 0.5109485743270583,
198 0.5137229745593818,
199 0.5165124395106142,
200 0.5193170509806894,
201 0.5221368912137069,
202 0.5249720429003435,
203 0.5278225891802786,
204 0.5306886136446309,
205 0.5335702003384117,
206 0.5364674337629877,
207 0.5393803988785598,
208 0.5423091811066545,
209 0.5452538663326288,
210 0.5482145409081883,
211 0.5511912916539204,
212 0.5541842058618393,
213 0.5571933712979462,
214 0.5602188762048033,
215 0.5632608093041209,
216 0.5663192597993595,
217 0.5693943173783458,
218 0.572486072215902,
219 0.5755946149764913,
220 0.5787200368168754,
221 0.5818624293887887,
222 0.585021884841625,
223 0.5881984958251406,
224 0.5913923554921704,
225 0.5946035575013605,
226 0.5978321960199137,
227 0.6010783657263515,
228 0.6043421618132907,
229 0.6076236799902344,
230 0.6109230164863786,
231 0.6142402680534349,
232 0.6175755319684665,
233 0.620928906036742,
234 0.6243004885946023,
235 0.6276903785123455,
236 0.6310986751971253,
237 0.6345254785958666,
238 0.637970889198196,
239 0.6414350080393891,
240 0.6449179367033329,
241 0.6484197773255048,
242 0.6519406325959679,
243 0.6554806057623822,
244 0.659039800633032,
245 0.6626183215798706,
246 0.6662162735415805,
247 0.6698337620266515,
248 0.6734708931164728,
249 0.6771277734684463,
250 0.6808045103191123,
251 0.6845012114872953,
252 0.688217985377265,
253 0.6919549409819159,
254 0.6957121878859629,
255 0.6994898362691555,
256 0.7032879969095076,
257 0.7071067811865475,
258 0.7109463010845827,
259 0.7148066691959849,
260 0.718687998724491,
261 0.7225904034885232,
262 0.7265139979245261,
263 0.7304588970903234,
264 0.7344252166684908,
265 0.7384130729697496,
266 0.7424225829363761,
267 0.7464538641456323,
268 0.7505070348132126,
269 0.7545822137967112,
270 0.7586795205991071,
271 0.762799075372269,
272 0.7669409989204777,
273 0.7711054127039704,
274 0.7752924388424999,
275 0.7795022001189185,
276 0.7837348199827764,
277 0.7879904225539431,
278 0.7922691326262467,
279 0.7965710756711334,
280 0.8008963778413465,
281 0.805245165974627,
282 0.8096175675974316,
283 0.8140137109286738,
284 0.8184337248834821,
285 0.8228777390769823,
286 0.8273458838280969,
287 0.8318382901633681,
288 0.8363550898207981,
289 0.8408964152537144,
290 0.8454623996346523,
291 0.8500531768592616,
292 0.8546688815502312,
293 0.8593096490612387,
294 0.8639756154809185,
295 0.8686669176368529,
296 0.8733836930995842,
297 0.8781260801866495,
298 0.8828942179666361,
299 0.8876882462632604,
300 0.8925083056594671,
301 0.8973545375015533,
302 0.9022270839033115,
303 0.9071260877501991,
304 0.9120516927035263,
305 0.9170040432046711,
306 0.9219832844793128,
307 0.9269895625416926,
308 0.9320230241988943,
309 0.9370838170551498,
310 0.9421720895161669,
311 0.9472879907934827,
312 0.9524316709088368,
313 0.9576032806985735,
314 0.9628029718180622,
315 0.9680308967461471,
316 0.9732872087896164,
317 0.9785720620876999,
318 0.9838856116165875,
319 0.9892280131939752,
320 0.9945994234836328,
321 ],
322 &[
324 0.5,
325 0.5013556375251013,
326 0.5027149505564014,
327 0.5040779490592088,
328 0.5054446430258502,
329 0.5068150424757447,
330 0.5081891574554764,
331 0.509566998038869,
332 0.5109485743270583,
333 0.5123338964485679,
334 0.5137229745593818,
335 0.5151158188430205,
336 0.5165124395106142,
337 0.5179128468009786,
338 0.5193170509806894,
339 0.520725062344158,
340 0.5221368912137069,
341 0.5235525479396449,
342 0.5249720429003435,
343 0.526395386502313,
344 0.5278225891802786,
345 0.5292536613972564,
346 0.5306886136446309,
347 0.5321274564422321,
348 0.5335702003384117,
349 0.5350168559101208,
350 0.5364674337629877,
351 0.5379219445313954,
352 0.5393803988785598,
353 0.5408428074966075,
354 0.5423091811066545,
355 0.5437795304588847,
356 0.5452538663326288,
357 0.5467321995364429,
358 0.5482145409081883,
359 0.549700901315111,
360 0.5511912916539204,
361 0.5526857228508706,
362 0.5541842058618393,
363 0.5556867516724088,
364 0.5571933712979462,
365 0.5587040757836845,
366 0.5602188762048033,
367 0.5617377836665098,
368 0.5632608093041209,
369 0.564787964283144,
370 0.5663192597993595,
371 0.5678547070789026,
372 0.5693943173783458,
373 0.5709381019847808,
374 0.572486072215902,
375 0.5740382394200894,
376 0.5755946149764913,
377 0.5771552102951081,
378 0.5787200368168754,
379 0.5802891060137493,
380 0.5818624293887887,
381 0.5834400184762408,
382 0.585021884841625,
383 0.5866080400818185,
384 0.5881984958251406,
385 0.5897932637314379,
386 0.5913923554921704,
387 0.5929957828304968,
388 0.5946035575013605,
389 0.5962156912915756,
390 0.5978321960199137,
391 0.5994530835371903,
392 0.6010783657263515,
393 0.6027080545025619,
394 0.6043421618132907,
395 0.6059806996384005,
396 0.6076236799902344,
397 0.6092711149137041,
398 0.6109230164863786,
399 0.6125793968185725,
400 0.6142402680534349,
401 0.6159056423670379,
402 0.6175755319684665,
403 0.6192499490999082,
404 0.620928906036742,
405 0.622612415087629,
406 0.6243004885946023,
407 0.6259931389331581,
408 0.6276903785123455,
409 0.6293922197748583,
410 0.6310986751971253,
411 0.6328097572894031,
412 0.6345254785958666,
413 0.6362458516947014,
414 0.637970889198196,
415 0.6397006037528346,
416 0.6414350080393891,
417 0.6431741147730128,
418 0.6449179367033329,
419 0.6466664866145447,
420 0.6484197773255048,
421 0.6501778216898253,
422 0.6519406325959679,
423 0.6537082229673385,
424 0.6554806057623822,
425 0.6572577939746774,
426 0.659039800633032,
427 0.6608266388015788,
428 0.6626183215798706,
429 0.6644148621029772,
430 0.6662162735415805,
431 0.6680225691020727,
432 0.6698337620266515,
433 0.6716498655934177,
434 0.6734708931164728,
435 0.6752968579460171,
436 0.6771277734684463,
437 0.6789636531064505,
438 0.6808045103191123,
439 0.6826503586020058,
440 0.6845012114872953,
441 0.6863570825438342,
442 0.688217985377265,
443 0.690083933630119,
444 0.6919549409819159,
445 0.6938310211492645,
446 0.6957121878859629,
447 0.6975984549830999,
448 0.6994898362691555,
449 0.7013863456101023,
450 0.7032879969095076,
451 0.7051948041086352,
452 0.7071067811865475,
453 0.7090239421602076,
454 0.7109463010845827,
455 0.7128738720527471,
456 0.7148066691959849,
457 0.7167447066838943,
458 0.718687998724491,
459 0.7206365595643126,
460 0.7225904034885232,
461 0.7245495448210174,
462 0.7265139979245261,
463 0.7284837772007218,
464 0.7304588970903234,
465 0.7324393720732029,
466 0.7344252166684908,
467 0.7364164454346837,
468 0.7384130729697496,
469 0.7404151139112358,
470 0.7424225829363761,
471 0.7444354947621984,
472 0.7464538641456323,
473 0.7484777058836176,
474 0.7505070348132126,
475 0.7525418658117031,
476 0.7545822137967112,
477 0.7566280937263048,
478 0.7586795205991071,
479 0.7607365094544071,
480 0.762799075372269,
481 0.7648672334736434,
482 0.7669409989204777,
483 0.7690203869158282,
484 0.7711054127039704,
485 0.7731960915705107,
486 0.7752924388424999,
487 0.7773944698885442,
488 0.7795022001189185,
489 0.7816156449856788,
490 0.7837348199827764,
491 0.7858597406461707,
492 0.7879904225539431,
493 0.7901268813264122,
494 0.7922691326262467,
495 0.7944171921585818,
496 0.7965710756711334,
497 0.7987307989543135,
498 0.8008963778413465,
499 0.8030678282083853,
500 0.805245165974627,
501 0.8074284071024302,
502 0.8096175675974316,
503 0.8118126635086642,
504 0.8140137109286738,
505 0.8162207259936375,
506 0.8184337248834821,
507 0.820652723822003,
508 0.8228777390769823,
509 0.8251087869603088,
510 0.8273458838280969,
511 0.8295890460808079,
512 0.8318382901633681,
513 0.8340936325652911,
514 0.8363550898207981,
515 0.8386226785089391,
516 0.8408964152537144,
517 0.8431763167241966,
518 0.8454623996346523,
519 0.8477546807446661,
520 0.8500531768592616,
521 0.8523579048290255,
522 0.8546688815502312,
523 0.8569861239649629,
524 0.8593096490612387,
525 0.8616394738731368,
526 0.8639756154809185,
527 0.8663180910111553,
528 0.8686669176368529,
529 0.871022112577578,
530 0.8733836930995842,
531 0.8757516765159389,
532 0.8781260801866495,
533 0.8805069215187917,
534 0.8828942179666361,
535 0.8852879870317771,
536 0.8876882462632604,
537 0.890095013257712,
538 0.8925083056594671,
539 0.8949281411607002,
540 0.8973545375015533,
541 0.8997875124702672,
542 0.9022270839033115,
543 0.9046732696855155,
544 0.9071260877501991,
545 0.909585556079304,
546 0.9120516927035263,
547 0.9145245157024483,
548 0.9170040432046711,
549 0.9194902933879467,
550 0.9219832844793128,
551 0.9244830347552253,
552 0.9269895625416926,
553 0.92950288621441,
554 0.9320230241988943,
555 0.9345499949706191,
556 0.9370838170551498,
557 0.93962450902828,
558 0.9421720895161669,
559 0.9447265771954693,
560 0.9472879907934827,
561 0.9498563490882775,
562 0.9524316709088368,
563 0.9550139751351947,
564 0.9576032806985735,
565 0.9601996065815236,
566 0.9628029718180622,
567 0.9654133954938133,
568 0.9680308967461471,
569 0.9706554947643201,
570 0.9732872087896164,
571 0.9759260581154889,
572 0.9785720620876999,
573 0.9812252401044634,
574 0.9838856116165875,
575 0.9865531961276168,
576 0.9892280131939752,
577 0.9919100824251095,
578 0.9945994234836328,
579 0.9972960560854698,
580 ],
581];
582
583pub(crate) fn calculate_schema_from_bucket_factor(bucket_factor: f64) -> i32 {
593 assert!(bucket_factor > 1.0, "bucket_factor must be greater than 1.0");
600
601 let log_bucket_factor = bucket_factor.log2();
602 assert!(log_bucket_factor > 0.0, "log of bucket_factor must be positive");
603
604 #[allow(clippy::cast_possible_truncation)]
605 let schema = -(log_bucket_factor.log2()).round() as i32;
606
607 schema.clamp(MIN_SCHEMA, MAX_SCHEMA)
609}
610
611#[derive(Debug, Clone)]
613pub struct NativeHistogramConfig {
614 bucket_factor: f64,
618 max_buckets: u32,
620 zero_threshold: f64,
623}
624
625impl NativeHistogramConfig {
626 pub fn new(
640 bucket_factor: f64,
641 max_buckets: u32,
642 zero_threshold: f64,
643 ) -> Result<Self, &'static str> {
644 if bucket_factor <= 1.0 {
645 return Err("bucket_factor must be greater than 1.0");
646 }
647 if max_buckets == 0 {
648 return Err("max_buckets must be greater than 0");
649 }
650 if zero_threshold < 0.0 {
651 return Err("zero_threshold must be non-negative");
652 }
653
654 Ok(Self { bucket_factor, max_buckets, zero_threshold })
655 }
656
657 pub fn bucket_factor(&self) -> f64 {
659 self.bucket_factor
660 }
661
662 pub fn max_buckets(&self) -> u32 {
664 self.max_buckets
665 }
666
667 pub fn zero_threshold(&self) -> f64 {
669 self.zero_threshold
670 }
671}
672
673#[derive(Debug)]
678pub struct NativeHistogram {
679 config: NativeHistogramConfig,
680 count: AtomicU64,
682 sum: AtomicU64,
684 zero_count: AtomicU64,
686 positive_buckets: std::sync::RwLock<std::collections::BTreeMap<i32, u64>>,
688 negative_buckets: std::sync::RwLock<std::collections::BTreeMap<i32, u64>>,
690 schema: AtomicI32,
692 bucket_count: AtomicU64,
694}
695
696impl NativeHistogram {
697 pub(crate) fn new(config: NativeHistogramConfig) -> Self {
699 let schema = calculate_schema_from_bucket_factor(config.bucket_factor());
700
701 Self {
702 schema: AtomicI32::new(schema),
703 config,
704 count: AtomicU64::new(0),
705 sum: AtomicU64::new((0.0f64).to_bits()),
706 zero_count: AtomicU64::new(0),
707 positive_buckets: std::sync::RwLock::new(std::collections::BTreeMap::new()),
708 negative_buckets: std::sync::RwLock::new(std::collections::BTreeMap::new()),
709 bucket_count: AtomicU64::new(0),
710 }
711 }
712
713 pub(crate) fn observe(&self, value: f64) {
715 self.count.fetch_add(1, Ordering::Relaxed);
716
717 if value.is_nan() {
719 return;
720 }
721
722 loop {
724 let current_sum_bits = self.sum.load(Ordering::Relaxed);
725 let current_sum = f64::from_bits(current_sum_bits);
726 let new_sum = current_sum + value;
727
728 if self
729 .sum
730 .compare_exchange_weak(
731 current_sum_bits,
732 new_sum.to_bits(),
733 Ordering::Relaxed,
734 Ordering::Relaxed,
735 )
736 .is_ok()
737 {
738 break;
739 }
740 }
741
742 let mut v = value;
743 let mut key: i32;
744 let schema = self.schema.load(Ordering::Relaxed);
745 let zero_threshold = self.config.zero_threshold();
746 let mut is_inf = false;
747
748 if v.is_infinite() {
749 if v.is_sign_positive() {
750 v = f64::MAX;
751 } else {
752 v = f64::MIN;
753 }
754 is_inf = true;
755 }
756
757 let (frac, exp) = frexp(v.abs());
759
760 if schema > 0 {
761 #[allow(clippy::cast_sign_loss)]
763 let bounds = &NATIVE_HISTOGRAM_BOUNDS[schema as usize];
764 let idx = bounds
766 .binary_search_by(|&bound| {
767 bound.partial_cmp(&frac).unwrap_or(std::cmp::Ordering::Equal)
768 })
769 .unwrap_or_else(|x| x);
770 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
771 let key_base = idx as i32;
772 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
773 let bounds_len = bounds.len() as i32;
774 key = key_base + (exp - 1) * bounds_len;
775 } else {
776 key = exp;
778 if (frac - 0.5).abs() < f64::EPSILON {
779 key -= 1;
780 }
781 if schema < 0 {
782 let offset = (1 << (-schema)) - 1;
783 key = (key + offset) >> (-schema);
784 }
785 }
786
787 if is_inf {
789 key += 1;
790 }
791
792 let mut added_new_bucket = false;
794
795 if value > zero_threshold {
797 let mut buckets = self.positive_buckets.write().unwrap();
798 match buckets.entry(key) {
800 Entry::Vacant(entry) => {
801 entry.insert(1);
802 self.bucket_count.fetch_add(1, Ordering::Relaxed);
803 added_new_bucket = true;
804 }
805 Entry::Occupied(mut entry) => {
806 *entry.get_mut() += 1;
807 }
808 }
809 } else if value < -zero_threshold {
810 let mut buckets = self.negative_buckets.write().unwrap();
811 match buckets.entry(key) {
813 Entry::Vacant(entry) => {
814 entry.insert(1);
815 self.bucket_count.fetch_add(1, Ordering::Relaxed);
816 added_new_bucket = true;
817 }
818 Entry::Occupied(mut entry) => {
819 *entry.get_mut() += 1;
820 }
821 }
822 } else {
823 self.zero_count.fetch_add(1, Ordering::Relaxed);
825 }
826
827 if added_new_bucket {
829 self.limit_buckets();
830 }
831 }
832
833 fn limit_buckets(&self) {
835 if self.config.max_buckets() == 0 {
836 return; }
838
839 let current_bucket_count = self.bucket_count();
840 if current_bucket_count <= u64::from(self.config.max_buckets()) {
841 return; }
843
844 self.reduce_bucket_resolution();
845 }
846
847 fn reduce_bucket_resolution(&self) {
851 let current_schema = self.schema.load(Ordering::Relaxed);
852
853 if current_schema <= MIN_SCHEMA {
855 return;
856 }
857
858 let new_schema = current_schema - 1;
860 self.schema.store(new_schema, Ordering::Relaxed);
861
862 {
864 let mut pos_buckets = self.positive_buckets.write().unwrap();
865 let old_buckets = std::mem::take(&mut *pos_buckets);
866 let old_count = old_buckets.len() as u64;
867
868 for (mut k, v) in old_buckets {
869 if k > 0 {
870 k += 1;
871 }
872 k /= 2;
873
874 *pos_buckets.entry(k).or_insert(0) += v;
875 }
876
877 let new_count = pos_buckets.len() as u64;
879 if new_count < old_count {
880 self.bucket_count.fetch_sub(old_count - new_count, Ordering::Relaxed);
881 }
882 }
883
884 {
886 let mut neg_buckets = self.negative_buckets.write().unwrap();
887 let old_buckets = std::mem::take(&mut *neg_buckets);
888 let old_count = old_buckets.len() as u64;
889
890 for (mut k, v) in old_buckets {
891 if k > 0 {
892 k += 1;
893 }
894 k /= 2;
895
896 *neg_buckets.entry(k).or_insert(0) += v;
897 }
898
899 let new_count = neg_buckets.len() as u64;
901 if new_count < old_count {
902 self.bucket_count.fetch_sub(old_count - new_count, Ordering::Relaxed);
903 }
904 }
905
906 }
909
910 #[cfg(any(feature = "protobuf", test))]
912 pub(crate) fn count(&self) -> u64 {
913 self.count.load(Ordering::Relaxed)
914 }
915
916 #[cfg(any(feature = "protobuf", test))]
918 pub(crate) fn sum(&self) -> f64 {
919 f64::from_bits(self.sum.load(Ordering::Relaxed))
920 }
921
922 #[cfg(any(feature = "protobuf", test))]
924 pub(crate) fn zero_count(&self) -> u64 {
925 self.zero_count.load(Ordering::Relaxed)
926 }
927
928 #[cfg(any(feature = "protobuf", test))]
930 pub(crate) fn positive_buckets(&self) -> std::collections::BTreeMap<i32, u64> {
931 self.positive_buckets.read().unwrap().clone()
932 }
933
934 #[cfg(any(feature = "protobuf", test))]
936 pub(crate) fn negative_buckets(&self) -> std::collections::BTreeMap<i32, u64> {
937 self.negative_buckets.read().unwrap().clone()
938 }
939
940 #[cfg(feature = "protobuf")]
942 pub(crate) fn config(&self) -> &NativeHistogramConfig {
943 &self.config
944 }
945
946 #[cfg(any(feature = "protobuf", test))]
948 pub(crate) fn schema(&self) -> i32 {
949 self.schema.load(Ordering::Relaxed)
950 }
951
952 fn bucket_count(&self) -> u64 {
954 self.bucket_count.load(Ordering::Relaxed)
955 }
956}
957
958impl Clone for NativeHistogram {
959 fn clone(&self) -> Self {
960 Self {
961 config: self.config.clone(),
962 count: AtomicU64::new(self.count.load(Ordering::Relaxed)),
963 sum: AtomicU64::new(self.sum.load(Ordering::Relaxed)),
964 zero_count: AtomicU64::new(self.zero_count.load(Ordering::Relaxed)),
965 positive_buckets: std::sync::RwLock::new(self.positive_buckets.read().unwrap().clone()),
966 negative_buckets: std::sync::RwLock::new(self.negative_buckets.read().unwrap().clone()),
967 schema: AtomicI32::new(self.schema.load(Ordering::Relaxed)),
968 bucket_count: AtomicU64::new(self.bucket_count()),
969 }
970 }
971}
972
973#[cfg(test)]
974mod tests {
975 use super::*;
976
977 #[test]
978 fn test_frexp_function() {
979 let (m, e) = frexp(1.0);
980 assert!((m - 0.5).abs() < f64::EPSILON);
981 assert_eq!(e, 1);
982
983 let (m, e) = frexp(2.0);
984 assert!((m - 0.5).abs() < f64::EPSILON);
985 assert_eq!(e, 2);
986
987 let (m, e) = frexp(0.5);
988 assert!((m - 0.5).abs() < f64::EPSILON);
989 assert_eq!(e, 0);
990
991 let (m, e) = frexp(4.0);
992 assert!((m - 0.5).abs() < f64::EPSILON);
993 assert_eq!(e, 3);
994
995 let (m, e) = frexp(0.0);
997 assert_eq!(m, 0.0);
998 assert_eq!(e, 0);
999
1000 let (m, e) = frexp(-2.0);
1002 assert!((m - (-0.5)).abs() < f64::EPSILON);
1003 assert_eq!(e, 2);
1004 }
1005
1006 #[test]
1007 fn test_observe_nan_values() {
1008 let config = NativeHistogramConfig::new(2.0, 160, 0.1).unwrap();
1009 let histogram = NativeHistogram::new(config);
1010
1011 histogram.observe(1.0);
1013 histogram.observe(2.0);
1014
1015 histogram.observe(f64::NAN);
1017
1018 assert_eq!(histogram.count(), 3);
1019 assert!((histogram.sum() - 3.0).abs() < f64::EPSILON); assert_eq!(histogram.zero_count(), 0);
1021 assert!(!histogram.positive_buckets().is_empty()); }
1023
1024 #[test]
1025 fn test_bucket_reduction() {
1026 let config = NativeHistogramConfig::new(2.0, 2, 0.1).unwrap(); let histogram = NativeHistogram::new(config);
1029
1030 let initial_schema = histogram.schema();
1031
1032 histogram.observe(1.0);
1034 histogram.observe(2.0);
1035 histogram.observe(4.0);
1036 histogram.observe(8.0);
1037
1038 let final_schema = histogram.schema();
1040 assert!(final_schema < initial_schema, "Schema should have been reduced");
1041
1042 assert_eq!(histogram.count(), 4);
1044 assert!((histogram.sum() - 15.0).abs() < f64::EPSILON);
1045
1046 let pos_buckets = histogram.positive_buckets();
1048 assert!(!pos_buckets.is_empty(), "Buckets should be preserved after reduction");
1049
1050 let total_bucket_count: u64 = pos_buckets.values().sum();
1052 assert_eq!(total_bucket_count, 4, "All observations should be preserved in buckets");
1053 }
1054
1055 #[test]
1056 fn test_new_native_histogram() {
1057 let config = NativeHistogramConfig::new(2.0, 160, 0.1).unwrap();
1058 let histogram = NativeHistogram::new(config);
1059 assert_eq!(histogram.count(), 0);
1060 assert!((histogram.sum() - 0.0).abs() < f64::EPSILON);
1061 assert_eq!(histogram.zero_count(), 0);
1062 assert_eq!(histogram.schema(), 0); }
1064
1065 #[test]
1066 fn test_observe_positive_values() {
1067 let config = NativeHistogramConfig::new(2.0, 160, 0.1).unwrap();
1068 let histogram = NativeHistogram::new(config);
1069 histogram.observe(1.0);
1070 histogram.observe(2.0);
1071 histogram.observe(4.0);
1072
1073 assert_eq!(histogram.count(), 3);
1074 assert!((histogram.sum() - 7.0).abs() < f64::EPSILON);
1075 assert_eq!(histogram.zero_count(), 0);
1076
1077 let pos_buckets = histogram.positive_buckets();
1078 assert!(!pos_buckets.is_empty());
1079 }
1080
1081 #[test]
1082 fn test_observe_negative_values() {
1083 let config = NativeHistogramConfig::new(2.0, 160, 0.1).unwrap();
1084 let histogram = NativeHistogram::new(config);
1085 histogram.observe(-1.0);
1086 histogram.observe(-2.0);
1087
1088 assert_eq!(histogram.count(), 2);
1089 assert!((histogram.sum() - (-3.0)).abs() < f64::EPSILON);
1090 assert_eq!(histogram.zero_count(), 0);
1091
1092 let neg_buckets = histogram.negative_buckets();
1093 assert!(!neg_buckets.is_empty());
1094 assert!(histogram.positive_buckets().is_empty());
1095 }
1096
1097 #[test]
1098 fn test_observe_zero_values() {
1099 let config = NativeHistogramConfig::new(2.0, 160, 0.1).unwrap();
1100 let histogram = NativeHistogram::new(config);
1101 histogram.observe(0.0);
1102 histogram.observe(0.05);
1103 histogram.observe(-0.05);
1104
1105 assert_eq!(histogram.count(), 3);
1106 assert_eq!(histogram.zero_count(), 3);
1107 assert!(histogram.positive_buckets().is_empty());
1108 assert!(histogram.negative_buckets().is_empty());
1109 }
1110
1111 #[test]
1112 fn test_schema_based_bucketing() {
1113 let config = NativeHistogramConfig::new(2.0, 160, 0.1).unwrap();
1114 let histogram = NativeHistogram::new(config);
1115
1116 histogram.observe(1.0);
1118 histogram.observe(2.0);
1119 histogram.observe(4.0);
1120
1121 assert_eq!(histogram.count(), 3);
1122 assert_eq!(histogram.zero_count(), 0);
1123
1124 let pos_buckets = histogram.positive_buckets();
1125 assert!(!pos_buckets.is_empty());
1127 }
1128
1129 #[test]
1130 fn test_invalid_config() {
1131 assert!(NativeHistogramConfig::new(0.5, 160, 1e-128).is_err());
1133
1134 assert!(NativeHistogramConfig::new(2.0, 0, 1e-128).is_err());
1136
1137 assert!(NativeHistogramConfig::new(2.0, 160, -1.0).is_err());
1139 }
1140}