From 328462ce4fa729c2103d3f42a8bf901f4323545e Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Thu, 10 Mar 2022 20:58:26 +0000 Subject: [PATCH 1/2] support sending trace id and span id in the PRW exporter --- CHANGELOG.md | 1 + .../prometheusremotewrite/helper.go | 33 ++++++++++++++++--- .../prometheusremotewrite/helper_test.go | 30 ++++++++++++++--- .../prometheusremotewrite/testutils_test.go | 29 ++++++++++++++-- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 744eb83a0f98..e024c41b2e38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - `googlecloudpubsubreceiver` Added implementation of Google Cloud Pubsub receiver. (#8391) - `googlecloudpubsubexporter` Added implementation of Google Cloud Pubsub exporter. (#8391) - `coralogixexporter` Allow exporter timeout to be configured (#7957) +- `prometheusremotewriteexporter` support adding trace id and span id attached to exemplars (#8380) ### đŸ›‘ Breaking changes đŸ›‘ diff --git a/pkg/translator/prometheusremotewrite/helper.go b/pkg/translator/prometheusremotewrite/helper.go index 7501df25cbb2..346567737350 100644 --- a/pkg/translator/prometheusremotewrite/helper.go +++ b/pkg/translator/prometheusremotewrite/helper.go @@ -46,6 +46,10 @@ const ( // according to the prometheus specification // https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars maxExemplarRunes = 128 + // Trace and Span id keys are defined as part of the spec: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification%2Fmetrics%2Fdatamodel.md#exemplars-2 + traceIDKey = "trace_id" + spanIDKey = "span_id" ) type bucketBoundsData struct { @@ -357,13 +361,32 @@ func getPromExemplars(pt pdata.HistogramDataPoint) []prompb.Exemplar { for i := 0; i < pt.Exemplars().Len(); i++ { exemplar := pt.Exemplars().At(i) + exemplarRunes := 0 promExemplar := &prompb.Exemplar{ Value: exemplar.DoubleVal(), Timestamp: timestamp.FromTime(exemplar.Timestamp().AsTime()), } + if !exemplar.TraceID().IsEmpty() { + val := exemplar.TraceID().HexString() + exemplarRunes += utf8.RuneCountInString(traceIDKey) + utf8.RuneCountInString(val) + promLabel := prompb.Label{ + Name: traceIDKey, + Value: val, + } + promExemplar.Labels = append(promExemplar.Labels, promLabel) + } + if !exemplar.SpanID().IsEmpty() { + val := exemplar.SpanID().HexString() + exemplarRunes += utf8.RuneCountInString(spanIDKey) + utf8.RuneCountInString(val) + promLabel := prompb.Label{ + Name: spanIDKey, + Value: val, + } + promExemplar.Labels = append(promExemplar.Labels, promLabel) + } + var labelsFromAttributes []prompb.Label - exemplarRunes := 0 exemplar.FilteredAttributes().Range(func(key string, value pdata.AttributeValue) bool { val := value.AsString() exemplarRunes += utf8.RuneCountInString(key) + utf8.RuneCountInString(val) @@ -372,12 +395,14 @@ func getPromExemplars(pt pdata.HistogramDataPoint) []prompb.Exemplar { Value: val, } - promExemplar.Labels = append(promExemplar.Labels, promLabel) + labelsFromAttributes = append(labelsFromAttributes, promLabel) return true }) - if exemplarRunes > maxExemplarRunes { - promExemplar.Labels = nil + if exemplarRunes <= maxExemplarRunes { + // only append filtered attributes if it does not cause exemplar + // labels to exceed the max number of runes + promExemplar.Labels = append(promExemplar.Labels, labelsFromAttributes...) } promExemplars = append(promExemplars, *promExemplar) diff --git a/pkg/translator/prometheusremotewrite/helper_test.go b/pkg/translator/prometheusremotewrite/helper_test.go index 7ddbcae86289..523091d142c3 100644 --- a/pkg/translator/prometheusremotewrite/helper_test.go +++ b/pkg/translator/prometheusremotewrite/helper_test.go @@ -424,18 +424,29 @@ func Test_getPromExemplars(t *testing.T) { }{ { "with_exemplars", - getHistogramDataPointWithExemplars(tnow, floatVal1, traceIDKey, traceIDValue1), + getHistogramDataPointWithExemplars(t, tnow, floatVal1, traceIDValue1, spanIDValue1, label11, value11), []prompb.Exemplar{ { Value: floatVal1, Timestamp: timestamp.FromTime(tnow), - Labels: []prompb.Label{getLabel(traceIDKey, traceIDValue1)}, + Labels: []prompb.Label{getLabel(traceIDKey, traceIDValue1), getLabel(spanIDKey, spanIDValue1), getLabel(label11, value11)}, + }, + }, + }, + { + "with_exemplars_without_trace_or_span", + getHistogramDataPointWithExemplars(t, tnow, floatVal1, "", "", label11, value11), + []prompb.Exemplar{ + { + Value: floatVal1, + Timestamp: timestamp.FromTime(tnow), + Labels: []prompb.Label{getLabel(label11, value11)}, }, }, }, { "too_many_runes_drops_labels", - getHistogramDataPointWithExemplars(tnow, floatVal1, keyWith129Runes, ""), + getHistogramDataPointWithExemplars(t, tnow, floatVal1, "", "", keyWith129Runes, ""), []prompb.Exemplar{ { Value: floatVal1, @@ -445,7 +456,7 @@ func Test_getPromExemplars(t *testing.T) { }, { "runes_at_limit_bytes_over_keeps_labels", - getHistogramDataPointWithExemplars(tnow, floatVal1, keyWith128Runes, ""), + getHistogramDataPointWithExemplars(t, tnow, floatVal1, "", "", keyWith128Runes, ""), []prompb.Exemplar{ { Value: floatVal1, @@ -454,6 +465,17 @@ func Test_getPromExemplars(t *testing.T) { }, }, }, + { + "too_many_runes_with_exemplar_drops_attrs_keeps_exemplar", + getHistogramDataPointWithExemplars(t, tnow, floatVal1, traceIDValue1, spanIDValue1, keyWith64Runes, ""), + []prompb.Exemplar{ + { + Value: floatVal1, + Timestamp: timestamp.FromTime(tnow), + Labels: []prompb.Label{getLabel(traceIDKey, traceIDValue1), getLabel(spanIDKey, spanIDValue1)}, + }, + }, + }, { "without_exemplar", getHistogramDataPoint(), diff --git a/pkg/translator/prometheusremotewrite/testutils_test.go b/pkg/translator/prometheusremotewrite/testutils_test.go index 214b038f529c..abc8822151fa 100644 --- a/pkg/translator/prometheusremotewrite/testutils_test.go +++ b/pkg/translator/prometheusremotewrite/testutils_test.go @@ -15,8 +15,10 @@ package prometheusremotewrite import ( + "encoding/hex" "math" "strings" + "testing" "time" "github.com/prometheus/prometheus/prompb" @@ -45,14 +47,16 @@ var ( value41 = "test_value41" dirty1 = "%" dirty2 = "?" - traceIDValue1 = "traceID-value1" - traceIDKey = "trace_id" + traceIDValue1 = "4303853f086f4f8c86cf198b6551df84" + spanIDValue1 = "e5513c32795c41b9" colliding1 = "test.colliding" colliding2 = "test/colliding" collidingSanitized = "test_colliding" keyWith129Runes = "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" // because of the special characters, this has 132 bytes and 128 runes keyWith128Runes = "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii世界" + // 64 + trace id + span id = 129 characters + keyWith64Runes = "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" intVal1 int64 = 1 intVal2 int64 = 2 @@ -221,7 +225,7 @@ func getTimeSeriesWithSamplesAndExemplars(labels []prompb.Label, samples []promp } } -func getHistogramDataPointWithExemplars(time time.Time, value float64, attributeKey string, attributeValue string) *pdata.HistogramDataPoint { +func getHistogramDataPointWithExemplars(t *testing.T, time time.Time, value float64, traceID string, spanID string, attributeKey string, attributeValue string) *pdata.HistogramDataPoint { h := pdata.NewHistogramDataPoint() e := h.Exemplars().AppendEmpty() @@ -229,6 +233,25 @@ func getHistogramDataPointWithExemplars(time time.Time, value float64, attribute e.SetTimestamp(pdata.NewTimestampFromTime(time)) e.FilteredAttributes().Insert(attributeKey, pdata.NewAttributeValueString(attributeValue)) + if traceID != "" { + var traceIDBytes [16]byte + traceIDBytesSlice, err := hex.DecodeString(traceID) + if err != nil { + t.Fatalf("error decoding trace id: %v", err) + } + copy(traceIDBytes[:], traceIDBytesSlice) + e.SetTraceID(pdata.NewTraceID(traceIDBytes)) + } + if spanID != "" { + var spanIDBytes [8]byte + spanIDBytesSlice, err := hex.DecodeString(spanID) + if err != nil { + t.Fatalf("error decoding span id: %v", err) + } + copy(spanIDBytes[:], spanIDBytesSlice) + e.SetSpanID(pdata.NewSpanID(spanIDBytes)) + } + return &h } From fc6a5fec4d126b3977da59c30f8a04b16f4614fd Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Mon, 14 Mar 2022 16:09:22 +0000 Subject: [PATCH 2/2] use require in unit tests --- pkg/translator/prometheusremotewrite/testutils_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/translator/prometheusremotewrite/testutils_test.go b/pkg/translator/prometheusremotewrite/testutils_test.go index abc8822151fa..f254a522573c 100644 --- a/pkg/translator/prometheusremotewrite/testutils_test.go +++ b/pkg/translator/prometheusremotewrite/testutils_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/model/pdata" ) @@ -236,18 +237,14 @@ func getHistogramDataPointWithExemplars(t *testing.T, time time.Time, value floa if traceID != "" { var traceIDBytes [16]byte traceIDBytesSlice, err := hex.DecodeString(traceID) - if err != nil { - t.Fatalf("error decoding trace id: %v", err) - } + require.NoErrorf(t, err, "error decoding trace id: %v", err) copy(traceIDBytes[:], traceIDBytesSlice) e.SetTraceID(pdata.NewTraceID(traceIDBytes)) } if spanID != "" { var spanIDBytes [8]byte spanIDBytesSlice, err := hex.DecodeString(spanID) - if err != nil { - t.Fatalf("error decoding span id: %v", err) - } + require.NoErrorf(t, err, "error decoding span id: %v", err) copy(spanIDBytes[:], spanIDBytesSlice) e.SetSpanID(pdata.NewSpanID(spanIDBytes)) }