Skip to content

Commit

Permalink
[exporter/datadogexporter] Take hostname into account for cache
Browse files Browse the repository at this point in the history
  • Loading branch information
mx-psi committed Nov 19, 2021
1 parent 55fee2d commit be5d8de
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 297 deletions.
97 changes: 97 additions & 0 deletions exporter/datadogexporter/internal/translator/dimensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright The OpenTelemetry 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.

package translator

import (
"fmt"
"sort"
"strings"

"go.opentelemetry.io/collector/model/pdata"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/translator/utils"
)

const (
dimensionSeparator = string(byte(0))
)

type metricsDimensions struct {
name string
tags []string
host string
}

// getTags maps an attributeMap into a slice of Datadog tags
func getTags(labels pdata.AttributeMap) []string {
tags := make([]string, 0, labels.Len())
labels.Range(func(key string, value pdata.AttributeValue) bool {
v := value.AsString()
tags = append(tags, utils.FormatKeyValueTag(key, v))
return true
})
return tags
}

// AddTags to metrics dimensions. The argument may be modified.
func (m *metricsDimensions) AddTags(tags ...string) metricsDimensions {
return metricsDimensions{
name: m.name,
// append the field to the passed argument,
// so that the slice we modify is the one we get as an argument.
tags: append(tags, m.tags...),
host: m.host,
}
}

// WithAttributeMap creates a new metricDimensions struct with additional tags from attributes.
func (m *metricsDimensions) WithAttributeMap(labels pdata.AttributeMap) metricsDimensions {
return m.AddTags(getTags(labels)...)
}

// WithSuffix creates a new dimensions struct with an extra name suffix.
func (m *metricsDimensions) WithSuffix(suffix string) metricsDimensions {
return metricsDimensions{
name: fmt.Sprintf("%s.%s", m.name, suffix),
host: m.host,
tags: m.tags,
}
}

// Uses a logic similar to what is done in the span processor to build metric keys:
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b2327211df976e0a57ef0425493448988772a16b/processor/spanmetricsprocessor/processor.go#L353-L387
// TODO: make this a public util function?
func concatDimensionValue(metricKeyBuilder *strings.Builder, value string) {
metricKeyBuilder.WriteString(value)
metricKeyBuilder.WriteString(dimensionSeparator)
}

// String maps dimensions to a string to use as an identifier.
// The tags order does not matter.
func (m *metricsDimensions) String() string {
var metricKeyBuilder strings.Builder

dimensions := make([]string, len(m.tags))
copy(dimensions, m.tags)

dimensions = append(dimensions, fmt.Sprintf("name:%s", m.name))
dimensions = append(dimensions, fmt.Sprintf("host:%s", m.name))
sort.Strings(dimensions)

for _, dim := range dimensions {
concatDimensionValue(&metricKeyBuilder, dim)
}
return metricKeyBuilder.String()
}
96 changes: 96 additions & 0 deletions exporter/datadogexporter/internal/translator/dimensions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright The OpenTelemetry 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.

package translator

import (
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/model/pdata"
)

func TestWithAttributeMap(t *testing.T) {
attributes := pdata.NewAttributeMapFromMap(map[string]pdata.AttributeValue{
"key1": pdata.NewAttributeValueString("val1"),
"key2": pdata.NewAttributeValueString("val2"),
"key3": pdata.NewAttributeValueString(""),
})

dims := metricsDimensions{}
assert.ElementsMatch(t,
dims.WithAttributeMap(attributes).tags,
[...]string{"key1:val1", "key2:val2", "key3:n/a"},
)
}

func TestMetricDimensionsString(t *testing.T) {
getKey := func(name string, tags []string) string {
dims := metricsDimensions{name: name, tags: tags}
return dims.String()
}
metricName := "metric.name"
noTags := getKey(metricName, []string{})
someTags := getKey(metricName, []string{"key1:val1", "key2:val2"})
sameTags := getKey(metricName, []string{"key2:val2", "key1:val1"})
diffTags := getKey(metricName, []string{"key3:val3"})

assert.NotEqual(t, noTags, someTags)
assert.NotEqual(t, someTags, diffTags)
assert.Equal(t, someTags, sameTags)
}

func TestMetricDimensionsStringNoTagsChange(t *testing.T) {
// The original metricDimensionsToMapKey had an issue where:
// - if the capacity of the tags array passed to it was higher than its length
// - and the metric name is earlier (in alphabetical order) than one of the tags
// then the original tag array would be modified (without a reallocation, since there is enough capacity),
// and would contain a tag labeled as the metric name, while the final tag (in alphabetical order)
// would get left out.
// This test checks that this doesn't happen anymore.

originalTags := make([]string, 2, 3)
originalTags[0] = "key1:val1"
originalTags[1] = "key2:val2"

dims := metricsDimensions{
name: "a.metric.name",
tags: originalTags,
}

_ = dims.String()
assert.Equal(t, []string{"key1:val1", "key2:val2"}, originalTags)

}

var testDims metricsDimensions = metricsDimensions{
name: "test.metric",
tags: []string{"key:val"},
host: "host",
}

func TestWithSuffix(t *testing.T) {
dimsSuf1 := testDims.WithSuffix("suffixOne")
dimsSuf2 := testDims.WithSuffix("suffixTwo")

assert.Equal(t, "test.metric", testDims.name)
assert.Equal(t, "test.metric.suffixOne", dimsSuf1.name)
assert.Equal(t, "test.metric.suffixTwo", dimsSuf2.name)
}

func TestAddTags(t *testing.T) {
dimsWithTags := testDims.AddTags("key1:val1", "key2:val2")
assert.ElementsMatch(t, []string{"key:val", "key1:val1", "key2:val2"}, dimsWithTags.tags)
assert.ElementsMatch(t, []string{"key:val"}, testDims.tags)
}
Loading

0 comments on commit be5d8de

Please sign in to comment.