prometheus/storage/remote/otlptranslator/prometheusremotewrite/helper_test.go
Charlie Le d87f7440ca support int exemplar value type
When the exemplar type is an int, it incorrectly gets converted to a 0
when DoubleValue() is called on the exemplar. This adds a check to
ensure that the value is converted properly based on the type.

Signed-off-by: Charlie Le <charlie_le@apple.com>
2024-10-25 08:47:54 -07:00

444 lines
12 KiB
Go

// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Provenance-includes-location: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/debbf30360b8d3a0ded8db09c4419d2a9c99b94a/pkg/translator/prometheusremotewrite/helper_test.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: Copyright The OpenTelemetry Authors.
package prometheusremotewrite
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/prompb"
)
func TestCreateAttributes(t *testing.T) {
resourceAttrs := map[string]string{
"service.name": "service name",
"service.instance.id": "service ID",
"existent-attr": "resource value",
// This one is for testing conflict with metric attribute.
"metric-attr": "resource value",
// This one is for testing conflict with auto-generated job attribute.
"job": "resource value",
// This one is for testing conflict with auto-generated instance attribute.
"instance": "resource value",
}
resource := pcommon.NewResource()
for k, v := range resourceAttrs {
resource.Attributes().PutStr(k, v)
}
attrs := pcommon.NewMap()
attrs.PutStr("__name__", "test_metric")
attrs.PutStr("metric-attr", "metric value")
testCases := []struct {
name string
promoteResourceAttributes []string
expectedLabels []prompb.Label
}{
{
name: "Successful conversion without resource attribute promotion",
promoteResourceAttributes: nil,
expectedLabels: []prompb.Label{
{
Name: "__name__",
Value: "test_metric",
},
{
Name: "instance",
Value: "service ID",
},
{
Name: "job",
Value: "service name",
},
{
Name: "metric_attr",
Value: "metric value",
},
},
},
{
name: "Successful conversion with resource attribute promotion",
promoteResourceAttributes: []string{"non-existent-attr", "existent-attr"},
expectedLabels: []prompb.Label{
{
Name: "__name__",
Value: "test_metric",
},
{
Name: "instance",
Value: "service ID",
},
{
Name: "job",
Value: "service name",
},
{
Name: "metric_attr",
Value: "metric value",
},
{
Name: "existent_attr",
Value: "resource value",
},
},
},
{
name: "Successful conversion with resource attribute promotion, conflicting resource attributes are ignored",
promoteResourceAttributes: []string{"non-existent-attr", "existent-attr", "metric-attr", "job", "instance"},
expectedLabels: []prompb.Label{
{
Name: "__name__",
Value: "test_metric",
},
{
Name: "instance",
Value: "service ID",
},
{
Name: "job",
Value: "service name",
},
{
Name: "existent_attr",
Value: "resource value",
},
{
Name: "metric_attr",
Value: "metric value",
},
},
},
{
name: "Successful conversion with resource attribute promotion, attributes are only promoted once",
promoteResourceAttributes: []string{"existent-attr", "existent-attr"},
expectedLabels: []prompb.Label{
{
Name: "__name__",
Value: "test_metric",
},
{
Name: "instance",
Value: "service ID",
},
{
Name: "job",
Value: "service name",
},
{
Name: "existent_attr",
Value: "resource value",
},
{
Name: "metric_attr",
Value: "metric value",
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
settings := Settings{
PromoteResourceAttributes: tc.promoteResourceAttributes,
}
lbls := createAttributes(resource, attrs, settings, nil, false)
assert.ElementsMatch(t, lbls, tc.expectedLabels)
})
}
}
func Test_convertTimeStamp(t *testing.T) {
tests := []struct {
name string
arg pcommon.Timestamp
want int64
}{
{"zero", 0, 0},
{"1ms", 1_000_000, 1},
{"1s", pcommon.Timestamp(time.Unix(1, 0).UnixNano()), 1000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := convertTimeStamp(tt.arg)
assert.Equal(t, tt.want, got)
})
}
}
func TestPrometheusConverter_AddSummaryDataPoints(t *testing.T) {
ts := pcommon.Timestamp(time.Now().UnixNano())
tests := []struct {
name string
metric func() pmetric.Metric
want func() map[uint64]*prompb.TimeSeries
}{
{
name: "summary with start time",
metric: func() pmetric.Metric {
metric := pmetric.NewMetric()
metric.SetName("test_summary")
metric.SetEmptySummary()
dp := metric.Summary().DataPoints().AppendEmpty()
dp.SetTimestamp(ts)
dp.SetStartTimestamp(ts)
return metric
},
want: func() map[uint64]*prompb.TimeSeries {
labels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_summary" + countStr},
}
createdLabels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_summary" + createdSuffix},
}
sumLabels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_summary" + sumStr},
}
return map[uint64]*prompb.TimeSeries{
timeSeriesSignature(labels): {
Labels: labels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
timeSeriesSignature(sumLabels): {
Labels: sumLabels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
timeSeriesSignature(createdLabels): {
Labels: createdLabels,
Samples: []prompb.Sample{
{Value: float64(convertTimeStamp(ts)), Timestamp: convertTimeStamp(ts)},
},
},
}
},
},
{
name: "summary without start time",
metric: func() pmetric.Metric {
metric := pmetric.NewMetric()
metric.SetName("test_summary")
metric.SetEmptySummary()
dp := metric.Summary().DataPoints().AppendEmpty()
dp.SetTimestamp(ts)
return metric
},
want: func() map[uint64]*prompb.TimeSeries {
labels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_summary" + countStr},
}
sumLabels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_summary" + sumStr},
}
return map[uint64]*prompb.TimeSeries{
timeSeriesSignature(labels): {
Labels: labels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
timeSeriesSignature(sumLabels): {
Labels: sumLabels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metric := tt.metric()
converter := NewPrometheusConverter()
converter.addSummaryDataPoints(
context.Background(),
metric.Summary().DataPoints(),
pcommon.NewResource(),
Settings{
ExportCreatedMetric: true,
},
metric.Name(),
)
assert.Equal(t, tt.want(), converter.unique)
assert.Empty(t, converter.conflicts)
})
}
}
func TestPrometheusConverter_AddHistogramDataPoints(t *testing.T) {
ts := pcommon.Timestamp(time.Now().UnixNano())
tests := []struct {
name string
metric func() pmetric.Metric
want func() map[uint64]*prompb.TimeSeries
}{
{
name: "histogram with start time",
metric: func() pmetric.Metric {
metric := pmetric.NewMetric()
metric.SetName("test_hist")
metric.SetEmptyHistogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
pt := metric.Histogram().DataPoints().AppendEmpty()
pt.SetTimestamp(ts)
pt.SetStartTimestamp(ts)
return metric
},
want: func() map[uint64]*prompb.TimeSeries {
labels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_hist" + countStr},
}
createdLabels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_hist" + createdSuffix},
}
infLabels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_hist_bucket"},
{Name: model.BucketLabel, Value: "+Inf"},
}
return map[uint64]*prompb.TimeSeries{
timeSeriesSignature(infLabels): {
Labels: infLabels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
timeSeriesSignature(labels): {
Labels: labels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
timeSeriesSignature(createdLabels): {
Labels: createdLabels,
Samples: []prompb.Sample{
{Value: float64(convertTimeStamp(ts)), Timestamp: convertTimeStamp(ts)},
},
},
}
},
},
{
name: "histogram without start time",
metric: func() pmetric.Metric {
metric := pmetric.NewMetric()
metric.SetName("test_hist")
metric.SetEmptyHistogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
pt := metric.Histogram().DataPoints().AppendEmpty()
pt.SetTimestamp(ts)
return metric
},
want: func() map[uint64]*prompb.TimeSeries {
labels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_hist" + countStr},
}
infLabels := []prompb.Label{
{Name: model.MetricNameLabel, Value: "test_hist_bucket"},
{Name: model.BucketLabel, Value: "+Inf"},
}
return map[uint64]*prompb.TimeSeries{
timeSeriesSignature(infLabels): {
Labels: infLabels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
timeSeriesSignature(labels): {
Labels: labels,
Samples: []prompb.Sample{
{Value: 0, Timestamp: convertTimeStamp(ts)},
},
},
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metric := tt.metric()
converter := NewPrometheusConverter()
converter.addHistogramDataPoints(
context.Background(),
metric.Histogram().DataPoints(),
pcommon.NewResource(),
Settings{
ExportCreatedMetric: true,
},
metric.Name(),
)
assert.Equal(t, tt.want(), converter.unique)
assert.Empty(t, converter.conflicts)
})
}
}
func TestGetPromExemplars(t *testing.T) {
ctx := context.Background()
everyN := &everyNTimes{n: 1}
t.Run("Exemplars with int value", func(t *testing.T) {
pt := pmetric.NewNumberDataPoint()
exemplar := pt.Exemplars().AppendEmpty()
exemplar.SetTimestamp(pcommon.Timestamp(time.Now().UnixNano()))
exemplar.SetIntValue(42)
exemplars, err := getPromExemplars(ctx, everyN, pt)
assert.NoError(t, err)
assert.Len(t, exemplars, 1)
assert.Equal(t, float64(42), exemplars[0].Value)
})
t.Run("Exemplars with double value", func(t *testing.T) {
pt := pmetric.NewNumberDataPoint()
exemplar := pt.Exemplars().AppendEmpty()
exemplar.SetTimestamp(pcommon.Timestamp(time.Now().UnixNano()))
exemplar.SetDoubleValue(69.420)
exemplars, err := getPromExemplars(ctx, everyN, pt)
assert.NoError(t, err)
assert.Len(t, exemplars, 1)
assert.Equal(t, 69.420, exemplars[0].Value)
})
t.Run("Exemplars with unsupported value type", func(t *testing.T) {
pt := pmetric.NewNumberDataPoint()
exemplar := pt.Exemplars().AppendEmpty()
exemplar.SetTimestamp(pcommon.Timestamp(time.Now().UnixNano()))
_, err := getPromExemplars(ctx, everyN, pt)
assert.Error(t, err)
})
}