diff --git a/.chloggen/mx-psi_splat-tags.yaml b/.chloggen/mx-psi_splat-tags.yaml new file mode 100644 index 00000000..c0d6095b --- /dev/null +++ b/.chloggen/mx-psi_splat-tags.yaml @@ -0,0 +1,17 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component (e.g. pkg/quantile) +component: pkg/otlp/metrics + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add `WithSplatArrayAttributes` translator option + +# The PR related to this change +issues: [329] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: | + - This option destructures an array valued attribute into multiple tags with the same key and different values. diff --git a/.chloggen/mx-psi_splat-tags2.yaml b/.chloggen/mx-psi_splat-tags2.yaml new file mode 100644 index 00000000..eae42540 --- /dev/null +++ b/.chloggen/mx-psi_splat-tags2.yaml @@ -0,0 +1,17 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: breaking + +# The name of the component (e.g. pkg/quantile) +component: pkg/otlp/metrics + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add `splat` option to `WithAttributeMap` + +# The PR related to this change +issues: [329] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: | + - This option destructures an array valued attribute into multiple tags with the same key and different values. diff --git a/pkg/otlp/metrics/config.go b/pkg/otlp/metrics/config.go index 3875bda2..51648264 100644 --- a/pkg/otlp/metrics/config.go +++ b/pkg/otlp/metrics/config.go @@ -29,6 +29,7 @@ type translatorConfig struct { InitialCumulMonoValueMode InitialCumulMonoValueMode InstrumentationLibraryMetadataAsTags bool InstrumentationScopeMetadataAsTags bool + SplatArrayAttributes bool originProduct OriginProduct @@ -215,3 +216,11 @@ func WithInitialCumulMonoValueMode(mode InitialCumulMonoValueMode) TranslatorOpt return nil } } + +// WithSplatArrayAttributes destructures an array attribute into multiple Datadog tags. +func WithSplatArrayAttributes() TranslatorOption { + return func(t *translatorConfig) error { + t.SplatArrayAttributes = true + return nil + } +} diff --git a/pkg/otlp/metrics/dimensions.go b/pkg/otlp/metrics/dimensions.go index 8187607e..11853846 100644 --- a/pkg/otlp/metrics/dimensions.go +++ b/pkg/otlp/metrics/dimensions.go @@ -80,14 +80,38 @@ func (d *Dimensions) OriginProductDetail() OriginProductDetail { // getTags maps an attributeMap into a slice of Datadog tags func getTags(labels pcommon.Map) []string { tags := make([]string, 0, labels.Len()) - labels.Range(func(key string, value pcommon.Value) bool { - v := value.AsString() + labels.Range(func(key string, val pcommon.Value) bool { + v := val.AsString() tags = append(tags, utils.FormatKeyValueTag(key, v)) return true }) return tags } +// getTagsWithSplat maps an attributeMap into a slice of Datadog tags. +// It will flatten the map values if they are slices. +func getTagsWithSplat(labels pcommon.Map) []string { + // Allocation is a lower bound since we are not traversing the whole map. + tags := make([]string, 0, labels.Len()) + + var addToTags func(string, pcommon.Value) bool + addToTags = func(key string, val pcommon.Value) bool { + switch val.Type() { + case pcommon.ValueTypeSlice: + for i := 0; i < val.Slice().Len(); i++ { + _ = addToTags(key, val.Slice().At(i)) + } + default: + v := val.AsString() + tags = append(tags, utils.FormatKeyValueTag(key, v)) + } + return true + } + + labels.Range(addToTags) + return tags +} + // AddTags to metrics dimensions. func (d *Dimensions) AddTags(tags ...string) *Dimensions { // defensively copy the tags @@ -106,7 +130,10 @@ func (d *Dimensions) AddTags(tags ...string) *Dimensions { } // WithAttributeMap creates a new metricDimensions struct with additional tags from attributes. -func (d *Dimensions) WithAttributeMap(labels pcommon.Map) *Dimensions { +func (d *Dimensions) WithAttributeMap(labels pcommon.Map, splat bool) *Dimensions { + if splat { + return d.AddTags(getTagsWithSplat(labels)...) + } return d.AddTags(getTags(labels)...) } diff --git a/pkg/otlp/metrics/dimensions_test.go b/pkg/otlp/metrics/dimensions_test.go index e8187c40..d196fad3 100644 --- a/pkg/otlp/metrics/dimensions_test.go +++ b/pkg/otlp/metrics/dimensions_test.go @@ -27,12 +27,41 @@ func TestWithAttributeMap(t *testing.T) { "key1": "val1", "key2": "val2", "key3": "", + "key4": []any{"val4a", "val4b"}, + "key5": []any{"val5a", []any{"val5b", "val5c", []any{"val5d"}}}, }) dims := Dimensions{} assert.ElementsMatch(t, - dims.WithAttributeMap(attributes).tags, - [...]string{"key1:val1", "key2:val2", "key3:n/a"}, + dims.WithAttributeMap(attributes, false).tags, + [...]string{"key1:val1", "key2:val2", "key3:n/a", `key4:["val4a","val4b"]`, `key5:["val5a",["val5b","val5c",["val5d"]]]`}, + ) +} + +func TestWithAttributeMapSplat(t *testing.T) { + attributes := pcommon.NewMap() + attributes.FromRaw(map[string]interface{}{ + "key1": "val1", + "key2": "val2", + "key3": "", + "key4": []any{"val4a", "val4b"}, + "key5": []any{"val5a", []any{"val5b", "val5c", []any{"val5d"}}}, + }) + + dims := Dimensions{} + assert.ElementsMatch(t, + dims.WithAttributeMap(attributes, true).tags, + [...]string{ + "key1:val1", + "key2:val2", + "key3:n/a", + "key4:val4a", + "key4:val4b", + "key5:val5a", + "key5:val5b", + "key5:val5c", + "key5:val5d", + }, ) } @@ -115,7 +144,7 @@ func TestAllFieldsAreCopied(t *testing.T) { newDims := dims. AddTags("tagThree:c"). WithSuffix("suffix"). - WithAttributeMap(attributes) + WithAttributeMap(attributes, false) assert.Equal(t, "example.name.suffix", newDims.Name()) assert.Equal(t, "hostname", newDims.Host()) diff --git a/pkg/otlp/metrics/exponential_histograms_translator.go b/pkg/otlp/metrics/exponential_histograms_translator.go index 006610f8..ac3a5697 100644 --- a/pkg/otlp/metrics/exponential_histograms_translator.go +++ b/pkg/otlp/metrics/exponential_histograms_translator.go @@ -93,7 +93,7 @@ func (t *Translator) mapExponentialHistogramMetrics( p := slice.At(i) startTs := uint64(p.StartTimestamp()) ts := uint64(p.Timestamp()) - pointDims := dims.WithAttributeMap(p.Attributes()) + pointDims := dims.WithAttributeMap(p.Attributes(), t.cfg.SplatArrayAttributes) histInfo := histogramInfo{ok: true} diff --git a/pkg/otlp/metrics/metrics_translator.go b/pkg/otlp/metrics/metrics_translator.go index 70e4bc50..8f87fb21 100644 --- a/pkg/otlp/metrics/metrics_translator.go +++ b/pkg/otlp/metrics/metrics_translator.go @@ -156,7 +156,7 @@ func (t *Translator) mapNumberMetrics( continue } - pointDims := dims.WithAttributeMap(p.Attributes()) + pointDims := dims.WithAttributeMap(p.Attributes(), t.cfg.SplatArrayAttributes) var val float64 switch p.ValueType() { case pmetric.NumberDataPointValueTypeDouble: @@ -215,7 +215,7 @@ func (t *Translator) mapNumberMonotonicMetrics( ts := uint64(p.Timestamp()) startTs := uint64(p.StartTimestamp()) - pointDims := dims.WithAttributeMap(p.Attributes()) + pointDims := dims.WithAttributeMap(p.Attributes(), t.cfg.SplatArrayAttributes) var val float64 switch p.ValueType() { @@ -467,7 +467,7 @@ func (t *Translator) mapHistogramMetrics( startTs := uint64(p.StartTimestamp()) ts := uint64(p.Timestamp()) - pointDims := dims.WithAttributeMap(p.Attributes()) + pointDims := dims.WithAttributeMap(p.Attributes(), t.cfg.SplatArrayAttributes) histInfo := histogramInfo{ok: true} @@ -576,7 +576,7 @@ func (t *Translator) mapSummaryMetrics( startTs := uint64(p.StartTimestamp()) ts := uint64(p.Timestamp()) - pointDims := dims.WithAttributeMap(p.Attributes()) + pointDims := dims.WithAttributeMap(p.Attributes(), t.cfg.SplatArrayAttributes) // count and sum are increasing; we treat them as cumulative monotonic sums. {