1use std::collections::BTreeMap;
23use std::sync::Arc;
24
25use datafusion::arrow::array::{
26 Array, ArrayRef, Float64Array, Int32Array, ListArray, PrimitiveArray, StructArray,
27 TimestampMillisecondArray, UInt64Array,
28};
29use datafusion::arrow::buffer::NullBuffer;
30use datafusion::arrow::datatypes::{
31 ArrowPrimitiveType, DataType as ArrowDataType, Field, Float64Type, Int32Type, Int64Type,
32 TimestampMillisecondType, UInt32Type, UInt64Type,
33};
34use datafusion_common::{DataFusionError, Result as DfResult};
35use datatypes::data_type::{ConcreteDataType, DataType};
36use datatypes::types::{StructField, StructType};
37use once_cell::sync::Lazy;
38
39use crate::prelude::greptime_native_histogram;
40use crate::prometheus::format_prometheus_float;
41
42pub const NATIVE_HISTOGRAM_FIELD: &str = "greptime_native_histogram";
43pub const SCHEMA_FIELD: &str = "schema";
44pub const ZERO_THRESHOLD_FIELD: &str = "zero_threshold";
45pub const SUM_FIELD: &str = "sum";
46pub const RESET_HINT_FIELD: &str = "reset_hint";
47pub const START_TIMESTAMP_FIELD: &str = "start_timestamp";
48pub const CUSTOM_VALUES_FIELD: &str = "custom_values";
49pub const POSITIVE_SPAN_OFFSETS_FIELD: &str = "positive_span_offsets";
50pub const POSITIVE_SPAN_LENGTHS_FIELD: &str = "positive_span_lengths";
51pub const NEGATIVE_SPAN_OFFSETS_FIELD: &str = "negative_span_offsets";
52pub const NEGATIVE_SPAN_LENGTHS_FIELD: &str = "negative_span_lengths";
53pub const COUNT_U64_FIELD: &str = "count_u64";
54pub const ZERO_COUNT_U64_FIELD: &str = "zero_count_u64";
55pub const POSITIVE_BUCKETS_I64_FIELD: &str = "positive_buckets_i64";
56pub const NEGATIVE_BUCKETS_I64_FIELD: &str = "negative_buckets_i64";
57pub const COUNT_F64_FIELD: &str = "count_f64";
58pub const ZERO_COUNT_F64_FIELD: &str = "zero_count_f64";
59pub const POSITIVE_BUCKETS_F64_FIELD: &str = "positive_buckets_f64";
60pub const NEGATIVE_BUCKETS_F64_FIELD: &str = "negative_buckets_f64";
61
62pub const NATIVE_HISTOGRAM_FIELD_NAMES: &[&str] = &[
65 SCHEMA_FIELD,
66 ZERO_THRESHOLD_FIELD,
67 SUM_FIELD,
68 RESET_HINT_FIELD,
69 START_TIMESTAMP_FIELD,
70 CUSTOM_VALUES_FIELD,
71 POSITIVE_SPAN_OFFSETS_FIELD,
72 POSITIVE_SPAN_LENGTHS_FIELD,
73 NEGATIVE_SPAN_OFFSETS_FIELD,
74 NEGATIVE_SPAN_LENGTHS_FIELD,
75 COUNT_U64_FIELD,
76 ZERO_COUNT_U64_FIELD,
77 POSITIVE_BUCKETS_I64_FIELD,
78 NEGATIVE_BUCKETS_I64_FIELD,
79 COUNT_F64_FIELD,
80 ZERO_COUNT_F64_FIELD,
81 POSITIVE_BUCKETS_F64_FIELD,
82 NEGATIVE_BUCKETS_F64_FIELD,
83];
84
85static NATIVE_HISTOGRAM_VALUE_TYPE: Lazy<ConcreteDataType> = Lazy::new(|| {
86 let fields = NATIVE_HISTOGRAM_FIELD_NAMES
87 .iter()
88 .filter_map(|name| {
89 let data_type = native_histogram_field_type(name)?;
90 Some(StructField::new((*name).to_string(), data_type, true))
91 })
92 .collect();
93 ConcreteDataType::struct_datatype(StructType::new(Arc::new(fields)))
94});
95
96pub fn native_histogram_field_type(name: &str) -> Option<ConcreteDataType> {
98 match name {
99 SCHEMA_FIELD | RESET_HINT_FIELD => Some(ConcreteDataType::int32_datatype()),
100 ZERO_THRESHOLD_FIELD | SUM_FIELD | COUNT_F64_FIELD | ZERO_COUNT_F64_FIELD => {
101 Some(ConcreteDataType::float64_datatype())
102 }
103 START_TIMESTAMP_FIELD => Some(ConcreteDataType::timestamp_millisecond_datatype()),
104 CUSTOM_VALUES_FIELD | POSITIVE_BUCKETS_F64_FIELD | NEGATIVE_BUCKETS_F64_FIELD => Some(
105 ConcreteDataType::list_datatype(Arc::new(ConcreteDataType::float64_datatype())),
106 ),
107 POSITIVE_SPAN_OFFSETS_FIELD | NEGATIVE_SPAN_OFFSETS_FIELD => Some(
108 ConcreteDataType::list_datatype(Arc::new(ConcreteDataType::int32_datatype())),
109 ),
110 POSITIVE_SPAN_LENGTHS_FIELD | NEGATIVE_SPAN_LENGTHS_FIELD => Some(
111 ConcreteDataType::list_datatype(Arc::new(ConcreteDataType::uint32_datatype())),
112 ),
113 COUNT_U64_FIELD | ZERO_COUNT_U64_FIELD => Some(ConcreteDataType::uint64_datatype()),
114 POSITIVE_BUCKETS_I64_FIELD | NEGATIVE_BUCKETS_I64_FIELD => Some(
115 ConcreteDataType::list_datatype(Arc::new(ConcreteDataType::int64_datatype())),
116 ),
117 _ => None,
118 }
119}
120
121pub fn native_histogram_value_type() -> &'static ConcreteDataType {
123 &NATIVE_HISTOGRAM_VALUE_TYPE
124}
125
126pub fn is_native_histogram_value_type(data_type: &ConcreteDataType) -> bool {
128 data_type == native_histogram_value_type()
129}
130
131pub fn is_native_histogram_value_schema(name: &str, data_type: &ConcreteDataType) -> bool {
133 name == greptime_native_histogram() && is_native_histogram_value_type(data_type)
134}
135
136pub const CUSTOM_BUCKETS_SCHEMA: i32 = -53;
138const MIN_EXPONENTIAL_SCHEMA: i32 = -4;
139const MAX_EXPONENTIAL_SCHEMA: i32 = 8;
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum CounterResetHint {
147 Unknown,
149 CounterReset,
151 NotCounterReset,
153 Gauge,
155 Unrecognized(i32),
157}
158
159impl From<i32> for CounterResetHint {
160 fn from(value: i32) -> Self {
161 match value {
162 0 => Self::Unknown,
163 1 => Self::CounterReset,
164 2 => Self::NotCounterReset,
165 3 => Self::Gauge,
166 value => Self::Unrecognized(value),
167 }
168 }
169}
170
171impl From<CounterResetHint> for i32 {
172 fn from(value: CounterResetHint) -> Self {
173 match value {
174 CounterResetHint::Unknown => 0,
175 CounterResetHint::CounterReset => 1,
176 CounterResetHint::NotCounterReset => 2,
177 CounterResetHint::Gauge => 3,
178 CounterResetHint::Unrecognized(value) => value,
179 }
180 }
181}
182
183pub const UNKNOWN_COUNTER_RESET_HINT: CounterResetHint = CounterResetHint::Unknown;
185pub const COUNTER_RESET_HINT: CounterResetHint = CounterResetHint::CounterReset;
187pub const NOT_COUNTER_RESET_HINT: CounterResetHint = CounterResetHint::NotCounterReset;
189pub const GAUGE_RESET_HINT: CounterResetHint = CounterResetHint::Gauge;
191
192#[derive(Clone, Debug, PartialEq)]
194pub struct Span {
195 pub offset: i32,
197 pub length: u32,
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203enum BoundaryRule {
204 OpenLeft = 0,
205 OpenRight = 1,
206 ClosedBoth = 3,
207}
208
209#[derive(Clone, Debug, PartialEq)]
211struct Bucket {
212 lower: f64,
213 upper: f64,
214 count: f64,
215 boundary_rule: BoundaryRule,
216}
217
218#[derive(Clone, Debug, PartialEq)]
223pub struct NativeHistogram {
224 pub schema: i32,
226 pub zero_threshold: f64,
228 pub sum: f64,
230 pub reset_hint: CounterResetHint,
232 pub start_timestamp: Option<i64>,
234 pub custom_values: Vec<f64>,
236 pub positive_spans: Vec<Span>,
238 pub negative_spans: Vec<Span>,
240 pub count: f64,
242 pub zero_count: f64,
244 pub positive_buckets: Vec<f64>,
246 pub negative_buckets: Vec<f64>,
248}
249
250impl NativeHistogram {
251 fn uses_custom_buckets(&self) -> bool {
252 self.schema == CUSTOM_BUCKETS_SCHEMA
253 }
254
255 fn compatible_with(&self, other: &Self) -> bool {
256 self.schema == other.schema
257 && self.zero_threshold == other.zero_threshold
258 && self.custom_values == other.custom_values
259 }
260
261 pub fn zero_like(&self) -> Self {
263 let mut result = self.clone();
264 result.count = 0.0;
265 result.zero_count = 0.0;
266 result.sum = 0.0;
267 result.positive_buckets.fill(0.0);
268 result.negative_buckets.fill(0.0);
269 result
270 }
271
272 fn combine_exact(
273 &self,
274 other: &Self,
275 reset_hint: CounterResetHint,
276 op: impl Fn(f64, f64) -> f64 + Copy,
277 ) -> Option<Self> {
278 if !self.compatible_with(other) {
279 return None;
280 }
281
282 let mut result = self.clone();
283 result.count = op(result.count, other.count);
284 result.zero_count = op(result.zero_count, other.zero_count);
285 result.sum = op(result.sum, other.sum);
286 result.reset_hint = reset_hint;
287 (result.positive_spans, result.positive_buckets) = merge_side(
288 &self.positive_spans,
289 &self.positive_buckets,
290 &other.positive_spans,
291 &other.positive_buckets,
292 op,
293 )?;
294 (result.negative_spans, result.negative_buckets) = merge_side(
295 &self.negative_spans,
296 &self.negative_buckets,
297 &other.negative_spans,
298 &other.negative_buckets,
299 op,
300 )?;
301 Some(result)
302 }
303
304 pub fn add(&self, other: &Self) -> Option<Self> {
308 let reset_hint = add_reset_hint(self.reset_hint, other.reset_hint);
309 if self.is_empty_payload() {
310 let mut result = other.clone();
311 result.reset_hint = reset_hint;
312 return result.compact();
313 }
314 if other.is_empty_payload() {
315 let mut result = self.clone();
316 result.reset_hint = reset_hint;
317 return result.compact();
318 }
319
320 let (left, right) = self.reconcile(other)?;
321 left.combine_exact(&right, reset_hint, |left, right| left + right)?
322 .compact()
323 }
324
325 pub fn sub(&self, other: &Self) -> Option<Self> {
329 if self.is_empty_payload() {
330 return other.clone().negated().compact();
331 }
332 if other.is_empty_payload() {
333 return self.clone().into_gauge().compact();
334 }
335
336 let (left, right) = self.reconcile(other)?;
337 left.combine_exact(&right, CounterResetHint::Gauge, |left, right| left - right)?
338 .compact()
339 }
340
341 pub fn negated(self) -> Self {
343 self.scale(-1.0)
344 }
345
346 pub fn into_gauge(mut self) -> Self {
348 self.reset_hint = CounterResetHint::Gauge;
349 self
350 }
351
352 pub fn counter_reset_hints_contradict(&self, other: &Self) -> bool {
354 matches!(
355 (self.reset_hint, other.reset_hint),
356 (
357 CounterResetHint::CounterReset,
358 CounterResetHint::NotCounterReset
359 ) | (
360 CounterResetHint::NotCounterReset,
361 CounterResetHint::CounterReset
362 )
363 )
364 }
365
366 pub fn needs_custom_reconciliation(&self, other: &Self) -> bool {
368 self.uses_custom_buckets()
369 && other.uses_custom_buckets()
370 && self.custom_values != other.custom_values
371 }
372
373 pub fn promql_eq(&self, other: &Self) -> bool {
377 self.schema == other.schema
378 && self.zero_threshold == other.zero_threshold
379 && self.custom_values == other.custom_values
380 && self.count.to_bits() == other.count.to_bits()
381 && self.zero_count.to_bits() == other.zero_count.to_bits()
382 && self.sum.to_bits() == other.sum.to_bits()
383 && side_counts_equal(
384 &self.positive_spans,
385 &self.positive_buckets,
386 &other.positive_spans,
387 &other.positive_buckets,
388 )
389 && side_counts_equal(
390 &self.negative_spans,
391 &self.negative_buckets,
392 &other.negative_spans,
393 &other.negative_buckets,
394 )
395 }
396
397 pub fn promql_string(&self) -> String {
399 let mut parts = vec![format!("count:{}", self.count), format!("sum:{}", self.sum)];
400 if let Some(buckets) = self.all_buckets() {
401 parts.extend(
402 buckets
403 .into_iter()
404 .filter(|bucket| bucket.count != 0.0)
405 .map(|bucket| {
406 let (left, right) = match bucket.boundary_rule {
407 BoundaryRule::OpenLeft => ("(", "]"),
408 BoundaryRule::OpenRight => ("[", ")"),
409 BoundaryRule::ClosedBoth => ("[", "]"),
410 };
411 format!(
412 "{}{},{}{}:{}",
413 left, bucket.lower, bucket.upper, right, bucket.count
414 )
415 }),
416 );
417 }
418 format!("{{{}}}", parts.join(", "))
419 }
420
421 pub fn estimated_stdvar(&self) -> f64 {
423 if self.count == 0.0 {
424 return f64::NAN;
425 }
426 let mean = self.sum / self.count;
427 let Some(buckets) = self.all_buckets() else {
428 return f64::NAN;
429 };
430 buckets
431 .into_iter()
432 .map(|bucket| {
433 let midpoint = self.bucket_midpoint(&bucket);
434 bucket.count * (midpoint - mean).powi(2)
435 })
436 .sum::<f64>()
437 / self.count
438 }
439
440 pub fn estimated_stddev(&self) -> f64 {
442 self.estimated_stdvar().sqrt()
443 }
444
445 pub fn scale(mut self, factor: f64) -> Self {
449 self.count *= factor;
450 self.zero_count *= factor;
451 self.sum *= factor;
452 for count in &mut self.positive_buckets {
453 *count *= factor;
454 }
455 for count in &mut self.negative_buckets {
456 *count *= factor;
457 }
458 if factor < 0.0 {
459 self.reset_hint = CounterResetHint::Gauge;
460 }
461 self
462 }
463
464 pub fn divide_by(mut self, divisor: f64) -> Self {
468 self.count /= divisor;
469 self.zero_count /= divisor;
470 self.sum /= divisor;
471 if divisor == 0.0 {
472 self.positive_spans.clear();
473 self.positive_buckets.clear();
474 self.negative_spans.clear();
475 self.negative_buckets.clear();
476 } else {
477 for count in &mut self.positive_buckets {
478 *count /= divisor;
479 }
480 for count in &mut self.negative_buckets {
481 *count /= divisor;
482 }
483 }
484 if divisor < 0.0 {
485 self.reset_hint = CounterResetHint::Gauge;
486 }
487 self
488 }
489
490 fn compact(mut self) -> Option<Self> {
491 let (spans, buckets) = compact_side(&self.positive_spans, &self.positive_buckets)?;
492 self.positive_spans = spans;
493 self.positive_buckets = buckets;
494 let (spans, buckets) = compact_side(&self.negative_spans, &self.negative_buckets)?;
495 self.negative_spans = spans;
496 self.negative_buckets = buckets;
497 Some(self)
498 }
499
500 pub fn detect_reset(&self, previous: &Self) -> bool {
502 match self.reset_hint {
503 CounterResetHint::CounterReset => return true,
504 CounterResetHint::NotCounterReset => return false,
505 CounterResetHint::Unknown
506 | CounterResetHint::Gauge
507 | CounterResetHint::Unrecognized(_) => {}
508 }
509
510 if self.count < previous.count {
511 return true;
512 }
513
514 match (self.uses_custom_buckets(), previous.uses_custom_buckets()) {
515 (true, true) => {
516 let Some((current, previous)) = reconcile_custom(self, previous) else {
517 return true;
518 };
519 current.zero_count < previous.zero_count
520 || current.side_has_reset(true, &previous)
521 || current.side_has_reset(false, &previous)
522 }
523 (true, false) | (false, true) => true,
524 (false, false) => {
525 if self.schema > previous.schema || self.zero_threshold < previous.zero_threshold {
526 return true;
527 }
528
529 let mut previous = previous.clone();
530 if self.zero_threshold > previous.zero_threshold {
531 let Some(expanded) = previous.expanded_zero_threshold(self.zero_threshold)
532 else {
533 return true;
534 };
535 if expanded != self.zero_threshold
536 || previous.grow_zero_threshold(self.zero_threshold).is_none()
537 {
538 return true;
539 }
540 }
541 let Some(previous) = previous.copy_to_schema(self.schema) else {
542 return true;
543 };
544
545 self.zero_count < previous.zero_count
546 || self.side_has_reset(true, &previous)
547 || self.side_has_reset(false, &previous)
548 }
549 }
550 }
551
552 fn detect_start_timestamp_reset(
553 &self,
554 previous: &Self,
555 previous_ts: i64,
556 current_ts: i64,
557 ) -> bool {
558 let current_start = self.start_timestamp.unwrap_or_default();
559 if current_start == 0 || current_start >= current_ts || current_start < previous_ts {
560 return false;
561 }
562 if current_start > previous_ts {
563 return true;
564 }
565
566 let previous_start = previous.start_timestamp.unwrap_or_default();
567 previous_start <= previous_ts && previous_start != 0 && previous_start != previous_ts
568 }
569
570 pub fn detect_counter_reset(&self, previous: &Self, previous_ts: i64, current_ts: i64) -> bool {
572 self.detect_start_timestamp_reset(previous, previous_ts, current_ts)
573 || self.detect_reset(previous)
574 }
575
576 fn all_buckets(&self) -> Option<Vec<Bucket>> {
577 let mut buckets = self.side_buckets(false)?;
578 buckets.reverse();
579 if self.zero_count != 0.0 {
580 buckets.push(Bucket {
581 lower: -self.zero_threshold,
582 upper: self.zero_threshold,
583 count: self.zero_count,
584 boundary_rule: BoundaryRule::ClosedBoth,
585 });
586 }
587 buckets.extend(self.side_buckets(true)?);
588 Some(buckets)
589 }
590
591 fn side_buckets(&self, positive: bool) -> Option<Vec<Bucket>> {
592 let (spans, counts) = if positive {
593 (&self.positive_spans, &self.positive_buckets)
594 } else {
595 (&self.negative_spans, &self.negative_buckets)
596 };
597
598 let mut result = Vec::with_capacity(counts.len());
599 for (idx, count) in side_bucket_indices(spans)?.into_iter().zip(counts) {
600 let upper = get_bound(idx, self.schema, &self.custom_values)?;
601 let lower = get_bound(idx.checked_sub(1)?, self.schema, &self.custom_values)?;
602 if positive {
603 result.push(Bucket {
604 lower,
605 upper,
606 count: *count,
607 boundary_rule: if self.uses_custom_buckets() && idx == 0 {
608 BoundaryRule::ClosedBoth
609 } else {
610 BoundaryRule::OpenLeft
611 },
612 });
613 } else {
614 result.push(Bucket {
615 lower: -upper,
616 upper: -lower,
617 count: *count,
618 boundary_rule: BoundaryRule::OpenRight,
619 });
620 }
621 }
622 Some(result)
623 }
624
625 pub fn quantile(&self, q: f64) -> f64 {
630 if q < 0.0 {
631 return f64::NEG_INFINITY;
632 }
633 if q > 1.0 {
634 return f64::INFINITY;
635 }
636 if self.count == 0.0 || q.is_nan() {
637 return f64::NAN;
638 }
639
640 let Some(mut buckets) = self.all_buckets() else {
641 return f64::NAN;
642 };
643 let rank = q * self.count;
644 let mut count = 0.0;
645 for bucket in &mut buckets {
646 if bucket.count == 0.0 {
647 continue;
648 }
649 count += bucket.count;
650 if count < rank {
651 continue;
652 }
653
654 if !self.uses_custom_buckets() && bucket.lower < 0.0 && bucket.upper > 0.0 {
655 if self.negative_buckets.is_empty() && !self.positive_buckets.is_empty() {
656 bucket.lower = 0.0;
657 } else if self.positive_buckets.is_empty() && !self.negative_buckets.is_empty() {
658 bucket.upper = 0.0;
659 }
660 } else if self.uses_custom_buckets() {
661 if bucket.lower == f64::NEG_INFINITY {
662 if bucket.upper <= 0.0 {
663 return bucket.upper;
664 }
665 bucket.lower = 0.0;
666 } else if bucket.upper == f64::INFINITY {
667 return bucket.lower;
668 }
669 }
670
671 let rank_in_bucket = rank - (count - bucket.count);
672 let fraction = rank_in_bucket / bucket.count;
673 if self.uses_custom_buckets() || (bucket.lower <= 0.0 && bucket.upper >= 0.0) {
674 return bucket.lower + (bucket.upper - bucket.lower) * fraction;
675 }
676
677 let log_lower = bucket.lower.abs().log2();
678 let log_upper = bucket.upper.abs().log2();
679 if bucket.lower > 0.0 {
680 return 2.0_f64.powf(log_lower + (log_upper - log_lower) * fraction);
681 }
682 return -2.0_f64.powf(log_upper + (log_lower - log_upper) * (1.0 - fraction));
683 }
684
685 f64::NAN
686 }
687
688 pub fn fraction(&self, lower: f64, upper: f64) -> f64 {
690 if self.count == 0.0 || lower.is_nan() || upper.is_nan() {
691 return f64::NAN;
692 }
693 if lower >= upper {
694 return 0.0;
695 }
696
697 let Some(mut buckets) = self.all_buckets() else {
698 return f64::NAN;
699 };
700 let count = if self.sum.is_nan() {
701 buckets.iter().map(|bucket| bucket.count).sum()
702 } else {
703 self.count
704 };
705
706 let mut rank = 0.0;
707 let mut lower_rank = 0.0;
708 let mut upper_rank = 0.0;
709 let mut lower_set = false;
710 let mut upper_set = false;
711
712 for bucket in &mut buckets {
713 let zero_bucket = bucket.lower <= 0.0 && bucket.upper >= 0.0;
714 if zero_bucket {
715 if self.negative_buckets.is_empty() && !self.positive_buckets.is_empty() {
716 bucket.lower = 0.0;
717 } else if self.positive_buckets.is_empty() && !self.negative_buckets.is_empty() {
718 bucket.upper = 0.0;
719 }
720 }
721
722 if !lower_set && bucket.lower >= lower {
723 lower_rank = rank;
724 lower_set = true;
725 }
726 if !upper_set && bucket.lower >= upper {
727 upper_rank = rank;
728 upper_set = true;
729 }
730 if lower_set && upper_set {
731 break;
732 }
733 if !lower_set && bucket.lower < lower && bucket.upper > lower {
734 lower_rank = self.interpolate_rank(bucket, rank, lower, zero_bucket);
735 lower_set = true;
736 }
737 if !upper_set && bucket.lower < upper && bucket.upper > upper {
738 upper_rank = self.interpolate_rank(bucket, rank, upper, zero_bucket);
739 upper_set = true;
740 }
741 if lower_set && upper_set {
742 break;
743 }
744 rank += bucket.count;
745 }
746
747 if !lower_set || lower_rank > count {
748 lower_rank = count;
749 }
750 if !upper_set || upper_rank > count {
751 upper_rank = count;
752 }
753
754 (upper_rank - lower_rank) / self.count
755 }
756
757 pub fn to_prometheus_buckets(&self) -> Option<Vec<(u8, String, String, String)>> {
759 Some(
760 self.all_buckets()?
761 .into_iter()
762 .filter(|bucket| bucket.count != 0.0)
763 .map(|bucket| {
764 (
765 bucket.boundary_rule as u8,
766 format_prometheus_float(bucket.lower),
767 format_prometheus_float(bucket.upper),
768 format_prometheus_float(bucket.count),
769 )
770 })
771 .collect(),
772 )
773 }
774
775 fn interpolate_rank(&self, bucket: &Bucket, rank: f64, value: f64, zero_bucket: bool) -> f64 {
776 if self.uses_custom_buckets() || zero_bucket {
777 if bucket.lower == f64::NEG_INFINITY {
778 return bucket.count;
779 }
780 return rank + bucket.count * (value - bucket.lower) / (bucket.upper - bucket.lower);
781 }
782
783 let log_lower = bucket.lower.abs().log2();
784 let log_upper = bucket.upper.abs().log2();
785 let log_value = value.abs().log2();
786 let fraction = if value > 0.0 {
787 (log_value - log_lower) / (log_upper - log_lower)
788 } else {
789 1.0 - ((log_value - log_upper) / (log_lower - log_upper))
790 };
791 rank + bucket.count * fraction
792 }
793
794 fn bucket_midpoint(&self, bucket: &Bucket) -> f64 {
795 if bucket.lower == f64::NEG_INFINITY && bucket.upper == f64::INFINITY {
796 return 0.0;
797 }
798 if bucket.lower == f64::NEG_INFINITY {
799 return bucket.upper;
800 }
801 if bucket.upper == f64::INFINITY {
802 return bucket.lower;
803 }
804 if self.uses_custom_buckets() || (bucket.lower <= 0.0 && bucket.upper >= 0.0) {
805 return (bucket.lower + bucket.upper) / 2.0;
806 }
807 if bucket.upper < 0.0 {
808 -((bucket.lower.abs() * bucket.upper.abs()).sqrt())
809 } else {
810 (bucket.lower * bucket.upper).sqrt()
811 }
812 }
813
814 fn side_has_reset(&self, positive: bool, previous: &Self) -> bool {
815 let (current_spans, current_buckets, previous_spans, previous_buckets) = if positive {
816 (
817 &self.positive_spans,
818 &self.positive_buckets,
819 &previous.positive_spans,
820 &previous.positive_buckets,
821 )
822 } else {
823 (
824 &self.negative_spans,
825 &self.negative_buckets,
826 &previous.negative_spans,
827 &previous.negative_buckets,
828 )
829 };
830 let Some(current) = side_counts(current_spans, current_buckets) else {
831 return true;
832 };
833 let Some(previous) = side_counts(previous_spans, previous_buckets) else {
834 return true;
835 };
836 previous.keys().chain(current.keys()).any(|idx| {
837 current.get(idx).copied().unwrap_or_default()
838 < previous.get(idx).copied().unwrap_or_default()
839 })
840 }
841
842 fn reconcile(&self, other: &Self) -> Option<(Self, Self)> {
843 match (self.uses_custom_buckets(), other.uses_custom_buckets()) {
844 (true, true) => reconcile_custom(self, other),
845 (false, false) => reconcile_exponential(self, other),
846 _ => None,
847 }
848 }
849
850 fn is_empty_payload(&self) -> bool {
851 self.count == 0.0
852 && self.zero_count == 0.0
853 && self.sum == 0.0
854 && self.positive_buckets.iter().all(|count| *count == 0.0)
855 && self.negative_buckets.iter().all(|count| *count == 0.0)
856 }
857}
858
859fn reconcile_exponential(
860 left: &NativeHistogram,
861 right: &NativeHistogram,
862) -> Option<(NativeHistogram, NativeHistogram)> {
863 let schema = left.schema.min(right.schema);
864 let mut left = left.copy_to_schema(schema)?;
865 let mut right = right.copy_to_schema(schema)?;
866 let mut zero_threshold = left.zero_threshold.max(right.zero_threshold);
867 loop {
868 let expanded = left
869 .expanded_zero_threshold(zero_threshold)?
870 .max(right.expanded_zero_threshold(zero_threshold)?);
871 if expanded == zero_threshold {
872 break;
873 }
874 zero_threshold = expanded;
875 }
876 left.grow_zero_threshold(zero_threshold)?;
877 right.grow_zero_threshold(zero_threshold)?;
878 Some((left.compact()?, right.compact()?))
879}
880
881fn reconcile_custom(
882 left: &NativeHistogram,
883 right: &NativeHistogram,
884) -> Option<(NativeHistogram, NativeHistogram)> {
885 let custom_values = if left.custom_values == right.custom_values {
886 left.custom_values.clone()
887 } else {
888 left.custom_values
889 .iter()
890 .copied()
891 .filter(|value| right.custom_values.contains(value))
892 .collect()
893 };
894
895 Some((
896 left.copy_to_custom_values(custom_values.clone())?
897 .compact()?,
898 right.copy_to_custom_values(custom_values)?.compact()?,
899 ))
900}
901
902impl NativeHistogram {
903 fn copy_to_schema(&self, target_schema: i32) -> Option<Self> {
904 if self.uses_custom_buckets()
905 || !(MIN_EXPONENTIAL_SCHEMA..=MAX_EXPONENTIAL_SCHEMA).contains(&target_schema)
906 || target_schema > self.schema
907 {
908 return None;
909 }
910 if target_schema == self.schema {
911 return Some(self.clone());
912 }
913
914 let mut result = self.clone();
915 result.schema = target_schema;
916 (result.positive_spans, result.positive_buckets) = reduce_side(
917 &self.positive_spans,
918 &self.positive_buckets,
919 self.schema,
920 target_schema,
921 )?;
922 (result.negative_spans, result.negative_buckets) = reduce_side(
923 &self.negative_spans,
924 &self.negative_buckets,
925 self.schema,
926 target_schema,
927 )?;
928 Some(result)
929 }
930
931 fn copy_to_custom_values(&self, custom_values: Vec<f64>) -> Option<Self> {
932 if !self.uses_custom_buckets() {
933 return None;
934 }
935 if self.custom_values == custom_values {
936 return Some(self.clone());
937 }
938
939 let mut result = self.clone();
940 result.custom_values = custom_values.clone();
941 result.negative_spans.clear();
942 result.negative_buckets.clear();
943 (result.positive_spans, result.positive_buckets) = map_custom_side(
944 &self.positive_spans,
945 &self.positive_buckets,
946 &self.custom_values,
947 &custom_values,
948 )?;
949 Some(result)
950 }
951
952 fn grow_zero_threshold(&mut self, zero_threshold: f64) -> Option<()> {
953 if self.uses_custom_buckets() || zero_threshold == self.zero_threshold {
954 self.zero_threshold = zero_threshold;
955 return Some(());
956 }
957
958 let (spans, buckets, zero_count) = fold_zero_side(
959 &self.positive_spans,
960 &self.positive_buckets,
961 self.schema,
962 zero_threshold,
963 )?;
964 self.positive_spans = spans;
965 self.positive_buckets = buckets;
966 self.zero_count += zero_count;
967
968 let (spans, buckets, zero_count) = fold_zero_side(
969 &self.negative_spans,
970 &self.negative_buckets,
971 self.schema,
972 zero_threshold,
973 )?;
974 self.negative_spans = spans;
975 self.negative_buckets = buckets;
976 self.zero_count += zero_count;
977 self.zero_threshold = zero_threshold;
978 Some(())
979 }
980
981 fn expanded_zero_threshold(&self, mut zero_threshold: f64) -> Option<f64> {
982 if self.uses_custom_buckets() {
983 return Some(zero_threshold);
984 }
985 zero_threshold = expand_zero_threshold_side(
986 &self.positive_spans,
987 &self.positive_buckets,
988 self.schema,
989 zero_threshold,
990 )?;
991 expand_zero_threshold_side(
992 &self.negative_spans,
993 &self.negative_buckets,
994 self.schema,
995 zero_threshold,
996 )
997 }
998}
999
1000fn add_reset_hint(left: CounterResetHint, right: CounterResetHint) -> CounterResetHint {
1001 if left == CounterResetHint::Gauge || right == CounterResetHint::Gauge {
1002 CounterResetHint::Gauge
1003 } else if left == right {
1004 left
1005 } else {
1006 CounterResetHint::Unknown
1007 }
1008}
1009
1010fn side_bucket_indices(spans: &[Span]) -> Option<Vec<i32>> {
1011 let mut indices = Vec::new();
1012 let mut current_index = 0i32;
1013 let mut first = true;
1014 for span in spans {
1015 if first {
1016 current_index = span.offset;
1017 first = false;
1018 } else {
1019 current_index = current_index.checked_add(span.offset)?;
1020 }
1021 for _ in 0..span.length {
1022 indices.push(current_index);
1023 current_index = current_index.checked_add(1)?;
1024 }
1025 }
1026 Some(indices)
1027}
1028
1029fn spans_from_indices_counts(values: Vec<(i32, f64)>) -> Option<(Vec<Span>, Vec<f64>)> {
1030 let mut spans = Vec::<Span>::new();
1031 let mut buckets = Vec::new();
1032 let mut previous_index = None::<i32>;
1033
1034 for (idx, count) in values {
1035 if count == 0.0 {
1036 continue;
1037 }
1038 match (spans.last_mut(), previous_index) {
1039 (Some(span), Some(previous)) if previous.checked_add(1) == Some(idx) => {
1040 span.length = span.length.checked_add(1)?;
1041 }
1042 (_, Some(previous)) => {
1043 spans.push(Span {
1044 offset: idx.checked_sub(previous)?.checked_sub(1)?,
1045 length: 1,
1046 });
1047 }
1048 (_, None) => {
1049 spans.push(Span {
1050 offset: idx,
1051 length: 1,
1052 });
1053 }
1054 }
1055 buckets.push(count);
1056 previous_index = Some(idx);
1057 }
1058
1059 Some((spans, buckets))
1060}
1061
1062fn side_counts(spans: &[Span], buckets: &[f64]) -> Option<BTreeMap<i32, f64>> {
1063 let mut values = BTreeMap::new();
1064 for (idx, count) in side_bucket_indices(spans)?.into_iter().zip(buckets) {
1065 if *count != 0.0 {
1066 values.insert(idx, *count);
1067 }
1068 }
1069 Some(values)
1070}
1071
1072fn side_counts_equal(
1073 left_spans: &[Span],
1074 left_buckets: &[f64],
1075 right_spans: &[Span],
1076 right_buckets: &[f64],
1077) -> bool {
1078 match (
1079 side_counts(left_spans, left_buckets),
1080 side_counts(right_spans, right_buckets),
1081 ) {
1082 (Some(left), Some(right)) => {
1083 left.len() == right.len()
1084 && left.iter().all(|(idx, count)| {
1085 right
1086 .get(idx)
1087 .is_some_and(|other| count.to_bits() == other.to_bits())
1088 })
1089 }
1090 (None, None) => true,
1091 _ => false,
1092 }
1093}
1094
1095fn merge_side(
1096 left_spans: &[Span],
1097 left_buckets: &[f64],
1098 right_spans: &[Span],
1099 right_buckets: &[f64],
1100 op: impl Fn(f64, f64) -> f64,
1101) -> Option<(Vec<Span>, Vec<f64>)> {
1102 let left = side_counts(left_spans, left_buckets)?;
1103 let right = side_counts(right_spans, right_buckets)?;
1104 let mut values = BTreeMap::new();
1105 for idx in left.keys().chain(right.keys()) {
1106 values.insert(
1107 *idx,
1108 op(
1109 left.get(idx).copied().unwrap_or_default(),
1110 right.get(idx).copied().unwrap_or_default(),
1111 ),
1112 );
1113 }
1114 spans_from_indices_counts(values.into_iter().collect())
1115}
1116
1117fn reduce_side(
1118 spans: &[Span],
1119 buckets: &[f64],
1120 schema: i32,
1121 target_schema: i32,
1122) -> Option<(Vec<Span>, Vec<f64>)> {
1123 let factor = 1_i32.checked_shl((schema - target_schema) as u32)?;
1124 let mut values = std::collections::BTreeMap::<i32, f64>::new();
1125 for (idx, count) in side_bucket_indices(spans)?.into_iter().zip(buckets) {
1126 let target_idx = ceil_div(idx, factor);
1127 *values.entry(target_idx).or_default() += *count;
1128 }
1129 spans_from_indices_counts(values.into_iter().collect())
1130}
1131
1132fn compact_side(spans: &[Span], buckets: &[f64]) -> Option<(Vec<Span>, Vec<f64>)> {
1133 let indices = side_bucket_indices(spans)?;
1134 spans_from_indices_counts(indices.into_iter().zip(buckets.iter().copied()).collect())
1135}
1136
1137fn map_custom_side(
1138 spans: &[Span],
1139 buckets: &[f64],
1140 old_values: &[f64],
1141 new_values: &[f64],
1142) -> Option<(Vec<Span>, Vec<f64>)> {
1143 let mut values = std::collections::BTreeMap::<i32, f64>::new();
1144 for (idx, count) in side_bucket_indices(spans)?.into_iter().zip(buckets) {
1145 let upper = get_bound(idx, CUSTOM_BUCKETS_SCHEMA, old_values)?;
1146 let target_idx = new_values
1147 .iter()
1148 .position(|value| *value >= upper)
1149 .unwrap_or(new_values.len()) as i32;
1150 *values.entry(target_idx).or_default() += *count;
1151 }
1152 spans_from_indices_counts(values.into_iter().collect())
1153}
1154
1155fn fold_zero_side(
1156 spans: &[Span],
1157 buckets: &[f64],
1158 schema: i32,
1159 zero_threshold: f64,
1160) -> Option<(Vec<Span>, Vec<f64>, f64)> {
1161 let mut kept = Vec::new();
1162 let mut zero_count = 0.0;
1163 for (idx, count) in side_bucket_indices(spans)?.into_iter().zip(buckets) {
1164 if get_bound(idx, schema, &[])? <= zero_threshold {
1165 zero_count += *count;
1166 } else {
1167 kept.push((idx, *count));
1168 }
1169 }
1170 let (spans, buckets) = spans_from_indices_counts(kept)?;
1171 Some((spans, buckets, zero_count))
1172}
1173
1174fn expand_zero_threshold_side(
1175 spans: &[Span],
1176 buckets: &[f64],
1177 schema: i32,
1178 mut zero_threshold: f64,
1179) -> Option<f64> {
1180 for (idx, count) in side_bucket_indices(spans)?.into_iter().zip(buckets) {
1181 if *count == 0.0 {
1182 continue;
1183 }
1184 let lower = get_bound(idx.checked_sub(1)?, schema, &[])?;
1185 let upper = get_bound(idx, schema, &[])?;
1186 if lower < zero_threshold && zero_threshold < upper {
1187 zero_threshold = upper;
1188 }
1189 }
1190 Some(zero_threshold)
1191}
1192
1193fn ceil_div(value: i32, divisor: i32) -> i32 {
1194 value.div_euclid(divisor) + i32::from(value.rem_euclid(divisor) != 0)
1195}
1196
1197fn get_bound(idx: i32, schema: i32, custom_values: &[f64]) -> Option<f64> {
1198 if schema == CUSTOM_BUCKETS_SCHEMA {
1199 return match idx {
1200 -1 => Some(f64::NEG_INFINITY),
1201 idx if idx == custom_values.len() as i32 => Some(f64::INFINITY),
1202 idx if idx >= 0 && (idx as usize) < custom_values.len() => {
1203 Some(custom_values[idx as usize])
1204 }
1205 _ => None,
1206 };
1207 }
1208
1209 if !(MIN_EXPONENTIAL_SCHEMA..=MAX_EXPONENTIAL_SCHEMA).contains(&schema) {
1210 return None;
1211 }
1212 if schema < 0 {
1213 let exponent = idx.checked_shl((-schema) as u32)?;
1214 return Some(if exponent == f64::MAX_EXP {
1215 f64::MAX
1216 } else {
1217 2.0_f64.powi(exponent)
1218 });
1219 }
1220 let exponent = idx as f64 / (1u32 << schema) as f64;
1221 Some(if exponent == f64::MAX_EXP as f64 {
1222 f64::MAX
1223 } else {
1224 2.0_f64.powf(exponent)
1225 })
1226}
1227
1228pub fn native_histogram_arrow_type() -> ArrowDataType {
1230 native_histogram_value_type().as_arrow_type()
1231}
1232
1233fn struct_child<'a>(array: &'a StructArray, name: &str) -> DfResult<&'a ArrayRef> {
1234 let index = array
1235 .fields()
1236 .iter()
1237 .position(|field| field.name() == name)
1238 .ok_or_else(|| {
1239 DataFusionError::Execution(format!("native histogram missing field {name}"))
1240 })?;
1241 Ok(array.column(index))
1242}
1243
1244fn primitive_child<'a, T>(array: &'a StructArray, name: &str) -> DfResult<&'a PrimitiveArray<T>>
1245where
1246 T: ArrowPrimitiveType,
1247{
1248 let child = struct_child(array, name)?;
1249 child
1250 .as_any()
1251 .downcast_ref::<PrimitiveArray<T>>()
1252 .ok_or_else(|| {
1253 DataFusionError::Execution(format!(
1254 "native histogram field {name} has invalid type {}",
1255 child.data_type()
1256 ))
1257 })
1258}
1259
1260fn list_child<'a>(array: &'a StructArray, name: &str) -> DfResult<&'a ListArray> {
1261 let child = struct_child(array, name)?;
1262 child.as_any().downcast_ref::<ListArray>().ok_or_else(|| {
1263 DataFusionError::Execution(format!(
1264 "native histogram field {name} has invalid type {}",
1265 child.data_type()
1266 ))
1267 })
1268}
1269
1270fn required_primitive<T>(array: &StructArray, name: &str, row: usize) -> DfResult<T::Native>
1271where
1272 T: ArrowPrimitiveType,
1273{
1274 let values = primitive_child::<T>(array, name)?;
1275 if values.is_null(row) {
1276 return Err(DataFusionError::Execution(format!(
1277 "native histogram field {name} is null"
1278 )));
1279 }
1280 Ok(values.value(row))
1281}
1282
1283fn optional_primitive<T>(array: &StructArray, name: &str, row: usize) -> DfResult<Option<T::Native>>
1284where
1285 T: ArrowPrimitiveType,
1286{
1287 let values = primitive_child::<T>(array, name)?;
1288 Ok((!values.is_null(row)).then(|| values.value(row)))
1289}
1290
1291fn list_values<T>(array: &StructArray, name: &str, row: usize) -> DfResult<Vec<T::Native>>
1292where
1293 T: ArrowPrimitiveType,
1294{
1295 let list = list_child(array, name)?;
1296 if list.is_null(row) {
1297 return Ok(Vec::new());
1298 }
1299
1300 let values = list.value(row);
1301 let values = values
1302 .as_any()
1303 .downcast_ref::<PrimitiveArray<T>>()
1304 .ok_or_else(|| {
1305 DataFusionError::Execution(format!(
1306 "native histogram list field {name} has invalid value type {}",
1307 values.data_type()
1308 ))
1309 })?;
1310
1311 values
1312 .iter()
1313 .map(|value| {
1314 value.ok_or_else(|| {
1315 DataFusionError::Execution(format!(
1316 "native histogram list field {name} contains null"
1317 ))
1318 })
1319 })
1320 .collect()
1321}
1322
1323fn read_spans(offsets: Vec<i32>, lengths: Vec<u32>, name: &str) -> DfResult<Vec<Span>> {
1324 if offsets.len() != lengths.len() {
1325 return Err(DataFusionError::Execution(format!(
1326 "native histogram {name} span offsets and lengths mismatch: {} vs {}",
1327 offsets.len(),
1328 lengths.len()
1329 )));
1330 }
1331 Ok(offsets
1332 .into_iter()
1333 .zip(lengths)
1334 .map(|(offset, length)| Span { offset, length })
1335 .collect())
1336}
1337
1338fn check_span_bucket_count(spans: &[Span], buckets: usize, name: &str) -> DfResult<()> {
1339 let span_len = spans
1340 .iter()
1341 .try_fold(0usize, |sum, span| sum.checked_add(span.length as usize))
1342 .ok_or_else(|| {
1343 DataFusionError::Execution(format!("native histogram {name} spans overflow"))
1344 })?;
1345 if span_len != buckets {
1346 return Err(DataFusionError::Execution(format!(
1347 "native histogram {name} spans describe {span_len} buckets, found {buckets}"
1348 )));
1349 }
1350 Ok(())
1351}
1352
1353pub fn read_histogram(array: &StructArray, row: usize) -> DfResult<Option<NativeHistogram>> {
1358 if array.is_null(row) {
1359 return Ok(None);
1360 }
1361
1362 let schema = required_primitive::<Int32Type>(array, SCHEMA_FIELD, row)?;
1363 let positive_spans = read_spans(
1364 list_values::<Int32Type>(array, POSITIVE_SPAN_OFFSETS_FIELD, row)?,
1365 list_values::<UInt32Type>(array, POSITIVE_SPAN_LENGTHS_FIELD, row)?,
1366 "positive",
1367 )?;
1368 let negative_spans = read_spans(
1369 list_values::<Int32Type>(array, NEGATIVE_SPAN_OFFSETS_FIELD, row)?,
1370 list_values::<UInt32Type>(array, NEGATIVE_SPAN_LENGTHS_FIELD, row)?,
1371 "negative",
1372 )?;
1373
1374 let (count, zero_count, positive_buckets, negative_buckets) =
1375 if let Some(count) = optional_primitive::<Float64Type>(array, COUNT_F64_FIELD, row)? {
1376 (
1377 count,
1378 optional_primitive::<Float64Type>(array, ZERO_COUNT_F64_FIELD, row)?
1379 .unwrap_or_default(),
1380 list_values::<Float64Type>(array, POSITIVE_BUCKETS_F64_FIELD, row)?,
1381 list_values::<Float64Type>(array, NEGATIVE_BUCKETS_F64_FIELD, row)?,
1382 )
1383 } else {
1384 (
1385 required_primitive::<UInt64Type>(array, COUNT_U64_FIELD, row)? as f64,
1386 optional_primitive::<UInt64Type>(array, ZERO_COUNT_U64_FIELD, row)?
1387 .unwrap_or_default() as f64,
1388 list_values::<Int64Type>(array, POSITIVE_BUCKETS_I64_FIELD, row)?
1389 .into_iter()
1390 .map(|value| value as f64)
1391 .collect(),
1392 list_values::<Int64Type>(array, NEGATIVE_BUCKETS_I64_FIELD, row)?
1393 .into_iter()
1394 .map(|value| value as f64)
1395 .collect(),
1396 )
1397 };
1398
1399 check_span_bucket_count(&positive_spans, positive_buckets.len(), "positive")?;
1400 check_span_bucket_count(&negative_spans, negative_buckets.len(), "negative")?;
1401
1402 Ok(Some(NativeHistogram {
1403 schema,
1404 zero_threshold: required_primitive::<Float64Type>(array, ZERO_THRESHOLD_FIELD, row)?,
1405 sum: required_primitive::<Float64Type>(array, SUM_FIELD, row)?,
1406 reset_hint: required_primitive::<Int32Type>(array, RESET_HINT_FIELD, row)?.into(),
1407 start_timestamp: optional_primitive::<TimestampMillisecondType>(
1408 array,
1409 START_TIMESTAMP_FIELD,
1410 row,
1411 )?,
1412 custom_values: list_values::<Float64Type>(array, CUSTOM_VALUES_FIELD, row)?,
1413 positive_spans,
1414 negative_spans,
1415 count,
1416 zero_count,
1417 positive_buckets,
1418 negative_buckets,
1419 }))
1420}
1421
1422fn list_opt<T>(values: Vec<T>) -> Option<Vec<Option<T>>> {
1423 Some(values.into_iter().map(Some).collect())
1424}
1425
1426pub fn build_histogram_array(values: &[Option<NativeHistogram>]) -> ArrayRef {
1432 let mut schemas = Vec::with_capacity(values.len());
1433 let mut zero_thresholds = Vec::with_capacity(values.len());
1434 let mut sums = Vec::with_capacity(values.len());
1435 let mut reset_hints = Vec::with_capacity(values.len());
1436 let mut start_timestamps = Vec::with_capacity(values.len());
1437 let mut custom_values = Vec::with_capacity(values.len());
1438 let mut positive_span_offsets = Vec::with_capacity(values.len());
1439 let mut positive_span_lengths = Vec::with_capacity(values.len());
1440 let mut negative_span_offsets = Vec::with_capacity(values.len());
1441 let mut negative_span_lengths = Vec::with_capacity(values.len());
1442 let mut count_u64 = Vec::<Option<u64>>::with_capacity(values.len());
1443 let mut zero_count_u64 = Vec::<Option<u64>>::with_capacity(values.len());
1444 let mut positive_buckets_i64 = Vec::with_capacity(values.len());
1445 let mut negative_buckets_i64 = Vec::with_capacity(values.len());
1446 let mut count_f64 = Vec::with_capacity(values.len());
1447 let mut zero_count_f64 = Vec::with_capacity(values.len());
1448 let mut positive_buckets_f64 = Vec::with_capacity(values.len());
1449 let mut negative_buckets_f64 = Vec::with_capacity(values.len());
1450 let mut validity = Vec::with_capacity(values.len());
1451
1452 for value in values {
1453 validity.push(value.is_some());
1454 if let Some(histogram) = value {
1455 schemas.push(Some(histogram.schema));
1456 zero_thresholds.push(Some(histogram.zero_threshold));
1457 sums.push(Some(histogram.sum));
1458 reset_hints.push(Some(i32::from(histogram.reset_hint)));
1459 start_timestamps.push(histogram.start_timestamp);
1460 custom_values.push(list_opt(histogram.custom_values.clone()));
1461 positive_span_offsets.push(list_opt(
1462 histogram
1463 .positive_spans
1464 .iter()
1465 .map(|span| span.offset)
1466 .collect(),
1467 ));
1468 positive_span_lengths.push(list_opt(
1469 histogram
1470 .positive_spans
1471 .iter()
1472 .map(|span| span.length)
1473 .collect(),
1474 ));
1475 negative_span_offsets.push(list_opt(
1476 histogram
1477 .negative_spans
1478 .iter()
1479 .map(|span| span.offset)
1480 .collect(),
1481 ));
1482 negative_span_lengths.push(list_opt(
1483 histogram
1484 .negative_spans
1485 .iter()
1486 .map(|span| span.length)
1487 .collect(),
1488 ));
1489 count_u64.push(None);
1490 zero_count_u64.push(None);
1491 positive_buckets_i64.push(list_opt(Vec::<i64>::new()));
1492 negative_buckets_i64.push(list_opt(Vec::<i64>::new()));
1493 count_f64.push(Some(histogram.count));
1494 zero_count_f64.push(Some(histogram.zero_count));
1495 positive_buckets_f64.push(list_opt(histogram.positive_buckets.clone()));
1496 negative_buckets_f64.push(list_opt(histogram.negative_buckets.clone()));
1497 } else {
1498 schemas.push(None);
1499 zero_thresholds.push(None);
1500 sums.push(None);
1501 reset_hints.push(None);
1502 start_timestamps.push(None);
1503 custom_values.push(None);
1504 positive_span_offsets.push(None);
1505 positive_span_lengths.push(None);
1506 negative_span_offsets.push(None);
1507 negative_span_lengths.push(None);
1508 count_u64.push(None);
1509 zero_count_u64.push(None);
1510 positive_buckets_i64.push(None);
1511 negative_buckets_i64.push(None);
1512 count_f64.push(None);
1513 zero_count_f64.push(None);
1514 positive_buckets_f64.push(None);
1515 negative_buckets_f64.push(None);
1516 }
1517 }
1518
1519 let named_arrays: Vec<(&str, ArrayRef)> = vec![
1520 (SCHEMA_FIELD, Arc::new(Int32Array::from(schemas))),
1521 (
1522 ZERO_THRESHOLD_FIELD,
1523 Arc::new(Float64Array::from(zero_thresholds)),
1524 ),
1525 (SUM_FIELD, Arc::new(Float64Array::from(sums))),
1526 (RESET_HINT_FIELD, Arc::new(Int32Array::from(reset_hints))),
1527 (
1528 START_TIMESTAMP_FIELD,
1529 Arc::new(TimestampMillisecondArray::from_iter(start_timestamps)),
1530 ),
1531 (
1532 CUSTOM_VALUES_FIELD,
1533 Arc::new(ListArray::from_iter_primitive::<Float64Type, _, _>(
1534 custom_values,
1535 )),
1536 ),
1537 (
1538 POSITIVE_SPAN_OFFSETS_FIELD,
1539 Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(
1540 positive_span_offsets,
1541 )),
1542 ),
1543 (
1544 POSITIVE_SPAN_LENGTHS_FIELD,
1545 Arc::new(ListArray::from_iter_primitive::<UInt32Type, _, _>(
1546 positive_span_lengths,
1547 )),
1548 ),
1549 (
1550 NEGATIVE_SPAN_OFFSETS_FIELD,
1551 Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(
1552 negative_span_offsets,
1553 )),
1554 ),
1555 (
1556 NEGATIVE_SPAN_LENGTHS_FIELD,
1557 Arc::new(ListArray::from_iter_primitive::<UInt32Type, _, _>(
1558 negative_span_lengths,
1559 )),
1560 ),
1561 (COUNT_U64_FIELD, Arc::new(UInt64Array::from(count_u64))),
1562 (
1563 ZERO_COUNT_U64_FIELD,
1564 Arc::new(UInt64Array::from(zero_count_u64)),
1565 ),
1566 (
1567 POSITIVE_BUCKETS_I64_FIELD,
1568 Arc::new(ListArray::from_iter_primitive::<Int64Type, _, _>(
1569 positive_buckets_i64,
1570 )),
1571 ),
1572 (
1573 NEGATIVE_BUCKETS_I64_FIELD,
1574 Arc::new(ListArray::from_iter_primitive::<Int64Type, _, _>(
1575 negative_buckets_i64,
1576 )),
1577 ),
1578 (COUNT_F64_FIELD, Arc::new(Float64Array::from(count_f64))),
1579 (
1580 ZERO_COUNT_F64_FIELD,
1581 Arc::new(Float64Array::from(zero_count_f64)),
1582 ),
1583 (
1584 POSITIVE_BUCKETS_F64_FIELD,
1585 Arc::new(ListArray::from_iter_primitive::<Float64Type, _, _>(
1586 positive_buckets_f64,
1587 )),
1588 ),
1589 (
1590 NEGATIVE_BUCKETS_F64_FIELD,
1591 Arc::new(ListArray::from_iter_primitive::<Float64Type, _, _>(
1592 negative_buckets_f64,
1593 )),
1594 ),
1595 ];
1596 let (fields, arrays): (Vec<_>, Vec<_>) = named_arrays
1597 .into_iter()
1598 .map(|(name, array)| (Field::new(name, array.data_type().clone(), true), array))
1599 .unzip();
1600
1601 Arc::new(StructArray::new(
1602 fields.into(),
1603 arrays,
1604 Some(NullBuffer::from(validity)),
1605 ))
1606}
1607
1608#[cfg(test)]
1609mod tests {
1610 use super::*;
1611
1612 fn histogram(positive_spans: Vec<Span>, positive_buckets: Vec<f64>) -> NativeHistogram {
1613 let count = positive_buckets.iter().sum();
1614 NativeHistogram {
1615 schema: 0,
1616 zero_threshold: 0.0,
1617 sum: count,
1618 reset_hint: CounterResetHint::CounterReset,
1619 start_timestamp: None,
1620 custom_values: Vec::new(),
1621 positive_spans,
1622 negative_spans: Vec::new(),
1623 count,
1624 zero_count: 0.0,
1625 positive_buckets,
1626 negative_buckets: Vec::new(),
1627 }
1628 }
1629
1630 #[test]
1631 fn span_rebuild_checks_offsets_and_preserves_empty_input() {
1632 assert_eq!(
1633 spans_from_indices_counts(Vec::new()),
1634 Some((Vec::new(), Vec::new()))
1635 );
1636 assert_eq!(
1637 spans_from_indices_counts(vec![(2, 1.0), (3, 2.0), (5, 3.0)]),
1638 Some((
1639 vec![
1640 Span {
1641 offset: 2,
1642 length: 2,
1643 },
1644 Span {
1645 offset: 1,
1646 length: 1,
1647 },
1648 ],
1649 vec![1.0, 2.0, 3.0],
1650 ))
1651 );
1652 assert_eq!(
1653 spans_from_indices_counts(vec![(i32::MIN, 1.0), (i32::MAX, 2.0)]),
1654 None
1655 );
1656 }
1657
1658 #[test]
1659 fn arrow_round_trip_preserves_unrecognized_reset_hint() {
1660 let mut expected = histogram(
1661 vec![Span {
1662 offset: 0,
1663 length: 1,
1664 }],
1665 vec![1.0],
1666 );
1667 expected.reset_hint = CounterResetHint::Unrecognized(42);
1668
1669 let array = build_histogram_array(&[Some(expected.clone())]);
1670 assert_eq!(array.data_type(), &native_histogram_arrow_type());
1671 let array = array.as_any().downcast_ref::<StructArray>().unwrap();
1672
1673 assert_eq!(read_histogram(array, 0).unwrap(), Some(expected));
1674 assert!(
1675 primitive_child::<UInt64Type>(array, COUNT_U64_FIELD)
1676 .unwrap()
1677 .is_null(0)
1678 );
1679 assert_eq!(
1680 primitive_child::<Float64Type>(array, COUNT_F64_FIELD)
1681 .unwrap()
1682 .value(0),
1683 1.0
1684 );
1685 }
1686
1687 #[test]
1688 fn add_uses_sparse_bucket_union() {
1689 let left = histogram(
1690 vec![Span {
1691 offset: 0,
1692 length: 1,
1693 }],
1694 vec![1.0],
1695 );
1696 let right = histogram(
1697 vec![Span {
1698 offset: 2,
1699 length: 1,
1700 }],
1701 vec![2.0],
1702 );
1703
1704 let result = left.add(&right).unwrap();
1705 assert_eq!(
1706 result.positive_spans,
1707 vec![
1708 Span {
1709 offset: 0,
1710 length: 1,
1711 },
1712 Span {
1713 offset: 1,
1714 length: 1,
1715 },
1716 ]
1717 );
1718 assert_eq!(result.positive_buckets, vec![1.0, 2.0]);
1719 }
1720
1721 #[test]
1722 fn add_combines_reset_hints_for_empty_payloads() {
1723 let mut empty = histogram(Vec::new(), Vec::new());
1724 empty.reset_hint = CounterResetHint::Gauge;
1725 let mut populated = histogram(
1726 vec![Span {
1727 offset: 0,
1728 length: 1,
1729 }],
1730 vec![1.0],
1731 );
1732 populated.reset_hint = CounterResetHint::NotCounterReset;
1733
1734 assert_eq!(
1735 empty.add(&populated).unwrap().reset_hint,
1736 CounterResetHint::Gauge
1737 );
1738 assert_eq!(
1739 populated.add(&empty).unwrap().reset_hint,
1740 CounterResetHint::Gauge
1741 );
1742 }
1743
1744 #[test]
1745 fn zero_threshold_expands_to_populated_bucket_boundary() {
1746 let left = NativeHistogram {
1747 zero_threshold: 0.5,
1748 ..histogram(
1749 vec![Span {
1750 offset: 0,
1751 length: 1,
1752 }],
1753 vec![1.0],
1754 )
1755 };
1756 let right = NativeHistogram {
1757 zero_threshold: 0.75,
1758 ..histogram(
1759 vec![Span {
1760 offset: 1,
1761 length: 1,
1762 }],
1763 vec![1.0],
1764 )
1765 };
1766
1767 let result = left.add(&right).unwrap();
1768 assert_eq!(result.zero_threshold, 1.0);
1769 assert_eq!(result.zero_count, 1.0);
1770 assert_eq!(result.positive_buckets, vec![1.0]);
1771 }
1772
1773 #[test]
1774 fn custom_reconciliation_without_shared_bounds_uses_overflow_bucket() {
1775 let mut left = histogram(
1776 vec![Span {
1777 offset: 0,
1778 length: 1,
1779 }],
1780 vec![1.0],
1781 );
1782 left.schema = CUSTOM_BUCKETS_SCHEMA;
1783 left.custom_values = vec![1.0];
1784
1785 let mut right = histogram(
1786 vec![Span {
1787 offset: 0,
1788 length: 1,
1789 }],
1790 vec![2.0],
1791 );
1792 right.schema = CUSTOM_BUCKETS_SCHEMA;
1793 right.custom_values = vec![2.0];
1794
1795 let result = left.add(&right).unwrap();
1796 assert!(result.custom_values.is_empty());
1797 assert_eq!(
1798 result.positive_spans,
1799 vec![Span {
1800 offset: 0,
1801 length: 1,
1802 }]
1803 );
1804 assert_eq!(result.positive_buckets, vec![3.0]);
1805 }
1806
1807 #[test]
1808 fn promql_eq_ignores_reset_hint_start_timestamp_and_sparse_zeros() {
1809 let mut left = histogram(
1810 vec![Span {
1811 offset: 0,
1812 length: 2,
1813 }],
1814 vec![1.0, 0.0],
1815 );
1816 left.reset_hint = CounterResetHint::CounterReset;
1817 left.start_timestamp = Some(1000);
1818
1819 let mut right = histogram(
1820 vec![Span {
1821 offset: 0,
1822 length: 1,
1823 }],
1824 vec![1.0],
1825 );
1826 right.reset_hint = CounterResetHint::NotCounterReset;
1827 right.start_timestamp = Some(2000);
1828
1829 assert!(left.promql_eq(&right));
1830 }
1831
1832 #[test]
1833 fn promql_eq_compares_payload_floats_by_bits() {
1834 let nan = f64::from_bits(0x7ff8_0000_0000_0001);
1835 let other_nan = f64::from_bits(0x7ff8_0000_0000_0002);
1836 let mut left = histogram(
1837 vec![Span {
1838 offset: 0,
1839 length: 1,
1840 }],
1841 vec![nan],
1842 );
1843 left.count = nan;
1844 left.zero_count = nan;
1845 left.sum = nan;
1846 let right = left.clone();
1847
1848 assert!(left.promql_eq(&right));
1849
1850 for changed in [
1851 NativeHistogram {
1852 count: other_nan,
1853 ..right.clone()
1854 },
1855 NativeHistogram {
1856 zero_count: other_nan,
1857 ..right.clone()
1858 },
1859 NativeHistogram {
1860 sum: other_nan,
1861 ..right.clone()
1862 },
1863 NativeHistogram {
1864 positive_buckets: vec![other_nan],
1865 ..right.clone()
1866 },
1867 ] {
1868 assert!(!left.promql_eq(&changed));
1869 }
1870
1871 let undecodable = histogram(
1872 vec![Span {
1873 offset: i32::MAX,
1874 length: 1,
1875 }],
1876 vec![1.0],
1877 );
1878 assert!(undecodable.promql_eq(&undecodable.clone()));
1879 }
1880
1881 #[test]
1882 fn detect_reset_preserves_layout_direction() {
1883 let mut previous = histogram(
1884 vec![Span {
1885 offset: 1,
1886 length: 1,
1887 }],
1888 vec![1.0],
1889 );
1890 previous.reset_hint = CounterResetHint::Unknown;
1891
1892 let mut higher_resolution = previous.clone();
1893 higher_resolution.schema = 1;
1894 higher_resolution.positive_spans[0].offset = 2;
1895 assert!(higher_resolution.detect_reset(&previous));
1896
1897 let mut lower_resolution = previous.clone();
1898 lower_resolution.schema = 0;
1899 lower_resolution.positive_spans[0].offset = 1;
1900 let mut previous_higher_resolution = higher_resolution;
1901 previous_higher_resolution.reset_hint = CounterResetHint::Unknown;
1902 assert!(!lower_resolution.detect_reset(&previous_higher_resolution));
1903
1904 let mut smaller_zero_threshold = previous.clone();
1905 smaller_zero_threshold.zero_threshold = 0.5;
1906 previous.zero_threshold = 1.0;
1907 assert!(smaller_zero_threshold.detect_reset(&previous));
1908
1909 let mut previous = histogram(
1910 vec![Span {
1911 offset: 0,
1912 length: 1,
1913 }],
1914 vec![1.0],
1915 );
1916 previous.reset_hint = CounterResetHint::Unknown;
1917 let mut split_bucket = histogram(Vec::new(), Vec::new());
1918 split_bucket.reset_hint = CounterResetHint::Unknown;
1919 split_bucket.count = 1.0;
1920 split_bucket.zero_count = 1.0;
1921 split_bucket.zero_threshold = 0.75;
1922 assert!(split_bucket.detect_reset(&previous));
1923
1924 split_bucket.zero_threshold = 1.0;
1925 assert!(!split_bucket.detect_reset(&previous));
1926 }
1927
1928 #[test]
1929 fn extreme_bucket_index_returns_none_instead_of_overflowing() {
1930 assert_eq!(ceil_div(i32::MIN, 2), i32::MIN / 2);
1931
1932 let mut histogram = histogram(
1933 vec![Span {
1934 offset: i32::MIN,
1935 length: 1,
1936 }],
1937 vec![1.0],
1938 );
1939 assert!(histogram.side_buckets(true).is_none());
1940
1941 histogram.schema = 1;
1942 let reduced = histogram.copy_to_schema(0).unwrap();
1943 assert_eq!(reduced.positive_spans[0].offset, i32::MIN / 2);
1944 }
1945
1946 #[test]
1947 fn terminal_exponential_bucket_uses_max_finite_bound() {
1948 for (schema, index) in [(-1, 512), (0, 1024), (2, 4096)] {
1949 assert_eq!(get_bound(index, schema, &[]), Some(f64::MAX));
1950 assert_eq!(get_bound(index + 1, schema, &[]), Some(f64::INFINITY));
1951 }
1952 }
1953
1954 #[test]
1955 fn fraction_excludes_nan_observations() {
1956 let mut histogram = histogram(
1957 vec![Span {
1958 offset: 0,
1959 length: 1,
1960 }],
1961 vec![8.0],
1962 );
1963 histogram.count = 10.0;
1964 histogram.sum = f64::NAN;
1965
1966 assert_eq!(histogram.fraction(f64::NEG_INFINITY, f64::INFINITY), 0.8);
1967 }
1968
1969 #[test]
1970 fn subtraction_returns_gauge_histogram() {
1971 let left = histogram(
1972 vec![Span {
1973 offset: 0,
1974 length: 1,
1975 }],
1976 vec![3.0],
1977 );
1978 let right = histogram(
1979 vec![Span {
1980 offset: 0,
1981 length: 1,
1982 }],
1983 vec![1.0],
1984 );
1985
1986 let result = left.sub(&right).unwrap();
1987 assert_eq!(result.reset_hint, CounterResetHint::Gauge);
1988 assert_eq!(result.positive_buckets, vec![2.0]);
1989 }
1990
1991 #[test]
1992 fn subtraction_treats_empty_left_as_zero() {
1993 let left = histogram(vec![], vec![]);
1994 let mut right = histogram(
1995 vec![Span {
1996 offset: 0,
1997 length: 1,
1998 }],
1999 vec![2.0],
2000 );
2001 right.schema = CUSTOM_BUCKETS_SCHEMA;
2002 right.custom_values = vec![1.0];
2003
2004 let result = left.sub(&right).unwrap();
2005
2006 assert_eq!(result.schema, CUSTOM_BUCKETS_SCHEMA);
2007 assert_eq!(result.reset_hint, CounterResetHint::Gauge);
2008 assert_eq!(result.count, -2.0);
2009 assert_eq!(result.sum, -2.0);
2010 assert_eq!(result.positive_buckets, vec![-2.0]);
2011 }
2012}
2013
2014pub const NATIVE_HISTOGRAM_SUBFIELD_ID_BASE: i32 = 0x5000_0000;
2034
2035pub const NATIVE_HISTOGRAM_SUBFIELD_ID_STRIDE: i32 = 64;
2038
2039pub const NATIVE_HISTOGRAM_LIST_ELEMENT_OFFSET: i32 = 32;
2041
2042pub fn native_histogram_subfield_id(column_id: i32, name: &str) -> Option<i32> {
2046 let idx = subfield_index(name)?;
2047 NATIVE_HISTOGRAM_SUBFIELD_ID_BASE
2048 .checked_add(column_id.checked_mul(NATIVE_HISTOGRAM_SUBFIELD_ID_STRIDE)?)
2049 .and_then(|v| v.checked_add(idx))
2050}
2051
2052pub fn native_histogram_list_element_id(column_id: i32, name: &str) -> Option<i32> {
2056 let idx = subfield_index(name)?;
2057 NATIVE_HISTOGRAM_SUBFIELD_ID_BASE
2058 .checked_add(column_id.checked_mul(NATIVE_HISTOGRAM_SUBFIELD_ID_STRIDE)?)
2059 .and_then(|v| v.checked_add(NATIVE_HISTOGRAM_LIST_ELEMENT_OFFSET))
2060 .and_then(|v| v.checked_add(idx))
2061}
2062
2063fn subfield_index(name: &str) -> Option<i32> {
2064 NATIVE_HISTOGRAM_FIELD_NAMES
2065 .iter()
2066 .position(|n| *n == name)
2067 .map(|i| i as i32)
2068}
2069
2070#[cfg(test)]
2071mod subfield_id_tests {
2072 use super::*;
2073
2074 #[test]
2075 fn subfield_ids_are_namespaced_and_disjoint() {
2076 let sum_idx = 2;
2078 let custom_values_idx = 5;
2079
2080 assert_eq!(
2083 native_histogram_subfield_id(1, SUM_FIELD),
2084 Some(NATIVE_HISTOGRAM_SUBFIELD_ID_BASE + NATIVE_HISTOGRAM_SUBFIELD_ID_STRIDE + sum_idx,)
2085 );
2086 assert_eq!(
2088 native_histogram_list_element_id(1, CUSTOM_VALUES_FIELD),
2089 Some(
2090 NATIVE_HISTOGRAM_SUBFIELD_ID_BASE
2091 + NATIVE_HISTOGRAM_SUBFIELD_ID_STRIDE
2092 + NATIVE_HISTOGRAM_LIST_ELEMENT_OFFSET
2093 + custom_values_idx,
2094 )
2095 );
2096 assert_ne!(
2098 native_histogram_subfield_id(1, SUM_FIELD),
2099 native_histogram_subfield_id(7, SUM_FIELD)
2100 );
2101 assert_eq!(native_histogram_subfield_id(1, "not_a_field"), None);
2103 }
2104
2105 #[test]
2106 fn subfield_ids_overflow_returns_none() {
2107 assert_eq!(native_histogram_subfield_id(12_582_912, SUM_FIELD), None);
2112 assert_eq!(
2113 native_histogram_list_element_id(12_582_912, CUSTOM_VALUES_FIELD),
2114 None
2115 );
2116 assert!(native_histogram_subfield_id(12_582_911, SUM_FIELD).is_some());
2118 }
2119}