Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[datadogexporter] Add option to send instrumentation library metadata tags with metrics #5431

Merged
merged 5 commits into from
Oct 8, 2021
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
4 changes: 4 additions & 0 deletions exporter/datadogexporter/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ type MetricsExporterConfig struct {
// ResourceAttributesAsTags, if set to true, will use the exporterhelper feature to transform all
// resource attributes into metric labels, which are then converted into tags
ResourceAttributesAsTags bool `mapstructure:"resource_attributes_as_tags"`

// InstrumentationLibraryMetadataAsTags, if set to true, adds the name and version of the
// instrumentation library that created a metric to the metric tags
InstrumentationLibraryMetadataAsTags bool `mapstructure:"instrumentation_library_metadata_as_tags"`
}

// TracesConfig defines the traces exporter specific configuration options
Expand Down
5 changes: 5 additions & 0 deletions exporter/datadogexporter/example/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ exporters:
#
# resource_attributes_as_tags: false

## @param instrumentation_library_metadata_as_tags - string - optional - default: false
## Set to true to add metadata about the instrumentation library that created a metric.
#
# instrumentation_library_metadata_as_tags: false

## @param histograms - custom object - optional
## Histograms specific configuration.
## @param mode - string - optional - default: nobuckets
Expand Down
3 changes: 2 additions & 1 deletion exporter/datadogexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ func createDefaultConfig() config.Exporter {
DeltaTTL: 3600,
Quantiles: true,
ExporterConfig: ddconfig.MetricsExporterConfig{
ResourceAttributesAsTags: false,
ResourceAttributesAsTags: false,
InstrumentationLibraryMetadataAsTags: false,
},
HistConfig: ddconfig.HistogramConfig{
Mode: "nobuckets",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 instrumentationlibrary

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

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

const (
instrumentationLibraryTag = "instrumentation_library"
instrumentationLibraryVersionTag = "instrumentation_library_version"
)

// TagsFromInstrumentationLibraryMetadata takes the name and version of
// the instrumentation library and converts them to Datadog tags.
func TagsFromInstrumentationLibraryMetadata(il pdata.InstrumentationLibrary) []string {
return []string{
translatorUtils.FormatKeyValueTag(instrumentationLibraryTag, il.Name()),
translatorUtils.FormatKeyValueTag(instrumentationLibraryVersionTag, il.Version()),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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 instrumentationlibrary

import (
"fmt"
"testing"

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

func TestTagsFromInstrumentationLibraryMetadata(t *testing.T) {
tests := []struct {
name string
version string
expectedTags []string
}{
{"test-il", "1.0.0", []string{fmt.Sprintf("%s:%s", instrumentationLibraryTag, "test-il"), fmt.Sprintf("%s:%s", instrumentationLibraryVersionTag, "1.0.0")}},
{"test-il", "", []string{fmt.Sprintf("%s:%s", instrumentationLibraryTag, "test-il"), fmt.Sprintf("%s:%s", instrumentationLibraryVersionTag, "n/a")}},
{"", "1.0.0", []string{fmt.Sprintf("%s:%s", instrumentationLibraryTag, "n/a"), fmt.Sprintf("%s:%s", instrumentationLibraryVersionTag, "1.0.0")}},
{"", "", []string{fmt.Sprintf("%s:%s", instrumentationLibraryTag, "n/a"), fmt.Sprintf("%s:%s", instrumentationLibraryVersionTag, "n/a")}},
}

for _, testInstance := range tests {
il := pdata.NewInstrumentationLibrary()
il.SetName(testInstance.name)
il.SetVersion(testInstance.version)
tags := TagsFromInstrumentationLibraryMetadata(il)

assert.ElementsMatch(t, testInstance.expectedTags, tags)
}
}
19 changes: 14 additions & 5 deletions exporter/datadogexporter/internal/translator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import "fmt"

type translatorConfig struct {
// metrics export behavior
HistMode HistogramMode
SendCountSum bool
Quantiles bool
SendMonotonic bool
ResourceAttributesAsTags bool
HistMode HistogramMode
SendCountSum bool
Quantiles bool
SendMonotonic bool
ResourceAttributesAsTags bool
InstrumentationLibraryMetadataAsTags bool

// cache configuration
sweepInterval int64
Expand Down Expand Up @@ -76,6 +77,14 @@ func WithResourceAttributesAsTags() Option {
}
}

// WithInstrumentationLibraryMetadataAsTags sets instrumentation library metadata as tags.
func WithInstrumentationLibraryMetadataAsTags() Option {
return func(t *translatorConfig) error {
t.InstrumentationLibraryMetadataAsTags = true
return nil
}
}

// HistogramMode is an export mode for OTLP Histogram metrics.
type HistogramMode string

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import (
"gopkg.in/zorkian/go-datadog-api.v2"

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

const metricName string = "metric name"
Expand All @@ -43,14 +45,15 @@ type Translator struct {

func New(params component.ExporterCreateSettings, options ...Option) (*Translator, error) {
cfg := translatorConfig{
HistMode: HistogramModeDistributions,
SendCountSum: false,
Quantiles: false,
SendMonotonic: true,
ResourceAttributesAsTags: false,
sweepInterval: 1800,
deltaTTL: 3600,
fallbackHostnameProvider: &noHostProvider{},
HistMode: HistogramModeDistributions,
SendCountSum: false,
Quantiles: false,
SendMonotonic: true,
ResourceAttributesAsTags: false,
InstrumentationLibraryMetadataAsTags: false,
sweepInterval: 1800,
deltaTTL: 3600,
fallbackHostnameProvider: &noHostProvider{},
}

for _, opt := range options {
Expand All @@ -73,11 +76,7 @@ func getTags(labels pdata.AttributeMap) []string {
tags := make([]string, 0, labels.Len())
labels.Range(func(key string, value pdata.AttributeValue) bool {
v := value.AsString()
if v == "" {
// Tags can't end with ":" so we replace empty values with "n/a"
v = "n/a"
}
tags = append(tags, fmt.Sprintf("%s:%s", key, v))
tags = append(tags, utils.FormatKeyValueTag(key, v))
return true
})
return tags
Expand All @@ -104,12 +103,12 @@ func (t *Translator) isSkippable(name string, v float64) bool {
}

// mapNumberMetrics maps double datapoints into Datadog metrics
func (t *Translator) mapNumberMetrics(name string, dt metrics.MetricDataType, slice pdata.NumberDataPointSlice, attrTags []string) []datadog.Metric {
func (t *Translator) mapNumberMetrics(name string, dt metrics.MetricDataType, slice pdata.NumberDataPointSlice, additionalTags []string) []datadog.Metric {
ms := make([]datadog.Metric, 0, slice.Len())
for i := 0; i < slice.Len(); i++ {
p := slice.At(i)
tags := getTags(p.Attributes())
tags = append(tags, attrTags...)
tags = append(tags, additionalTags...)
var val float64
switch p.Type() {
case pdata.MetricValueTypeDouble:
Expand All @@ -130,13 +129,13 @@ func (t *Translator) mapNumberMetrics(name string, dt metrics.MetricDataType, sl
}

// mapNumberMonotonicMetrics maps monotonic datapoints into Datadog metrics
func (t *Translator) mapNumberMonotonicMetrics(name string, slice pdata.NumberDataPointSlice, attrTags []string) []datadog.Metric {
func (t *Translator) mapNumberMonotonicMetrics(name string, slice pdata.NumberDataPointSlice, additionalTags []string) []datadog.Metric {
ms := make([]datadog.Metric, 0, slice.Len())
for i := 0; i < slice.Len(); i++ {
p := slice.At(i)
ts := uint64(p.Timestamp())
tags := getTags(p.Attributes())
tags = append(tags, attrTags...)
tags = append(tags, additionalTags...)

var val float64
switch p.Type() {
Expand Down Expand Up @@ -238,7 +237,7 @@ func (t *Translator) getLegacyBuckets(name string, p pdata.HistogramDataPoint, d
// We follow a similar approach to our OpenMetrics check:
// we report sum and count by default; buckets count can also
// be reported (opt-in) tagged by lower bound.
func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataPointSlice, delta bool, attrTags []string) (ms []datadog.Metric, sl sketches.SketchSeriesList) {
func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataPointSlice, delta bool, additionalTags []string) (ms []datadog.Metric, sl sketches.SketchSeriesList) {
// Allocate assuming none are nil and no buckets
ms = make([]datadog.Metric, 0, 2*slice.Len())
if t.cfg.HistMode == HistogramModeDistributions {
Expand All @@ -248,7 +247,7 @@ func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataP
p := slice.At(i)
ts := uint64(p.Timestamp())
tags := getTags(p.Attributes())
tags = append(tags, attrTags...)
tags = append(tags, additionalTags...)

if t.cfg.SendCountSum {
count := float64(p.Count())
Expand Down Expand Up @@ -310,14 +309,14 @@ func getQuantileTag(quantile float64) string {
}

// mapSummaryMetrics maps summary datapoints into Datadog metrics
func (t *Translator) mapSummaryMetrics(name string, slice pdata.SummaryDataPointSlice, attrTags []string) []datadog.Metric {
func (t *Translator) mapSummaryMetrics(name string, slice pdata.SummaryDataPointSlice, additionalTags []string) []datadog.Metric {
// Allocate assuming none are nil and no quantiles
ms := make([]datadog.Metric, 0, 2*slice.Len())
for i := 0; i < slice.Len(); i++ {
p := slice.At(i)
ts := uint64(p.Timestamp())
tags := getTags(p.Attributes())
tags = append(tags, attrTags...)
tags = append(tags, additionalTags...)

// count and sum are increasing; we treat them as cumulative monotonic sums.
{
Expand Down Expand Up @@ -386,23 +385,29 @@ func (t *Translator) MapMetrics(ctx context.Context, md pdata.Metrics) (series [
for j := 0; j < ilms.Len(); j++ {
ilm := ilms.At(j)
metricsArray := ilm.Metrics()

var additionalTags []string
if t.cfg.InstrumentationLibraryMetadataAsTags {
additionalTags = append(attributeTags, instrumentationlibrary.TagsFromInstrumentationLibraryMetadata(ilm.InstrumentationLibrary())...)
}

for k := 0; k < metricsArray.Len(); k++ {
md := metricsArray.At(k)
var datapoints []datadog.Metric
var sketchesPoints sketches.SketchSeriesList
switch md.DataType() {
case pdata.MetricDataTypeGauge:
datapoints = t.mapNumberMetrics(md.Name(), metrics.Gauge, md.Gauge().DataPoints(), attributeTags)
datapoints = t.mapNumberMetrics(md.Name(), metrics.Gauge, md.Gauge().DataPoints(), additionalTags)
case pdata.MetricDataTypeSum:
switch md.Sum().AggregationTemporality() {
case pdata.MetricAggregationTemporalityCumulative:
if t.cfg.SendMonotonic && isCumulativeMonotonic(md) {
datapoints = t.mapNumberMonotonicMetrics(md.Name(), md.Sum().DataPoints(), attributeTags)
datapoints = t.mapNumberMonotonicMetrics(md.Name(), md.Sum().DataPoints(), additionalTags)
} else {
datapoints = t.mapNumberMetrics(md.Name(), metrics.Gauge, md.Sum().DataPoints(), attributeTags)
datapoints = t.mapNumberMetrics(md.Name(), metrics.Gauge, md.Sum().DataPoints(), additionalTags)
}
case pdata.MetricAggregationTemporalityDelta:
datapoints = t.mapNumberMetrics(md.Name(), metrics.Count, md.Sum().DataPoints(), attributeTags)
datapoints = t.mapNumberMetrics(md.Name(), metrics.Count, md.Sum().DataPoints(), additionalTags)
default: // pdata.MetricAggregationTemporalityUnspecified or any other not supported type
t.logger.Debug("Unknown or unsupported aggregation temporality",
zap.String(metricName, md.Name()),
Expand All @@ -414,7 +419,7 @@ func (t *Translator) MapMetrics(ctx context.Context, md pdata.Metrics) (series [
switch md.Histogram().AggregationTemporality() {
case pdata.MetricAggregationTemporalityCumulative, pdata.MetricAggregationTemporalityDelta:
delta := md.Histogram().AggregationTemporality() == pdata.MetricAggregationTemporalityDelta
datapoints, sketchesPoints = t.mapHistogramMetrics(md.Name(), md.Histogram().DataPoints(), delta, attributeTags)
datapoints, sketchesPoints = t.mapHistogramMetrics(md.Name(), md.Histogram().DataPoints(), delta, additionalTags)
default: // pdata.MetricAggregationTemporalityUnspecified or any other not supported type
t.logger.Debug("Unknown or unsupported aggregation temporality",
zap.String("metric name", md.Name()),
Expand All @@ -423,7 +428,7 @@ func (t *Translator) MapMetrics(ctx context.Context, md pdata.Metrics) (series [
continue
}
case pdata.MetricDataTypeSummary:
datapoints = t.mapSummaryMetrics(md.Name(), md.Summary().DataPoints(), attributeTags)
datapoints = t.mapSummaryMetrics(md.Name(), md.Summary().DataPoints(), additionalTags)
default: // pdata.MetricDataTypeNone or any other not supported type
t.logger.Debug("Unknown or unsupported metric type", zap.String(metricName, md.Name()), zap.Any("data type", md.DataType()))
continue
Expand Down
28 changes: 28 additions & 0 deletions exporter/datadogexporter/internal/translator/utils/tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 utils

import (
"fmt"
)

// FormatKeyValueTag takes a key-value pair, and creates a tag string out of it
// Tags can't end with ":" so we replace empty values with "n/a"
func FormatKeyValueTag(key, value string) string {
if value == "" {
value = "n/a"
}
return fmt.Sprintf("%s:%s", key, value)
}
36 changes: 36 additions & 0 deletions exporter/datadogexporter/internal/translator/utils/tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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 utils

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFormatKeyValueTag(t *testing.T) {
tests := []struct {
key string
value string
expectedTag string
}{
{"a.test.tag", "a.test.value", "a.test.tag:a.test.value"},
{"a.test.tag", "", "a.test.tag:n/a"},
}

for _, testInstance := range tests {
assert.Equal(t, testInstance.expectedTag, FormatKeyValueTag(testInstance.key, testInstance.value))
}
}
4 changes: 4 additions & 0 deletions exporter/datadogexporter/metrics_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func translatorFromConfig(params component.ExporterCreateSettings, cfg *config.C
options = append(options, translator.WithResourceAttributesAsTags())
}

if cfg.Metrics.ExporterConfig.InstrumentationLibraryMetadataAsTags {
options = append(options, translator.WithInstrumentationLibraryMetadataAsTags())
}

options = append(options, translator.WithHistogramMode(translator.HistogramMode(cfg.Metrics.HistConfig.Mode)))

var numberMode translator.NumberMode
Expand Down