Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .chloggen/52834-prometheus-remote-write-zero-threshold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
change_type: bug_fix
component: pkg/translator/prometheusremotewrite
note: Preserve the zero threshold when converting exponential histograms to Prometheus remote-write v1 and v2 native histograms.
issues: [52834]
change_logs: [user, api]
5 changes: 5 additions & 0 deletions .chloggen/52834-prometheus-zero-threshold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
change_type: bug_fix
component: exporter/prometheus
note: Preserve the zero threshold when converting exponential histograms to Prometheus native histograms.
issues: [52834]
change_logs: [user]
14 changes: 2 additions & 12 deletions exporter/prometheusexporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,7 @@ func (c *collector) convertMetric(metric pmetric.Metric, resourceAttrs pcommon.M
return nil, errUnknownMetricType
}

// defaultZeroThreshold matches the remote-write translator's default for native histograms
// when an explicit zero threshold is not provided in the datapoint.
const (
defaultZeroThreshold = 1e-128
cbnhScale = -53
)
const cbnhScale = -53

func bucketsToNativeMap(buckets pmetric.ExponentialHistogramDataPointBuckets, scaleDown int32) map[int]int64 {
counts := buckets.BucketCounts()
Expand Down Expand Up @@ -247,11 +242,6 @@ func (c *collector) convertExponentialHistogram(metric pmetric.Metric, resourceA
pos := bucketsToNativeMap(dp.Positive(), scaleDown)
neg := bucketsToNativeMap(dp.Negative(), scaleDown)

zeroThresh := dp.ZeroThreshold()
if zeroThresh == 0 {
zeroThresh = defaultZeroThreshold
}

// Use created timestamp if start time is set (> 0), else zero value.
created := time.Time{}
if dp.StartTimestamp().AsTime().Unix() > 0 {
Expand All @@ -266,7 +256,7 @@ func (c *collector) convertExponentialHistogram(metric pmetric.Metric, resourceA
neg,
dp.ZeroCount(),
schema,
zeroThresh,
dp.ZeroThreshold(),
created,
attributes...,
)
Expand Down
57 changes: 54 additions & 3 deletions exporter/prometheusexporter/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -907,12 +907,12 @@ func TestAccumulateExponentialHistograms(t *testing.T) {
wantZeroThresh float64
}{
{
name: "NativeHistogram basic (default zero threshold)",
name: "NativeHistogram basic (zero threshold)",
wantCount: 10,
wantSum: 42.0,
wantSchema: 0,
wantZeroCount: 4,
wantZeroThresh: defaultZeroThreshold,
wantZeroThresh: 0,
metric: func(ts time.Time, withStartTime bool) (metric pmetric.Metric) {
metric = pmetric.NewMetric()
metric.SetName("test_native_hist")
Expand All @@ -926,7 +926,7 @@ func TestAccumulateExponentialHistograms(t *testing.T) {
dp.SetZeroCount(4)
dp.SetCount(10)
dp.SetSum(42.0)
dp.SetZeroThreshold(0) // trigger defaultZeroThreshold
dp.SetZeroThreshold(0)
dp.Attributes().PutStr("label_1", "1")
dp.Attributes().PutStr("label_2", "2")
dp.SetTimestamp(pcommon.NewTimestampFromTime(ts))
Expand Down Expand Up @@ -1289,3 +1289,54 @@ func TestTargetInfoIdentifyingResourceAttributes(t *testing.T) {
})
}
}

func TestConvertExponentialHistogramZeroThreshold(t *testing.T) {
tests := []struct {
name string
threshold float64
setThreshold bool
offset int32
}{
{name: "unset threshold", offset: -432},
{name: "explicit zero threshold", setThreshold: true, offset: -432},
{name: "nonzero threshold", threshold: 0.5, setThreshold: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metric := pmetric.NewMetric()
metric.SetName("test_native_histogram")
hist := metric.SetEmptyExponentialHistogram()
hist.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
dp := hist.DataPoints().AppendEmpty()
dp.SetScale(0)
dp.SetCount(3)
dp.SetSum(0)
dp.SetZeroCount(1)
if tt.setThreshold {
dp.SetZeroThreshold(tt.threshold)
}
dp.Positive().SetOffset(tt.offset)
dp.Positive().BucketCounts().FromRaw([]uint64{1})
dp.Negative().SetOffset(tt.offset)
dp.Negative().BucketCounts().FromRaw([]uint64{1})
c := newCollector(&Config{}, zap.NewNop())
m, err := c.convertExponentialHistogram(metric, pcommon.NewMap(), "", "", "", pcommon.NewMap())
require.NoError(t, err)
out := &io_prometheus_client.Metric{}
require.NoError(t, m.Write(out))
h := out.GetHistogram()
require.Equal(t, tt.threshold, h.GetZeroThreshold())
require.Equal(t, uint64(3), h.GetSampleCount())
require.Equal(t, uint64(1), h.GetZeroCount())
require.Equal(t, 0.0, h.GetSampleSum())
require.Len(t, h.PositiveSpan, 1)
require.Len(t, h.NegativeSpan, 1)
require.Equal(t, tt.offset+1, h.PositiveSpan[0].GetOffset())
require.Equal(t, tt.offset+1, h.NegativeSpan[0].GetOffset())
require.Equal(t, uint32(1), h.PositiveSpan[0].GetLength())
require.Equal(t, uint32(1), h.NegativeSpan[0].GetLength())
require.Equal(t, []int64{1}, h.PositiveDelta)
require.Equal(t, []int64{1}, h.NegativeDelta)
})
}
}
11 changes: 2 additions & 9 deletions pkg/translator/prometheusremotewrite/histograms.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import (
"go.uber.org/multierr"
)

const defaultZeroThreshold = 1e-128

// explicitToNHCB converts an OTLP explicit-bucket histogram data point to a
// Prometheus histogram with custom buckets (schema -53) via Prometheus' own
// convertnhcb helper. Exactly one of the returned histograms is non-nil.
Expand Down Expand Up @@ -149,7 +147,8 @@ func exponentialToNativeHistogram(p pmetric.ExponentialHistogramDataPoint) (prom
ResetHint: prompb.Histogram_UNKNOWN,
Schema: scale,

ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: p.ZeroCount()},
ZeroThreshold: p.ZeroThreshold(),
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: p.ZeroCount()},

PositiveSpans: pSpans,
PositiveDeltas: pDeltas,
Expand All @@ -159,12 +158,6 @@ func exponentialToNativeHistogram(p pmetric.ExponentialHistogramDataPoint) (prom
Timestamp: convertTimeStamp(p.Timestamp()),
}

if p.ZeroThreshold() != 0 {
h.ZeroThreshold = p.ZeroThreshold()
} else {
h.ZeroThreshold = defaultZeroThreshold
}

if p.Flags().NoRecordedValue() {
h.Sum = math.Float64frombits(value.StaleNaN)
h.Count = &prompb.Histogram_CountInt{CountInt: value.StaleNaN}
Expand Down
41 changes: 34 additions & 7 deletions pkg/translator/prometheusremotewrite/histograms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,33 @@ func TestExponentialToNativeHistogram(t *testing.T) {
wantNativeHist func() prompb.Histogram
wantErrMessage string
}{
{
name: "preserve zero threshold with tiny positive and negative buckets",
exponentialHist: func() pmetric.ExponentialHistogramDataPoint {
pt := pmetric.NewExponentialHistogramDataPoint()
pt.SetScale(0)
pt.SetCount(2)
pt.SetSum(0)
pt.SetZeroCount(0)
pt.SetZeroThreshold(0)
// At scale 0, bucket -432 has upper bound 2^-431, below 1e-128.
pt.Positive().SetOffset(-432)
pt.Positive().BucketCounts().FromRaw([]uint64{1})
pt.Negative().SetOffset(-432)
pt.Negative().BucketCounts().FromRaw([]uint64{1})
return pt
},
wantNativeHist: func() prompb.Histogram {
return prompb.Histogram{
Count: &prompb.Histogram_CountInt{CountInt: 2},
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []prompb.BucketSpan{{Offset: -431, Length: 1}},
PositiveDeltas: []int64{1},
NegativeSpans: []prompb.BucketSpan{{Offset: -431, Length: 1}},
NegativeDeltas: []int64{1},
}
},
},
{
name: "convert exp. to native histogram",
exponentialHist: func() pmetric.ExponentialHistogramDataPoint {
Expand All @@ -552,7 +579,7 @@ func TestExponentialToNativeHistogram(t *testing.T) {
Count: &prompb.Histogram_CountInt{CountInt: 4},
Sum: 10.1,
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 1},
NegativeSpans: []prompb.BucketSpan{{Offset: 2, Length: 2}},
NegativeDeltas: []int64{1, 0},
Expand Down Expand Up @@ -650,7 +677,7 @@ func TestExponentialToNativeHistogram(t *testing.T) {
Count: &prompb.Histogram_CountInt{CountInt: 6},
Sum: 10.1,
Schema: 8,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 1},
PositiveSpans: []prompb.BucketSpan{{Offset: 2, Length: 3}},
PositiveDeltas: []int64{1, 0, 0}, // 1, 1, 1
Expand Down Expand Up @@ -682,7 +709,7 @@ func TestExponentialToNativeHistogram(t *testing.T) {
Count: &prompb.Histogram_CountInt{CountInt: 6},
Sum: 10.1,
Schema: 8,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 1},
PositiveSpans: []prompb.BucketSpan{{Offset: 1, Length: 2}},
PositiveDeltas: []int64{1, 1}, // 0+1, 1+1 = 1, 2
Expand Down Expand Up @@ -785,15 +812,15 @@ func TestPrometheusConverter_addExponentialHistogramDataPoints(t *testing.T) {
{
Count: &prompb.Histogram_CountInt{CountInt: 7},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []prompb.BucketSpan{{Offset: 0, Length: 2}},
PositiveDeltas: []int64{4, -2},
},
{
Count: &prompb.Histogram_CountInt{CountInt: 4},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []prompb.BucketSpan{{Offset: 0, Length: 3}},
PositiveDeltas: []int64{4, -2, -1},
Expand Down Expand Up @@ -851,7 +878,7 @@ func TestPrometheusConverter_addExponentialHistogramDataPoints(t *testing.T) {
{
Count: &prompb.Histogram_CountInt{CountInt: 7},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []prompb.BucketSpan{{Offset: 0, Length: 2}},
PositiveDeltas: []int64{4, -2},
Expand All @@ -867,7 +894,7 @@ func TestPrometheusConverter_addExponentialHistogramDataPoints(t *testing.T) {
{
Count: &prompb.Histogram_CountInt{CountInt: 4},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: 0},
NegativeSpans: []prompb.BucketSpan{{Offset: 0, Length: 3}},
NegativeDeltas: []int64{4, -2, -1},
Expand Down
7 changes: 1 addition & 6 deletions pkg/translator/prometheusremotewrite/histograms_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,6 @@ func exponentialToNativeHistogramV2(p pmetric.ExponentialHistogramDataPoint) (wr
pSpans, pDeltas := convertBucketsLayoutV2(p.Positive(), scaleDown)
nSpans, nDeltas := convertBucketsLayoutV2(p.Negative(), scaleDown)

zeroThreshold := p.ZeroThreshold()
if zeroThreshold == 0 {
zeroThreshold = defaultZeroThreshold
}

h := writev2.Histogram{
// The counter reset detection must be compatible with Prometheus to
// safely set ResetHint to NO. This is not ensured currently.
Expand All @@ -121,7 +116,7 @@ func exponentialToNativeHistogramV2(p pmetric.ExponentialHistogramDataPoint) (wr
Schema: scale,

ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: p.ZeroCount()},
ZeroThreshold: zeroThreshold,
ZeroThreshold: p.ZeroThreshold(),

PositiveSpans: pSpans,
PositiveDeltas: pDeltas,
Expand Down
41 changes: 34 additions & 7 deletions pkg/translator/prometheusremotewrite/histograms_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,33 @@ func TestExponentialToNativeHistogramV2(t *testing.T) {
wantNativeHist func() writev2.Histogram
wantErrMessage string
}{
{
name: "preserve zero threshold with tiny positive and negative buckets",
exponentialHist: func() pmetric.ExponentialHistogramDataPoint {
pt := pmetric.NewExponentialHistogramDataPoint()
pt.SetScale(0)
pt.SetCount(2)
pt.SetSum(0)
pt.SetZeroCount(0)
pt.SetZeroThreshold(0)
// At scale 0, bucket -432 has upper bound 2^-431, below 1e-128.
pt.Positive().SetOffset(-432)
pt.Positive().BucketCounts().FromRaw([]uint64{1})
pt.Negative().SetOffset(-432)
pt.Negative().BucketCounts().FromRaw([]uint64{1})
return pt
},
wantNativeHist: func() writev2.Histogram {
return writev2.Histogram{
Count: &writev2.Histogram_CountInt{CountInt: 2},
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []writev2.BucketSpan{{Offset: -431, Length: 1}},
PositiveDeltas: []int64{1},
NegativeSpans: []writev2.BucketSpan{{Offset: -431, Length: 1}},
NegativeDeltas: []int64{1},
}
},
},
{
name: "convert exp. to native histogram",
exponentialHist: func() pmetric.ExponentialHistogramDataPoint {
Expand All @@ -577,7 +604,7 @@ func TestExponentialToNativeHistogramV2(t *testing.T) {
Count: &writev2.Histogram_CountInt{CountInt: 4},
Sum: 10.1,
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 1},
NegativeSpans: []writev2.BucketSpan{{Offset: 2, Length: 2}},
NegativeDeltas: []int64{1, 0},
Expand Down Expand Up @@ -641,7 +668,7 @@ func TestExponentialToNativeHistogramV2(t *testing.T) {
Count: &writev2.Histogram_CountInt{CountInt: 6},
Sum: 10.1,
Schema: 8,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 1},
PositiveSpans: []writev2.BucketSpan{{Offset: 2, Length: 3}},
PositiveDeltas: []int64{1, 0, 0}, // 1, 1, 1
Expand Down Expand Up @@ -673,7 +700,7 @@ func TestExponentialToNativeHistogramV2(t *testing.T) {
Count: &writev2.Histogram_CountInt{CountInt: 6},
Sum: 10.1,
Schema: 8,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 1},
PositiveSpans: []writev2.BucketSpan{{Offset: 1, Length: 2}},
PositiveDeltas: []int64{1, 1}, // 0+1, 1+1 = 1, 2
Expand Down Expand Up @@ -806,15 +833,15 @@ func TestPrometheusConverterV2_addExponentialHistogramDataPoints(t *testing.T) {
{
Count: &writev2.Histogram_CountInt{CountInt: 7},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []writev2.BucketSpan{{Offset: 0, Length: 2}},
PositiveDeltas: []int64{4, -2},
},
{
Count: &writev2.Histogram_CountInt{CountInt: 4},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []writev2.BucketSpan{{Offset: 0, Length: 3}},
PositiveDeltas: []int64{4, -2, -1},
Expand Down Expand Up @@ -875,7 +902,7 @@ func TestPrometheusConverterV2_addExponentialHistogramDataPoints(t *testing.T) {
{
Count: &writev2.Histogram_CountInt{CountInt: 7},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 0},
PositiveSpans: []writev2.BucketSpan{{Offset: 0, Length: 2}},
PositiveDeltas: []int64{4, -2},
Expand All @@ -894,7 +921,7 @@ func TestPrometheusConverterV2_addExponentialHistogramDataPoints(t *testing.T) {
{
Count: &writev2.Histogram_CountInt{CountInt: 4},
Schema: 1,
ZeroThreshold: defaultZeroThreshold,
ZeroThreshold: 0,
ZeroCount: &writev2.Histogram_ZeroCountInt{ZeroCountInt: 0},
NegativeSpans: []writev2.BucketSpan{{Offset: 0, Length: 3}},
NegativeDeltas: []int64{4, -2, -1},
Expand Down
Loading