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

[exporter/datadogexporter] Decouple translator and exporter configuration #5270

Merged
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
7 changes: 6 additions & 1 deletion exporter/datadogexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ func createMetricsExporter(
return nil
}
} else {
pushMetricsFn = newMetricsExporter(ctx, set, cfg).PushMetricsData
exp, err := newMetricsExporter(ctx, set, cfg)
if err != nil {
cancel()
return nil, err
}
pushMetricsFn = exp.PushMetricsData
}

exporter, err := exporterhelper.NewMetricsExporter(
Expand Down
142 changes: 142 additions & 0 deletions exporter/datadogexporter/internal/translator/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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"

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

// cache configuration
sweepInterval int64
deltaTTL int64

// hostname provider configuration
fallbackHostnameProvider HostnameProvider
}

// Option is a translator creation option.
type Option func(*translatorConfig) error

// WithDeltaTTL sets the delta TTL for cumulative metrics datapoints.
// By default, 3600 seconds are used.
func WithDeltaTTL(deltaTTL int64) Option {
return func(t *translatorConfig) error {
if deltaTTL <= 0 {
return fmt.Errorf("time to live must be positive: %d", deltaTTL)
}
t.deltaTTL = deltaTTL
t.sweepInterval = 1
if t.deltaTTL > 1 {
t.sweepInterval = t.deltaTTL / 2
}
return nil
}
}

// WithFallbackHostnameProvider sets the fallback hostname provider.
// By default, an empty hostname is used as a fallback.
func WithFallbackHostnameProvider(provider HostnameProvider) Option {
return func(t *translatorConfig) error {
t.fallbackHostnameProvider = provider
return nil
}
}

// WithQuantiles enables quantiles exporting for summary metrics.
func WithQuantiles() Option {
return func(t *translatorConfig) error {
t.Quantiles = true
return nil
}
}

// WithResourceAttributesAsTags sets resource attributes as tags.
func WithResourceAttributesAsTags() Option {
return func(t *translatorConfig) error {
t.ResourceAttributesAsTags = true
return nil
}
}

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

const (
// HistogramModeNoBuckets disables bucket export.
HistogramModeNoBuckets HistogramMode = "nobuckets"
// HistogramModeCounters exports buckets as Datadog counts.
HistogramModeCounters HistogramMode = "counters"
// HistogramModeDistributions exports buckets as Datadog distributions.
HistogramModeDistributions HistogramMode = "distributions"
)

// WithHistogramMode sets the histograms mode.
// The default mode is HistogramModeOff.
func WithHistogramMode(mode HistogramMode) Option {
return func(t *translatorConfig) error {

switch mode {
case HistogramModeNoBuckets, HistogramModeCounters, HistogramModeDistributions:
t.HistMode = mode
default:
return fmt.Errorf("unknown histogram mode: %q", mode)
}
return nil
}
}

// WithCountSumMetrics exports .count and .sum histogram metrics.
func WithCountSumMetrics() Option {
return func(t *translatorConfig) error {
t.SendCountSum = true
return nil
}
}

// NumberMode is an export mode for OTLP Number metrics.
type NumberMode string

const (
// NumberModeCumulativeToDelta calculates delta for
// cumulative monotonic metrics in the client side and reports
// them as Datadog counts.
NumberModeCumulativeToDelta NumberMode = "cumulative_to_delta"

// NumberModeRawValue reports the raw value for cumulative monotonic
// metrics as a Datadog gauge.
NumberModeRawValue NumberMode = "raw_value"
)

