web: Add NHCB support to federation

This simply fills the classic buckets of the histogram protobuf with
the content of the custom buckets.

Signed-off-by: beorn7 <beorn@grafana.com>
This commit is contained in:
beorn7 2025-09-19 01:19:10 +02:00
parent aac5cc3d99
commit 92f4e21080

View File

@ -190,10 +190,13 @@ Loop:
isHistogram := s.H != nil
formatType := format.FormatType()
if isHistogram &&
!s.H.UsesCustomBuckets() &&
formatType != expfmt.TypeProtoDelim &&
formatType != expfmt.TypeProtoText &&
formatType != expfmt.TypeProtoCompact {
// Can't serve the native histogram.
// Can't serve a native histogram with a non-protobuf format.
// (We can serve an NHCB, though, as it is converted to a
// classic histogram for federation.)
// TODO(codesome): Serve them when other protocols get the native histogram support.
continue
}
@ -290,32 +293,10 @@ Loop:
}
} else {
lastHistogramWasGauge = s.H.CounterResetHint == histogram.GaugeType
protMetric.Histogram = &dto.Histogram{
SampleCountFloat: proto.Float64(s.H.Count),
SampleSum: proto.Float64(s.H.Sum),
Schema: proto.Int32(s.H.Schema),
ZeroThreshold: proto.Float64(s.H.ZeroThreshold),
ZeroCountFloat: proto.Float64(s.H.ZeroCount),
NegativeCount: s.H.NegativeBuckets,
PositiveCount: s.H.PositiveBuckets,
}
if len(s.H.PositiveSpans) > 0 {
protMetric.Histogram.PositiveSpan = make([]*dto.BucketSpan, len(s.H.PositiveSpans))
for i, sp := range s.H.PositiveSpans {
protMetric.Histogram.PositiveSpan[i] = &dto.BucketSpan{
Offset: proto.Int32(sp.Offset),
Length: proto.Uint32(sp.Length),
}
}
}
if len(s.H.NegativeSpans) > 0 {
protMetric.Histogram.NegativeSpan = make([]*dto.BucketSpan, len(s.H.NegativeSpans))
for i, sp := range s.H.NegativeSpans {
protMetric.Histogram.NegativeSpan[i] = &dto.BucketSpan{
Offset: proto.Int32(sp.Offset),
Length: proto.Uint32(sp.Length),
}
}
if s.H.UsesCustomBuckets() {
protMetric.Histogram = makeClassicHistogram(s.H)
} else {
protMetric.Histogram = makeNativeHistogram(s.H)
}
}
lastWasHistogram = isHistogram
@ -329,3 +310,66 @@ Loop:
}
}
}
// makeNativeHistogram creates a dto.Histogram representing a native histogram.
// Use only for standard exponential schemal.
func makeNativeHistogram(h *histogram.FloatHistogram) *dto.Histogram {
result := &dto.Histogram{
SampleCountFloat: proto.Float64(h.Count),
SampleSum: proto.Float64(h.Sum),
Schema: proto.Int32(h.Schema),
ZeroThreshold: proto.Float64(h.ZeroThreshold),
ZeroCountFloat: proto.Float64(h.ZeroCount),
NegativeCount: h.NegativeBuckets,
PositiveCount: h.PositiveBuckets,
}
if len(h.PositiveSpans) > 0 {
result.PositiveSpan = make([]*dto.BucketSpan, len(h.PositiveSpans))
for i, sp := range h.PositiveSpans {
result.PositiveSpan[i] = &dto.BucketSpan{
Offset: proto.Int32(sp.Offset),
Length: proto.Uint32(sp.Length),
}
}
}
if len(h.NegativeSpans) > 0 {
result.NegativeSpan = make([]*dto.BucketSpan, len(h.NegativeSpans))
for i, sp := range h.NegativeSpans {
result.NegativeSpan[i] = &dto.BucketSpan{
Offset: proto.Int32(sp.Offset),
Length: proto.Uint32(sp.Length),
}
}
}
return result
}
// makeClassicHistogram creates a dto.Histogram representing a classic
// histogram. Use only for NHCB (schema -53).
func makeClassicHistogram(h *histogram.FloatHistogram) *dto.Histogram {
result := &dto.Histogram{
SampleCountFloat: proto.Float64(h.Count),
SampleSum: proto.Float64(h.Sum),
}
result.Bucket = make([]*dto.Bucket, len(h.CustomValues))
var (
cumulativeCount float64
bucketIter = h.PositiveBucketIterator()
bucketAvailable = bucketIter.Next()
)
for i, le := range h.CustomValues {
for bucketAvailable && int(bucketIter.At().Index) < i {
bucketAvailable = bucketIter.Next()
}
if bucketAvailable && int(bucketIter.At().Index) == i {
cumulativeCount += bucketIter.At().Count
}
result.Bucket[i] = &dto.Bucket{
UpperBound: proto.Float64(le),
CumulativeCountFloat: proto.Float64(cumulativeCount),
}
}
// Note that we do not add the +Inf bucket explicitly. In the protobuf
// exposition format, it is optional.
return result
}