mirror of
https://github.com/prometheus/prometheus.git
synced 2026-05-04 20:06:12 +02:00
refactor(histogram): Converting to Absolute values and fixing the test
Signed-off-by: Naman-B-Parlecha <namanparlecha@gmail.com>
This commit is contained in:
parent
5eeba3638d
commit
73904b4c75
@ -21,12 +21,14 @@ import (
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
)
|
||||
|
||||
// BucketEmitter is a callback function type for emitting histogram bucket series.
|
||||
// Used in remote write to append converted bucket time series.
|
||||
type BucketEmitter func(labels labels.Labels, value float64) error
|
||||
|
||||
// ConvertNHCBToClassicHistogram converts Native Histogram Custom Buckets (NHCB) to classic histogram series.
|
||||
// This conversion is needed in various scenarios where users need to get NHCB back to classic histogram format,
|
||||
// such as Remote Write v1 for external system compatibility and migration use cases.
|
||||
func ConvertNHCBToClassicHistogram(nhcb interface{}, labels labels.Labels, lblBuilder *labels.Builder, bucketSeries BucketEmitter) error {
|
||||
func ConvertNHCBToClassicHistogram(nhcb any, labels labels.Labels, lblBuilder *labels.Builder, bucketSeries BucketEmitter) error {
|
||||
baseName := labels.Get("__name__")
|
||||
if baseName == "" {
|
||||
return errors.New("metric name label '__name__' is missing")
|
||||
@ -46,7 +48,11 @@ func ConvertNHCBToClassicHistogram(nhcb interface{}, labels labels.Labels, lblBu
|
||||
customValues = h.CustomValues
|
||||
positiveBuckets = make([]float64, len(h.PositiveBuckets))
|
||||
for i, v := range h.PositiveBuckets {
|
||||
positiveBuckets[i] = float64(v)
|
||||
if i == 0 {
|
||||
positiveBuckets[i] = float64(v)
|
||||
} else {
|
||||
positiveBuckets[i] = float64(v) + positiveBuckets[i-1]
|
||||
}
|
||||
}
|
||||
count = float64(h.Count)
|
||||
sum = h.Sum
|
||||
@ -59,13 +65,16 @@ func ConvertNHCBToClassicHistogram(nhcb interface{}, labels labels.Labels, lblBu
|
||||
return errors.New("unsupported histogram type")
|
||||
}
|
||||
|
||||
// Each customValue corresponds to a positive bucket (aligned with the "le" label).
|
||||
// The lengths of customValues and positiveBuckets must match to avoid inconsistencies
|
||||
// while mapping bucket counts to their upper bounds.
|
||||
if len(customValues) != len(positiveBuckets) {
|
||||
return errors.New("mismatched lengths of custom values and positive buckets")
|
||||
}
|
||||
|
||||
currCount := float64(0)
|
||||
for i := range customValues {
|
||||
currCount += positiveBuckets[i]
|
||||
currCount = positiveBuckets[i]
|
||||
lblBuilder.Reset(labels)
|
||||
lblBuilder.Set("__name__", baseName+"_bucket")
|
||||
lblBuilder.Set("le", fmt.Sprintf("%g", customValues[i]))
|
||||
|
||||
@ -14,57 +14,71 @@
|
||||
package histogram
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type BucketExpectation struct {
|
||||
le string
|
||||
val float64
|
||||
}
|
||||
|
||||
type ExpectedHistogram struct {
|
||||
buckets []BucketExpectation
|
||||
count float64
|
||||
sum float64
|
||||
}
|
||||
|
||||
func TestConvertNHCBToClassicHistogram(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nhcb interface{}
|
||||
labels labels.Labels
|
||||
expectErr bool
|
||||
expectedLabels []labels.Labels
|
||||
expectedValues []float64
|
||||
name string
|
||||
nhcb any
|
||||
labels labels.Labels
|
||||
expectErr bool
|
||||
expected ExpectedHistogram
|
||||
}{
|
||||
{
|
||||
name: "Valid Histogram",
|
||||
nhcb: &Histogram{
|
||||
CustomValues: []float64{1, 2, 3},
|
||||
PositiveBuckets: []int64{10, 20, 30},
|
||||
PositiveBuckets: []int64{10, 20, 30}, // Delta format: {10, 20, 30} -> Absolute: {10, 30, 60}
|
||||
Count: 60,
|
||||
Sum: 100.0,
|
||||
},
|
||||
labels: labels.FromStrings("__name__", "test_metric"),
|
||||
expectedLabels: []labels.Labels{
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "1"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "2"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "3"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "+Inf"),
|
||||
labels.FromStrings("__name__", "test_metric_count"),
|
||||
labels.FromStrings("__name__", "test_metric_sum"),
|
||||
expected: ExpectedHistogram{
|
||||
buckets: []BucketExpectation{
|
||||
{le: "1", val: 10},
|
||||
{le: "2", val: 30},
|
||||
{le: "3", val: 60},
|
||||
{le: "+Inf", val: 60},
|
||||
},
|
||||
count: 60,
|
||||
sum: 100,
|
||||
},
|
||||
expectedValues: []float64{10, 30, 60, 60, 60, 100},
|
||||
},
|
||||
{
|
||||
name: "Valid FloatHistogram",
|
||||
nhcb: &FloatHistogram{
|
||||
CustomValues: []float64{1, 2, 3},
|
||||
PositiveBuckets: []float64{20.0, 40.0, 60.0},
|
||||
Count: 120.0,
|
||||
Count: 60.0,
|
||||
Sum: 100.0,
|
||||
},
|
||||
labels: labels.FromStrings("__name__", "test_metric"),
|
||||
expectedLabels: []labels.Labels{
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "1"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "2"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "3"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "+Inf"),
|
||||
labels.FromStrings("__name__", "test_metric_count"),
|
||||
labels.FromStrings("__name__", "test_metric_sum"),
|
||||
expected: ExpectedHistogram{
|
||||
buckets: []BucketExpectation{
|
||||
{le: "1", val: 20},
|
||||
{le: "2", val: 40},
|
||||
{le: "3", val: 60},
|
||||
{le: "+Inf", val: 60},
|
||||
},
|
||||
count: 60,
|
||||
sum: 100,
|
||||
},
|
||||
expectedValues: []float64{20, 60, 120, 120, 120, 100},
|
||||
},
|
||||
{
|
||||
name: "Empty Histogram",
|
||||
@ -75,18 +89,19 @@ func TestConvertNHCBToClassicHistogram(t *testing.T) {
|
||||
Sum: 0.0,
|
||||
},
|
||||
labels: labels.FromStrings("__name__", "test_metric"),
|
||||
expectedLabels: []labels.Labels{
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "+Inf"),
|
||||
labels.FromStrings("__name__", "test_metric_count"),
|
||||
labels.FromStrings("__name__", "test_metric_sum"),
|
||||
expected: ExpectedHistogram{
|
||||
buckets: []BucketExpectation{
|
||||
{le: "+Inf", val: 0},
|
||||
},
|
||||
count: 0,
|
||||
sum: 0,
|
||||
},
|
||||
expectedValues: []float64{0, 0, 0},
|
||||
},
|
||||
{
|
||||
name: "Missing __name__ label",
|
||||
nhcb: &Histogram{
|
||||
CustomValues: []float64{1, 2, 3},
|
||||
PositiveBuckets: []int64{10, 20, 30},
|
||||
PositiveBuckets: []int64{10, 20, 30}, // Delta format: {10, 20, 30} -> Absolute: {10, 30, 60}
|
||||
Count: 60,
|
||||
Sum: 100.0,
|
||||
},
|
||||
@ -103,26 +118,27 @@ func TestConvertNHCBToClassicHistogram(t *testing.T) {
|
||||
name: "Histogram with zero bucket counts",
|
||||
nhcb: &Histogram{
|
||||
CustomValues: []float64{1, 2, 3},
|
||||
PositiveBuckets: []int64{0, 10, 0},
|
||||
PositiveBuckets: []int64{0, 10, 0}, // Delta format: {0, 10, 0} -> Absolute: {0, 10, 10}
|
||||
Count: 10,
|
||||
Sum: 50.0,
|
||||
},
|
||||
labels: labels.FromStrings("__name__", "test_metric"),
|
||||
expectedLabels: []labels.Labels{
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "1"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "2"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "3"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "+Inf"),
|
||||
labels.FromStrings("__name__", "test_metric_count"),
|
||||
labels.FromStrings("__name__", "test_metric_sum"),
|
||||
expected: ExpectedHistogram{
|
||||
buckets: []BucketExpectation{
|
||||
{le: "1", val: 0},
|
||||
{le: "2", val: 10},
|
||||
{le: "3", val: 10},
|
||||
{le: "+Inf", val: 10},
|
||||
},
|
||||
count: 10,
|
||||
sum: 50,
|
||||
},
|
||||
expectedValues: []float64{0, 10, 10, 10, 10, 50},
|
||||
},
|
||||
{
|
||||
name: "Mismatched bucket lengths",
|
||||
nhcb: &Histogram{
|
||||
CustomValues: []float64{1, 2},
|
||||
PositiveBuckets: []int64{10, 20, 30},
|
||||
PositiveBuckets: []int64{10, 20, 30}, // Mismatched lengths: 2 vs 3
|
||||
Count: 60,
|
||||
Sum: 100.0,
|
||||
},
|
||||
@ -133,51 +149,50 @@ func TestConvertNHCBToClassicHistogram(t *testing.T) {
|
||||
name: "single series Histogram",
|
||||
nhcb: &Histogram{
|
||||
CustomValues: []float64{1},
|
||||
PositiveBuckets: []int64{10},
|
||||
PositiveBuckets: []int64{10}, // Delta format: {10} -> Absolute: {10}
|
||||
Count: 10,
|
||||
Sum: 20.0,
|
||||
},
|
||||
labels: labels.FromStrings("__name__", "test_metric"),
|
||||
expectedLabels: []labels.Labels{
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "1"),
|
||||
labels.FromStrings("__name__", "test_metric_bucket", "le", "+Inf"),
|
||||
labels.FromStrings("__name__", "test_metric_count"),
|
||||
labels.FromStrings("__name__", "test_metric_sum"),
|
||||
expected: ExpectedHistogram{
|
||||
buckets: []BucketExpectation{
|
||||
{le: "1", val: 10},
|
||||
{le: "+Inf", val: 10},
|
||||
},
|
||||
count: 10,
|
||||
sum: 20,
|
||||
},
|
||||
expectedValues: []float64{10, 10, 10, 20},
|
||||
},
|
||||
}
|
||||
|
||||
labelBuilder := labels.NewBuilder(labels.EmptyLabels())
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var actualLabels []labels.Labels
|
||||
var actualValues []float64
|
||||
|
||||
err := ConvertNHCBToClassicHistogram(tt.nhcb, tt.labels, &labels.Builder{}, func(lbls labels.Labels, value float64) error {
|
||||
actualLabels = append(actualLabels, lbls)
|
||||
actualValues = append(actualValues, value)
|
||||
var got ExpectedHistogram
|
||||
err := ConvertNHCBToClassicHistogram(tt.nhcb, tt.labels, labelBuilder, func(lbls labels.Labels, val float64) error {
|
||||
switch lbls.Get("__name__") {
|
||||
case tt.labels.Get("__name__") + "_bucket":
|
||||
got.buckets = append(got.buckets, BucketExpectation{
|
||||
le: lbls.Get("le"),
|
||||
val: val,
|
||||
})
|
||||
case tt.labels.Get("__name__") + "_count":
|
||||
got.count = val
|
||||
case tt.labels.Get("__name__") + "_sum":
|
||||
got.sum = val
|
||||
default:
|
||||
return errors.New("unexpected metric name")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if (err != nil) != tt.expectErr {
|
||||
t.Errorf("ConvertNHCBToClassicHistogram() error = %v, expectErr %v", err, tt.expectErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, tt.expectErr, err != nil, "unexpected error: %v", err)
|
||||
if !tt.expectErr {
|
||||
if len(actualLabels) != len(tt.expectedLabels) {
|
||||
t.Errorf("Expected %d emissions, got %d", len(tt.expectedLabels), len(actualLabels))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expectedLabel := range tt.expectedLabels {
|
||||
if !labels.Equal(actualLabels[i], expectedLabel) {
|
||||
t.Errorf("Expected label[%d] = %v, got %v", i, expectedLabel, actualLabels[i])
|
||||
}
|
||||
if actualValues[i] != tt.expectedValues[i] {
|
||||
t.Errorf("Expected value[%d] = %f, got %f", i, tt.expectedValues[i], actualValues[i])
|
||||
}
|
||||
require.Equal(t, len(tt.expected.buckets), len(got.buckets))
|
||||
for i, expBucket := range tt.expected.buckets {
|
||||
require.Equal(t, expBucket, got.buckets[i])
|
||||
}
|
||||
require.Equal(t, tt.expected.count, got.count)
|
||||
require.Equal(t, tt.expected.sum, got.sum)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user