77 "context"
88 "errors"
99 "fmt"
10+ "math"
1011 "net/http"
1112 "strings"
1213 "sync"
@@ -652,7 +653,20 @@ func (prw *prometheusRemoteWriteReceiver) addExponentialHistogramDatapoint(datap
652653 dp .SetZeroThreshold (histogram .ZeroThreshold )
653654
654655 // Set count and sum using common helper
655- setCountAndSum (histogram , dp )
656+ if value .IsStaleNaN (histogram .Sum ) {
657+ dp .SetFlags (pmetric .DefaultDataPointFlags .WithNoRecordedValue (true ))
658+ } else {
659+ setCountAndSum (histogram , dp )
660+ }
661+
662+ // The maximum bucket index is derived from the formula (2**2**-n)**i <= MaxFloat64.
663+ // MaxFloat64 is approx 2^1024. So (2^-n) * i <= 1024 => i <= 1024 * 2^n.
664+ // The bucket containing MaxFloat64 has index i_max = 1024 * 2^n.
665+ // The next bucket (i_max + 1) is the +Inf overflow bucket, which is also allowed.
666+ // Buckets beyond that must be dropped.
667+ // See https://prometheus.io/docs/specs/native_histograms/#schema for more information.
668+ overflowLimit := 1024 * math .Pow (2 , float64 (histogram .Schema )) + 1
669+ var droppedCount uint64
656670
657671 // The difference between float and integer histograms is that float histograms are stored as absolute counts
658672 // while integer histograms are stored as deltas.
@@ -663,11 +677,11 @@ func (prw *prometheusRemoteWriteReceiver) addExponentialHistogramDatapoint(datap
663677
664678 if len (histogram .PositiveSpans ) > 0 {
665679 dp .Positive ().SetOffset (histogram .PositiveSpans [0 ].Offset - 1 ) // -1 because OTEL offset are for the lower bound, not the upper bound
666- convertAbsoluteBuckets (histogram .PositiveSpans , histogram .PositiveCounts , dp .Positive ().BucketCounts ())
680+ droppedCount += convertAbsoluteBuckets (histogram .PositiveSpans , histogram .PositiveCounts , dp .Positive ().BucketCounts (), overflowLimit )
667681 }
668682 if len (histogram .NegativeSpans ) > 0 {
669683 dp .Negative ().SetOffset (histogram .NegativeSpans [0 ].Offset - 1 ) // -1 because OTEL offset are for the lower bound, not the upper bound
670- convertAbsoluteBuckets (histogram .NegativeSpans , histogram .NegativeCounts , dp .Negative ().BucketCounts ())
684+ droppedCount += convertAbsoluteBuckets (histogram .NegativeSpans , histogram .NegativeCounts , dp .Negative ().BucketCounts (), overflowLimit )
671685 }
672686 } else {
673687 // Integer histograms
@@ -676,14 +690,18 @@ func (prw *prometheusRemoteWriteReceiver) addExponentialHistogramDatapoint(datap
676690
677691 if len (histogram .PositiveSpans ) > 0 {
678692 dp .Positive ().SetOffset (histogram .PositiveSpans [0 ].Offset - 1 ) // -1 because OTEL offset are for the lower bound, not the upper bound
679- convertDeltaBuckets (histogram .PositiveSpans , histogram .PositiveDeltas , dp .Positive ().BucketCounts ())
693+ droppedCount += convertDeltaBuckets (histogram .PositiveSpans , histogram .PositiveDeltas , dp .Positive ().BucketCounts (), overflowLimit )
680694 }
681695 if len (histogram .NegativeSpans ) > 0 {
682696 dp .Negative ().SetOffset (histogram .NegativeSpans [0 ].Offset - 1 ) // -1 because OTEL offset are for the lower bound, not the upper bound
683- convertDeltaBuckets (histogram .NegativeSpans , histogram .NegativeDeltas , dp .Negative ().BucketCounts ())
697+ droppedCount += convertDeltaBuckets (histogram .NegativeSpans , histogram .NegativeDeltas , dp .Negative ().BucketCounts (), overflowLimit )
684698 }
685699 }
686700
701+ if droppedCount > 0 && ! value .IsStaleNaN (histogram .Sum ) {
702+ dp .SetCount (dp .Count () - droppedCount )
703+ }
704+
687705 attrs .CopyTo (dp .Attributes ())
688706 stats .Histograms ++
689707}
@@ -738,7 +756,7 @@ func hasNegativeCounts(histogram *writev2.Histogram) bool {
738756
739757// convertDeltaBuckets converts Prometheus native histogram spans and deltas to OpenTelemetry bucket counts
740758// For integer buckets, the values are deltas between the buckets. i.e a bucket list of 1,2,-2 would correspond to a bucket count of 1,3,1
741- func convertDeltaBuckets (spans []writev2.BucketSpan , deltas []int64 , buckets pcommon.UInt64Slice ) {
759+ func convertDeltaBuckets (spans []writev2.BucketSpan , deltas []int64 , buckets pcommon.UInt64Slice , overflowLimit float64 ) uint64 {
742760 // The total capacity is the sum of the deltas and the offsets of the spans.
743761 totalCapacity := len (deltas )
744762 for _ , span := range spans {
@@ -748,23 +766,37 @@ func convertDeltaBuckets(spans []writev2.BucketSpan, deltas []int64, buckets pco
748766
749767 bucketIdx := 0
750768 bucketCount := int64 (0 )
769+ var droppedCount uint64
770+ initialOffset := spans [0 ].Offset
771+ k := initialOffset
772+
751773 for spanIdx , span := range spans {
752774 if spanIdx > 0 {
753775 for i := int32 (0 ); i < span .Offset ; i ++ {
754- buckets .Append (uint64 (0 ))
776+ if float64 (k ) <= overflowLimit {
777+ buckets .Append (uint64 (0 ))
778+ }
779+ k ++
755780 }
756781 }
757782 for i := uint32 (0 ); i < span .Length ; i ++ {
758783 bucketCount += deltas [bucketIdx ]
759784 bucketIdx ++
760- buckets .Append (uint64 (bucketCount ))
785+
786+ if float64 (k ) <= overflowLimit {
787+ buckets .Append (uint64 (bucketCount ))
788+ } else {
789+ droppedCount += uint64 (bucketCount )
790+ }
791+ k ++
761792 }
762793 }
794+ return droppedCount
763795}
764796
765797// convertAbsoluteBuckets converts Prometheus native histogram spans and absolute counts to OpenTelemetry bucket counts
766798// For float buckets, the values are absolute counts, and must be 0 or positive.
767- func convertAbsoluteBuckets (spans []writev2.BucketSpan , counts []float64 , buckets pcommon.UInt64Slice ) {
799+ func convertAbsoluteBuckets (spans []writev2.BucketSpan , counts []float64 , buckets pcommon.UInt64Slice , overflowLimit float64 ) uint64 {
768800 // The total capacity is the sum of the counts and the offsets of the spans.
769801 totalCapacity := len (counts )
770802 for _ , span := range spans {
@@ -773,17 +805,30 @@ func convertAbsoluteBuckets(spans []writev2.BucketSpan, counts []float64, bucket
773805 buckets .EnsureCapacity (totalCapacity )
774806
775807 bucketIdx := 0
808+ var droppedCount uint64
809+ initialOffset := spans [0 ].Offset
810+ k := initialOffset
811+
776812 for spanIdx , span := range spans {
777813 if spanIdx > 0 {
778814 for i := int32 (0 ); i < span .Offset ; i ++ {
779- buckets .Append (uint64 (0 ))
815+ if float64 (k ) <= overflowLimit {
816+ buckets .Append (uint64 (0 ))
817+ }
818+ k ++
780819 }
781820 }
782821 for i := uint32 (0 ); i < span .Length ; i ++ {
783- buckets .Append (uint64 (counts [bucketIdx ]))
822+ if float64 (k ) <= overflowLimit {
823+ buckets .Append (uint64 (counts [bucketIdx ]))
824+ } else {
825+ droppedCount += uint64 (counts [bucketIdx ])
826+ }
784827 bucketIdx ++
828+ k ++
785829 }
786830 }
831+ return droppedCount
787832}
788833
789834// extractAttributes return all attributes different from job, instance, metric name and scope name/version
0 commit comments