// WithNumberMode sets the number mode.
// The default mode is NumberModeCumulativeToDelta.
func WithNumberMode(mode NumberMode) Option {
return func(t *translatorConfig) error {
switch mode {
case NumberModeCumulativeToDelta:
t.SendMonotonic = true
case NumberModeRawValue:
t.SendMonotonic = false
default:
return fmt.Errorf("unknown number mode: %q", mode)
}
return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 "context"

// HostnameProvider gets a hostname
type HostnameProvider interface {
// Hostname gets the hostname from the machine.
Hostname(ctx context.Context) (string, error)
}

var _ HostnameProvider = (*noHostProvider)(nil)

type noHostProvider struct{}

func (*noHostProvider) Hostname(context.Context) (string, error) {
return "", nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,45 @@ import (
"go.uber.org/zap"
"gopkg.in/zorkian/go-datadog-api.v2"

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

const metricName string = "metric name"

const (
histogramModeNoBuckets = "nobuckets"
histogramModeCounters = "counters"
histogramModeDistributions = "distributions"
)

// HostnameProvider gets a hostname
type HostnameProvider interface {
// Hostname gets the hostname from the machine.
Hostname(ctx context.Context) (string, error)
}

type Translator struct {
prevPts *TTLCache
logger *zap.Logger
cfg config.MetricsConfig
buildInfo component.BuildInfo
fallbackHostnameProvider HostnameProvider
prevPts *ttlCache
logger *zap.Logger
cfg translatorConfig
buildInfo component.BuildInfo
}

func New(cache *TTLCache, params component.ExporterCreateSettings, cfg config.MetricsConfig, fallbackHostProvider HostnameProvider) *Translator {
return &Translator{cache, params.Logger, cfg, params.BuildInfo, fallbackHostProvider}
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{},
}

for _, opt := range options {
err := opt(&cfg)
if err != nil {
return nil, err
}
}

if cfg.HistMode == HistogramModeNoBuckets && !cfg.SendCountSum {
return nil, fmt.Errorf("no buckets mode and no send count sum are incompatible")
}

cache := newTTLCache(cfg.sweepInterval, cfg.deltaTTL)
return &Translator{cache, params.Logger, cfg, params.BuildInfo}, nil
}

// getTags maps an attributeMap into a slice of Datadog tags
Expand Down Expand Up @@ -232,7 +241,7 @@ func (t *Translator) getLegacyBuckets(name string, p pdata.HistogramDataPoint, d
func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataPointSlice, delta bool, attrTags []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.HistConfig.Mode == histogramModeDistributions {
if t.cfg.HistMode == HistogramModeDistributions {
sl = make(sketches.SketchSeriesList, 0, slice.Len())
}
for i := 0; i < slice.Len(); i++ {
Expand All @@ -241,7 +250,7 @@ func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataP
tags := getTags(p.Attributes())
tags = append(tags, attrTags...)

if t.cfg.HistConfig.SendCountSum {
if t.cfg.SendCountSum {
count := float64(p.Count())
countName := fmt.Sprintf("%s.count", name)
if delta {
Expand All @@ -251,7 +260,7 @@ func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataP
}
}

if t.cfg.HistConfig.SendCountSum {
if t.cfg.SendCountSum {
sum := p.Sum()
sumName := fmt.Sprintf("%s.sum", name)
if !t.isSkippable(sumName, p.Sum()) {
Expand All @@ -263,10 +272,10 @@ func (t *Translator) mapHistogramMetrics(name string, slice pdata.HistogramDataP
}
}

switch t.cfg.HistConfig.Mode {
case histogramModeCounters:
switch t.cfg.HistMode {
case HistogramModeCounters:
ms = append(ms, t.getLegacyBuckets(name, p, delta, tags)...)
case histogramModeDistributions:
case HistogramModeDistributions:
sl = append(sl, t.getSketchBuckets(name, ts, p, true, tags))
}
}
Expand Down Expand Up @@ -349,7 +358,7 @@ func (t *Translator) mapSummaryMetrics(name string, slice pdata.SummaryDataPoint
}

// MapMetrics maps OTLP metrics into the DataDog format
func (t *Translator) MapMetrics(md pdata.Metrics) (series []datadog.Metric, sl sketches.SketchSeriesList) {
func (t *Translator) MapMetrics(ctx context.Context, md pdata.Metrics) (series []datadog.Metric, sl sketches.SketchSeriesList, err error) {
pushTime := uint64(time.Now().UTC().UnixNano())
rms := md.ResourceMetrics()
seenHosts := make(map[string]struct{})
Expand All @@ -360,16 +369,15 @@ func (t *Translator) MapMetrics(md pdata.Metrics) (series []datadog.Metric, sl s

// Only fetch attribute tags if they're not already converted into labels.
// Otherwise some tags would be present twice in a metric's tag list.
if !t.cfg.ExporterConfig.ResourceAttributesAsTags {
if !t.cfg.ResourceAttributesAsTags {
attributeTags = attributes.TagsFromAttributes(rm.Resource().Attributes())
}

host, ok := attributes.HostnameFromAttributes(rm.Resource().Attributes())
if !ok {
fallbackHost, err := t.fallbackHostnameProvider.Hostname(context.Background())
host = ""
if err == nil {
host = fallbackHost
host, err = t.cfg.fallbackHostnameProvider.Hostname(context.Background())
if err != nil {
return nil, nil, fmt.Errorf("failed to get fallback host: %w", err)
}
}
seenHosts[host] = struct{}{}
Expand Down
Loading