diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a00f1106a10..fc30598190ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,14 @@ - `cmd/mdatagen`: Add resource attributes definition to metadata.yaml and move `pdata.Metrics` creation to the generated code (#5270) +### 💡 Enhancements 💡 + +- `datadogexporter`: Add `metrics::sums::cumulative_monotonic_mode` to specify export mode for cumulative monotonic sums (#8490) + +### 🚩 Deprecations 🚩 + +- `datadogexporter`: Deprecate `metrics::send_monotonic_counter` in favor of `metrics::sums::cumulative_monotonic_mode` (#8490) + ## v0.47.0 ### 💡 Enhancements 💡 diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index 60ed7eeaf48c..e2aacfd1c797 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -15,6 +15,7 @@ package config // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" import ( + "encoding" "errors" "fmt" "regexp" @@ -77,6 +78,7 @@ type MetricsConfig struct { // SendMonotonic states whether to report cumulative monotonic metrics as counters // or gauges + // Deprecated: [v0.48.0] Use `metrics::sums::cumulative_monotonic_mode` (SumConfig.CumulativeMonotonicMode) instead. SendMonotonic bool `mapstructure:"send_monotonic_counter"` // DeltaTTL defines the time that previous points of a cumulative monotonic @@ -92,6 +94,9 @@ type MetricsConfig struct { // HistConfig defines the export of OTLP Histograms. HistConfig HistogramConfig `mapstructure:"histograms"` + + // SumConfig defines the export of OTLP Sums. + SumConfig SumConfig `mapstructure:"sums"` } // HistogramConfig customizes export of OTLP Histograms. @@ -117,6 +122,46 @@ func (c *HistogramConfig) validate() error { return nil } +// CumulativeMonotonicSumMode is the export mode for OTLP Sum metrics. +type CumulativeMonotonicSumMode string + +const ( + // CumulativeMonotonicSumModeToDelta calculates delta for + // cumulative monotonic sum metrics in the client side and reports + // them as Datadog counts. + CumulativeMonotonicSumModeToDelta CumulativeMonotonicSumMode = "to_delta" + + // CumulativeMonotonicSumModeRawValue reports the raw value for + // cumulative monotonic sum metrics as a Datadog gauge. + CumulativeMonotonicSumModeRawValue CumulativeMonotonicSumMode = "raw_value" +) + +var _ encoding.TextUnmarshaler = (*CumulativeMonotonicSumMode)(nil) + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (sm *CumulativeMonotonicSumMode) UnmarshalText(in []byte) error { + switch mode := CumulativeMonotonicSumMode(in); mode { + case CumulativeMonotonicSumModeToDelta, + CumulativeMonotonicSumModeRawValue: + *sm = mode + return nil + default: + return fmt.Errorf("invalid cumulative monotonic sum mode %q", mode) + } +} + +// SumConfig customizes export of OTLP Sums. +type SumConfig struct { + // CumulativeMonotonicMode is the mode for exporting OTLP Cumulative Monotonic Sums. + // Valid values are 'to_delta' or 'raw_value'. + // - 'to_delta' calculates delta for cumulative monotonic sums and sends it as a Datadog count. + // - 'raw_value' sends the raw value of cumulative monotonic sums as Datadog gauges. + // + // The default is 'to_delta'. + // See https://docs.datadoghq.com/metrics/otlp/?tab=sum#mapping for details and examples. + CumulativeMonotonicMode CumulativeMonotonicSumMode `mapstructure:"cumulative_monotonic_mode"` +} + // MetricsExporterConfig provides options for a user to customize the behavior of the // metrics exporter type MetricsExporterConfig struct { @@ -352,6 +397,13 @@ func (c *Config) Unmarshal(configMap *config.Map) error { return err } + // Add deprecation warnings for deprecated settings. + renamingWarnings, err := handleRenamedSettings(configMap, c) + if err != nil { + return err + } + c.warnings = append(c.warnings, renamingWarnings...) + switch c.Metrics.HistConfig.Mode { case histogramModeCounters, histogramModeNoBuckets, histogramModeDistributions: // Do nothing diff --git a/exporter/datadogexporter/config/config_test.go b/exporter/datadogexporter/config/config_test.go index b4482dc83a83..963a5d97419a 100644 --- a/exporter/datadogexporter/config/config_test.go +++ b/exporter/datadogexporter/config/config_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config" "go.opentelemetry.io/collector/config/confignet" "go.uber.org/zap" ) @@ -190,3 +191,17 @@ func TestSpanNameRemappingsValidation(t *testing.T) { require.NoError(t, noErr) require.Error(t, err) } + +func TestInvalidSumMode(t *testing.T) { + cfgMap := config.NewMapFromStringMap(map[string]interface{}{ + "metrics": map[string]interface{}{ + "sums": map[string]interface{}{ + "cumulative_monotonic_mode": "invalid_mode", + }, + }, + }) + + cfg := futureDefaultConfig() + err := cfg.Unmarshal(cfgMap) + assert.EqualError(t, err, "1 error(s) decoding:\n\n* error decoding 'metrics.sums.cumulative_monotonic_mode': invalid cumulative monotonic sum mode \"invalid_mode\"") +} diff --git a/exporter/datadogexporter/config/warn_envvars.go b/exporter/datadogexporter/config/warn_envvars.go index a76e44108357..e7a786270a37 100644 --- a/exporter/datadogexporter/config/warn_envvars.go +++ b/exporter/datadogexporter/config/warn_envvars.go @@ -43,6 +43,9 @@ func futureDefaultConfig() *Config { Mode: "distributions", SendCountSum: false, }, + SumConfig: SumConfig{ + CumulativeMonotonicMode: CumulativeMonotonicSumModeToDelta, + }, }, Traces: TracesConfig{ SampleRate: 1, diff --git a/exporter/datadogexporter/config/warning_deprecated.go b/exporter/datadogexporter/config/warning_deprecated.go new file mode 100644 index 000000000000..91d9a9c536ce --- /dev/null +++ b/exporter/datadogexporter/config/warning_deprecated.go @@ -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 config // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" + +import ( + "fmt" + + "go.opentelemetry.io/collector/config" + "go.uber.org/multierr" +) + +var _ error = (*renameError)(nil) + +// renameError is an error related to a renamed setting. +type renameError struct { + // oldName of the configuration option. + oldName string + // newName of the configuration option. + newName string + // oldRemovedIn is the version where the old config option will be removed. + oldRemovedIn string + // updateFn updates the configuration to map the old value into the new one. + // It must only be called when the old value is set and is not the default. + updateFn func(*Config) + // issueNumber on opentelemetry-collector-contrib for tracking + issueNumber uint +} + +// List of settings that are deprecated. +var renamedSettings = []renameError{ + { + oldName: "metrics::send_monotonic_counter", + newName: "metrics::sums::cumulative_monotonic_mode", + oldRemovedIn: "v0.50.0", + issueNumber: 8489, + updateFn: func(c *Config) { + if c.Metrics.SendMonotonic { + c.Metrics.SumConfig.CumulativeMonotonicMode = CumulativeMonotonicSumModeToDelta + } else { + c.Metrics.SumConfig.CumulativeMonotonicMode = CumulativeMonotonicSumModeRawValue + } + }, + }, +} + +// Error implements the error interface. +func (e renameError) Error() string { + return fmt.Sprintf( + "%q has been deprecated in favor of %q and will be removed in %s. See github.com/open-telemetry/opentelemetry-collector-contrib/issues/%d", + e.oldName, + e.newName, + e.oldRemovedIn, + e.issueNumber, + ) +} + +// Check if the deprecated option is being used. +// Error out if both the old and new options are being used. +func (e renameError) Check(configMap *config.Map) (bool, error) { + if configMap.IsSet(e.oldName) && configMap.IsSet(e.newName) { + return false, fmt.Errorf("%q and %q can't be both set at the same time: use %q only instead", e.oldName, e.newName, e.newName) + } + return configMap.IsSet(e.oldName), nil +} + +// UpdateCfg to move the old configuration value into the new one. +func (e renameError) UpdateCfg(cfg *Config) { + e.updateFn(cfg) +} + +// handleRenamedSettings for a given configuration map. +// Error out if any pair of old-new options are set at the same time. +func handleRenamedSettings(configMap *config.Map, cfg *Config) (warnings []error, err error) { + for _, renaming := range renamedSettings { + isOldNameUsed, errCheck := renaming.Check(configMap) + err = multierr.Append(err, errCheck) + + if errCheck == nil && isOldNameUsed { + warnings = append(warnings, renaming) + // only update config if old name is in use + renaming.UpdateCfg(cfg) + } + } + return +} diff --git a/exporter/datadogexporter/config/warning_deprecated_test.go b/exporter/datadogexporter/config/warning_deprecated_test.go new file mode 100644 index 000000000000..432a746d7cb7 --- /dev/null +++ b/exporter/datadogexporter/config/warning_deprecated_test.go @@ -0,0 +1,103 @@ +// 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 config // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/config" +) + +func TestDeprecationSendMonotonic(t *testing.T) { + tests := []struct { + name string + cfgMap *config.Map + expectedMode CumulativeMonotonicSumMode + warnings []string + err string + }{ + { + name: "both metrics::send_monotonic and new metrics::sums::cumulative_monotonic_mode", + cfgMap: config.NewMapFromStringMap(map[string]interface{}{ + "metrics": map[string]interface{}{ + "send_monotonic_counter": true, + "sums": map[string]interface{}{ + "cumulative_monotonic_mode": "to_delta", + }, + }, + }), + err: "\"metrics::send_monotonic_counter\" and \"metrics::sums::cumulative_monotonic_mode\" can't be both set at the same time: use \"metrics::sums::cumulative_monotonic_mode\" only instead", + }, + { + name: "metrics::send_monotonic set to true", + cfgMap: config.NewMapFromStringMap(map[string]interface{}{ + "metrics": map[string]interface{}{ + "send_monotonic_counter": true, + }, + }), + expectedMode: CumulativeMonotonicSumModeToDelta, + warnings: []string{ + "\"metrics::send_monotonic_counter\" has been deprecated in favor of \"metrics::sums::cumulative_monotonic_mode\" and will be removed in v0.50.0. See github.com/open-telemetry/opentelemetry-collector-contrib/issues/8489", + }, + }, + { + name: "metrics::send_monotonic set to false", + cfgMap: config.NewMapFromStringMap(map[string]interface{}{ + "metrics": map[string]interface{}{ + "send_monotonic_counter": false, + }, + }), + expectedMode: CumulativeMonotonicSumModeRawValue, + warnings: []string{ + "\"metrics::send_monotonic_counter\" has been deprecated in favor of \"metrics::sums::cumulative_monotonic_mode\" and will be removed in v0.50.0. See github.com/open-telemetry/opentelemetry-collector-contrib/issues/8489", + }, + }, + { + name: "metrics::send_monotonic and metrics::sums::cumulative_monotonic_mode unset", + cfgMap: config.NewMapFromStringMap(map[string]interface{}{}), + expectedMode: CumulativeMonotonicSumModeToDelta, + }, + { + name: "metrics::sums::cumulative_monotonic_mode set", + cfgMap: config.NewMapFromStringMap(map[string]interface{}{ + "metrics": map[string]interface{}{ + "sums": map[string]interface{}{ + "cumulative_monotonic_mode": "raw_value", + }, + }, + }), + expectedMode: CumulativeMonotonicSumModeRawValue, + }, + } + + for _, testInstance := range tests { + t.Run(testInstance.name, func(t *testing.T) { + cfg := futureDefaultConfig() + err := cfg.Unmarshal(testInstance.cfgMap) + if err != nil || testInstance.err != "" { + assert.EqualError(t, err, testInstance.err) + } else { + assert.Equal(t, testInstance.expectedMode, cfg.Metrics.SumConfig.CumulativeMonotonicMode) + var warningStr []string + for _, warning := range cfg.warnings { + warningStr = append(warningStr, warning.Error()) + } + assert.ElementsMatch(t, testInstance.warnings, warningStr) + } + }) + } + +} diff --git a/exporter/datadogexporter/example/config.yaml b/exporter/datadogexporter/example/config.yaml index e8d66767f8c9..4174ed23dd99 100644 --- a/exporter/datadogexporter/example/config.yaml +++ b/exporter/datadogexporter/example/config.yaml @@ -72,6 +72,7 @@ exporters: # # metrics: ## @param send_monotonic_counter - boolean - optional - default: true + ## Deprecated: [v0.48.0] Use `metrics::sums::cumulative_monotonic_mode` instead. ## Whether to report monotonic metrics as counters or gauges (raw value). ## See https://docs.datadoghq.com/integrations/guide/prometheus-metrics/#counter ## for further details @@ -118,6 +119,16 @@ exporters: ## Whether to report sum and count as separate histogram metrics. # # send_count_sum_metrics: false + + ## @param sums - custom object - optional + ## Sums specific configuration. + ## @param cumulative_monotonic_mode - string - optional - default: to_delta + ## How to report cumulative monotonic sums. Valid values are: + ## + ## - `to_delta` to calculate delta for sum in the client side and report as Datadog counts. + ## - `raw_value` to report the raw value as a Datadog gauge. + # + # cumulative_monotonic_mode: to_delta ## @param traces - custom object - optional ## Trace exporter specific configuration. diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index e41a8da4b9f8..1341599552ea 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -95,6 +95,9 @@ func (*factory) createDefaultConfig() config.Exporter { Mode: "distributions", SendCountSum: false, }, + SumConfig: ddconfig.SumConfig{ + CumulativeMonotonicMode: ddconfig.CumulativeMonotonicSumModeToDelta, + }, }, Traces: ddconfig.TracesConfig{ diff --git a/exporter/datadogexporter/factory_test.go b/exporter/datadogexporter/factory_test.go index 625d582ba459..1158091b850b 100644 --- a/exporter/datadogexporter/factory_test.go +++ b/exporter/datadogexporter/factory_test.go @@ -85,6 +85,9 @@ func TestCreateDefaultConfig(t *testing.T) { Mode: "distributions", SendCountSum: false, }, + SumConfig: ddconfig.SumConfig{ + CumulativeMonotonicMode: ddconfig.CumulativeMonotonicSumModeToDelta, + }, }, Traces: ddconfig.TracesConfig{ @@ -158,6 +161,9 @@ func TestLoadConfig(t *testing.T) { Mode: "distributions", SendCountSum: false, }, + SumConfig: ddconfig.SumConfig{ + CumulativeMonotonicMode: ddconfig.CumulativeMonotonicSumModeToDelta, + }, }, Traces: ddconfig.TracesConfig{ @@ -206,6 +212,9 @@ func TestLoadConfig(t *testing.T) { Mode: "distributions", SendCountSum: false, }, + SumConfig: ddconfig.SumConfig{ + CumulativeMonotonicMode: ddconfig.CumulativeMonotonicSumModeToDelta, + }, }, Traces: ddconfig.TracesConfig{ @@ -294,6 +303,9 @@ func TestLoadConfigEnvVariables(t *testing.T) { Mode: "distributions", SendCountSum: false, }, + SumConfig: ddconfig.SumConfig{ + CumulativeMonotonicMode: ddconfig.CumulativeMonotonicSumModeToDelta, + }, }, apiConfig.Metrics) assert.Equal(t, ddconfig.TracesConfig{ @@ -337,6 +349,9 @@ func TestLoadConfigEnvVariables(t *testing.T) { Mode: "distributions", SendCountSum: false, }, + SumConfig: ddconfig.SumConfig{ + CumulativeMonotonicMode: ddconfig.CumulativeMonotonicSumModeToDelta, + }, }, defaultConfig.Metrics) assert.Equal(t, ddconfig.TracesConfig{ SampleRate: 1, diff --git a/exporter/datadogexporter/metrics_exporter.go b/exporter/datadogexporter/metrics_exporter.go index e24670d3957c..3a19f4a6f5d4 100644 --- a/exporter/datadogexporter/metrics_exporter.go +++ b/exporter/datadogexporter/metrics_exporter.go @@ -86,11 +86,13 @@ func translatorFromConfig(logger *zap.Logger, cfg *config.Config) (*translator.T options = append(options, translator.WithHistogramMode(translator.HistogramMode(cfg.Metrics.HistConfig.Mode))) var numberMode translator.NumberMode - if cfg.Metrics.SendMonotonic { - numberMode = translator.NumberModeCumulativeToDelta - } else { + switch cfg.Metrics.SumConfig.CumulativeMonotonicMode { + case config.CumulativeMonotonicSumModeRawValue: numberMode = translator.NumberModeRawValue + case config.CumulativeMonotonicSumModeToDelta: + numberMode = translator.NumberModeCumulativeToDelta } + options = append(options, translator.WithNumberMode(numberMode)) return translator.New(logger, options...) diff --git a/exporter/datadogexporter/metrics_exporter_test.go b/exporter/datadogexporter/metrics_exporter_test.go index a81ec7bdf96d..5a313ed48daf 100644 --- a/exporter/datadogexporter/metrics_exporter_test.go +++ b/exporter/datadogexporter/metrics_exporter_test.go @@ -47,6 +47,9 @@ func TestNewExporter(t *testing.T) { Mode: string(translator.HistogramModeDistributions), SendCountSum: false, }, + SumConfig: config.SumConfig{ + CumulativeMonotonicMode: config.CumulativeMonotonicSumModeToDelta, + }, }, } params := componenttest.NewNopExporterCreateSettings()