diff --git a/exporter/datadogexporter/README.md b/exporter/datadogexporter/README.md index b8f4058177bf2..636d9111d40b0 100644 --- a/exporter/datadogexporter/README.md +++ b/exporter/datadogexporter/README.md @@ -97,5 +97,5 @@ There are a number of optional settings for configuring how to send your metrics | `send_monotonic_counter` | Cumulative monotonic metrics are sent as deltas between successive measurements. Disable this flag to send get the raw, monotonically increasing value. | `true` | | `delta_ttl` | Maximum number of seconds values from cumulative monotonic metrics are kept in memory. | 3600 | | `report_quantiles` | Whether to report quantile values for summary type metrics. | `true` | -| `histograms::mode` | Mode for histograms. Valid values are `nobuckets` (no bucket metrics), `counters` (one metric per bucket) and `distributions` (send as Datadog distributions, recommended). | `nobuckets` | +| `histograms::mode` | Mode for histograms. Valid values are `nobuckets` (no bucket metrics), `counters` (one metric per bucket) and `distributions` (send as Datadog distributions, recommended). | `distributions` | | `histograms::send_count_sum_metrics` | Whether to report sum and count for histograms as separate metrics. | `true` | diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index a846701df77b6..5a4ad8cc4f684 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -95,17 +95,17 @@ type MetricsConfig struct { // HistogramConfig customizes export of OTLP Histograms. type HistogramConfig struct { - // Mode for exporting histograms. Valid values are 'counters' or 'nobuckets'. + // Mode for exporting histograms. Valid values are 'distributions', 'counters' or 'nobuckets'. + // - 'distributions' sends histograms as Datadog distributions (recommended). // - 'counters' sends histograms as Datadog counts, one metric per bucket. // - 'nobuckets' sends no bucket histogram metrics. .sum and .count metrics will still be sent // if `send_count_sum_metrics` is enabled. - // - 'distributions' sends histograms as Datadog distributions (recommended). // - // The current default is 'nobuckets'. + // The current default is 'distributions'. Mode string `mapstructure:"mode"` // SendCountSum states if the export should send .sum and .count metrics for histograms. - // The current default is true. + // The current default is false. SendCountSum bool `mapstructure:"send_count_sum_metrics"` } diff --git a/exporter/datadogexporter/config/config_test.go b/exporter/datadogexporter/config/config_test.go index ff1351ca788a2..b0e5f8b72185f 100644 --- a/exporter/datadogexporter/config/config_test.go +++ b/exporter/datadogexporter/config/config_test.go @@ -206,7 +206,7 @@ func TestErrorReportBuckets(t *testing.T) { for _, testInstance := range tests { t.Run(testInstance.name, func(t *testing.T) { // default config for buckets - config := Config{Metrics: MetricsConfig{HistConfig: HistogramConfig{Mode: histogramModeNoBuckets}}} + config := Config{Metrics: MetricsConfig{HistConfig: HistogramConfig{Mode: histogramModeDistributions}}} configMap := colconfig.NewMapFromStringMap(testInstance.stringMap) err := config.Unmarshal(configMap) diff --git a/exporter/datadogexporter/example/config.yaml b/exporter/datadogexporter/example/config.yaml index 7f64dfb07be31..5d1f41e6174ca 100644 --- a/exporter/datadogexporter/example/config.yaml +++ b/exporter/datadogexporter/example/config.yaml @@ -99,14 +99,14 @@ exporters: ## @param histograms - custom object - optional ## Histograms specific configuration. - ## @param mode - string - optional - default: nobuckets + ## @param mode - string - optional - default: distributions ## How to report histograms. Valid values are: ## + ## - `distributions` to report metrics as Datadog distributions (recommended). ## - `nobuckets` to not report bucket metrics, ## - `counters` to report one metric per histogram bucket. - ## - `distributions` to report metrics as Datadog distributions (recommended). # - # mode: nobuckets + # mode: distributions ## @param send_count_sum_metrics - boolean - optional - default: true ## Whether to report sum and count as separate histogram metrics. diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index b5cd4555536b7..716483e2bbfd5 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -77,8 +77,8 @@ func createDefaultConfig() config.Exporter { InstrumentationLibraryMetadataAsTags: false, }, HistConfig: ddconfig.HistogramConfig{ - Mode: "nobuckets", - SendCountSum: true, + Mode: "distributions", + SendCountSum: false, }, }, @@ -115,7 +115,7 @@ func createMetricsExporter( } // TODO: Remove after changing the default mode. - set.Logger.Warn("Default histograms configuration will change to mode 'distributions' and no .count and .sum metrics in a future release.") + set.Logger.Info("Histograms configuration now defaults to 'distributions' mode and no .count and .sum metrics.") ctx, cancel := context.WithCancel(ctx) var pushMetricsFn consumerhelper.ConsumeMetricsFunc diff --git a/exporter/datadogexporter/factory_test.go b/exporter/datadogexporter/factory_test.go index b0f236aecccb5..08c24d5e29933 100644 --- a/exporter/datadogexporter/factory_test.go +++ b/exporter/datadogexporter/factory_test.go @@ -61,8 +61,8 @@ func TestCreateDefaultConfig(t *testing.T) { SendMonotonic: true, Quantiles: true, HistConfig: ddconfig.HistogramConfig{ - Mode: "nobuckets", - SendCountSum: true, + Mode: "distributions", + SendCountSum: false, }, }, @@ -134,8 +134,8 @@ func TestLoadConfig(t *testing.T) { SendMonotonic: true, Quantiles: true, HistConfig: ddconfig.HistogramConfig{ - Mode: "nobuckets", - SendCountSum: true, + Mode: "distributions", + SendCountSum: false, }, }, @@ -182,8 +182,8 @@ func TestLoadConfig(t *testing.T) { DeltaTTL: 3600, Quantiles: true, HistConfig: ddconfig.HistogramConfig{ - Mode: "nobuckets", - SendCountSum: true, + Mode: "distributions", + SendCountSum: false, }, }, @@ -272,8 +272,8 @@ func TestLoadConfigEnvVariables(t *testing.T) { Quantiles: false, DeltaTTL: 3600, HistConfig: ddconfig.HistogramConfig{ - Mode: "nobuckets", - SendCountSum: true, + Mode: "distributions", + SendCountSum: false, }, }, @@ -323,8 +323,8 @@ func TestLoadConfigEnvVariables(t *testing.T) { DeltaTTL: 3600, Quantiles: true, HistConfig: ddconfig.HistogramConfig{ - Mode: "nobuckets", - SendCountSum: true, + Mode: "distributions", + SendCountSum: false, }, }, diff --git a/exporter/datadogexporter/internal/metrics/consumer_test.go b/exporter/datadogexporter/internal/metrics/consumer_test.go index 87e7395be3601..4f49dda96c011 100644 --- a/exporter/datadogexporter/internal/metrics/consumer_test.go +++ b/exporter/datadogexporter/internal/metrics/consumer_test.go @@ -36,8 +36,7 @@ func (t testProvider) Hostname(context.Context) (string, error) { func newTranslator(t *testing.T, logger *zap.Logger) *translator.Translator { tr, err := translator.New(logger, - translator.WithCountSumMetrics(), - translator.WithHistogramMode(translator.HistogramModeNoBuckets), + translator.WithHistogramMode(translator.HistogramModeDistributions), translator.WithNumberMode(translator.NumberModeCumulativeToDelta), translator.WithFallbackHostnameProvider(testProvider("fallbackHostname")), ) diff --git a/exporter/datadogexporter/internal/translator/metrics_translator_test.go b/exporter/datadogexporter/internal/translator/metrics_translator_test.go index 70ee01e2ca6a1..b7ac7e12dc645 100644 --- a/exporter/datadogexporter/internal/translator/metrics_translator_test.go +++ b/exporter/datadogexporter/internal/translator/metrics_translator_test.go @@ -110,8 +110,7 @@ func newTranslator(t *testing.T, logger *zap.Logger) *Translator { tr, err := New( logger, WithFallbackHostnameProvider(testProvider("fallbackHostname")), - WithCountSumMetrics(), - WithHistogramMode(HistogramModeNoBuckets), + WithHistogramMode(HistogramModeDistributions), WithNumberMode(NumberModeCumulativeToDelta), ) @@ -536,31 +535,25 @@ func TestMapDeltaHistogramMetrics(t *testing.T) { point.SetExplicitBounds([]float64{0}) point.SetTimestamp(ts) - noBuckets := []metric{ + counts := []metric{ newCount("doubleHist.test.count", uint64(ts), 20, []string{}), newCount("doubleHist.test.sum", uint64(ts), math.Pi, []string{}), } - buckets := []metric{ + countsAttributeTags := []metric{ + newCount("doubleHist.test.count", uint64(ts), 20, []string{"attribute_tag:attribute_value"}), + newCount("doubleHist.test.sum", uint64(ts), math.Pi, []string{"attribute_tag:attribute_value"}), + } + + bucketsCounts := []metric{ newCount("doubleHist.test.bucket", uint64(ts), 2, []string{"lower_bound:-inf", "upper_bound:0"}), newCount("doubleHist.test.bucket", uint64(ts), 18, []string{"lower_bound:0", "upper_bound:inf"}), } - ctx := context.Background() - tr := newTranslator(t, zap.NewNop()) - delta := true - - tr.cfg.HistMode = HistogramModeNoBuckets - consumer := &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{}, "") - assert.ElementsMatch(t, noBuckets, consumer.metrics) - assert.Empty(t, consumer.sketches) - - tr.cfg.HistMode = HistogramModeCounters - consumer = &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{}, "") - assert.ElementsMatch(t, append(noBuckets, buckets...), consumer.metrics) - assert.Empty(t, consumer.sketches) + bucketsCountsAttributeTags := []metric{ + newCount("doubleHist.test.bucket", uint64(ts), 2, []string{"lower_bound:-inf", "upper_bound:0", "attribute_tag:attribute_value"}), + newCount("doubleHist.test.bucket", uint64(ts), 18, []string{"lower_bound:0", "upper_bound:inf", "attribute_tag:attribute_value"}), + } sketches := []sketch{ newSketch("doubleHist.test", uint64(ts), summary.Summary{ @@ -574,35 +567,6 @@ func TestMapDeltaHistogramMetrics(t *testing.T) { ), } - tr.cfg.HistMode = HistogramModeDistributions - consumer = &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{}, "") - assert.ElementsMatch(t, noBuckets, consumer.metrics) - assert.ElementsMatch(t, sketches, consumer.sketches) - - // With attribute tags - noBucketsAttributeTags := []metric{ - newCount("doubleHist.test.count", uint64(ts), 20, []string{"attribute_tag:attribute_value"}), - newCount("doubleHist.test.sum", uint64(ts), math.Pi, []string{"attribute_tag:attribute_value"}), - } - - bucketsAttributeTags := []metric{ - newCount("doubleHist.test.bucket", uint64(ts), 2, []string{"lower_bound:-inf", "upper_bound:0", "attribute_tag:attribute_value"}), - newCount("doubleHist.test.bucket", uint64(ts), 18, []string{"lower_bound:0", "upper_bound:inf", "attribute_tag:attribute_value"}), - } - - tr.cfg.HistMode = HistogramModeNoBuckets - consumer = &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{"attribute_tag:attribute_value"}, "") - assert.ElementsMatch(t, noBucketsAttributeTags, consumer.metrics) - assert.Empty(t, consumer.sketches) - - tr.cfg.HistMode = HistogramModeCounters - consumer = &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{"attribute_tag:attribute_value"}, "") - assert.ElementsMatch(t, append(noBucketsAttributeTags, bucketsAttributeTags...), consumer.metrics) - assert.Empty(t, consumer.sketches) - sketchesAttributeTags := []sketch{ newSketch("doubleHist.test", uint64(ts), summary.Summary{ Min: 0, @@ -615,11 +579,111 @@ func TestMapDeltaHistogramMetrics(t *testing.T) { ), } - tr.cfg.HistMode = HistogramModeDistributions - consumer = &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{"attribute_tag:attribute_value"}, "") - assert.ElementsMatch(t, noBucketsAttributeTags, consumer.metrics) - assert.ElementsMatch(t, sketchesAttributeTags, consumer.sketches) + ctx := context.Background() + delta := true + + tests := []struct { + name string + histogramMode HistogramMode + sendCountSum bool + tags []string + expectedMetrics []metric + expectedSketches []sketch + }{ + { + name: "No buckets: send count & sum metrics, no attribute tags", + histogramMode: HistogramModeNoBuckets, + sendCountSum: true, + tags: []string{}, + expectedMetrics: counts, + expectedSketches: []sketch{}, + }, + { + name: "No buckets: send count & sum metrics, attribute tags", + histogramMode: HistogramModeNoBuckets, + sendCountSum: true, + tags: []string{"attribute_tag:attribute_value"}, + expectedMetrics: countsAttributeTags, + expectedSketches: []sketch{}, + }, + { + name: "Counters: do not send count & sum metrics, no tags", + histogramMode: HistogramModeCounters, + sendCountSum: false, + tags: []string{}, + expectedMetrics: bucketsCounts, + expectedSketches: []sketch{}, + }, + { + name: "Counters: do not send count & sum metrics, attribute tags", + histogramMode: HistogramModeCounters, + sendCountSum: false, + tags: []string{"attribute_tag:attribute_value"}, + expectedMetrics: bucketsCountsAttributeTags, + expectedSketches: []sketch{}, + }, + { + name: "Counters: send count & sum metrics, no tags", + histogramMode: HistogramModeCounters, + sendCountSum: true, + tags: []string{}, + expectedMetrics: append(counts, bucketsCounts...), + expectedSketches: []sketch{}, + }, + { + name: "Counters: send count & sum metrics, attribute tags", + histogramMode: HistogramModeCounters, + sendCountSum: true, + tags: []string{"attribute_tag:attribute_value"}, + expectedMetrics: append(countsAttributeTags, bucketsCountsAttributeTags...), + expectedSketches: []sketch{}, + }, + { + name: "Distributions: do not send count & sum metrics, no tags", + histogramMode: HistogramModeDistributions, + sendCountSum: false, + tags: []string{}, + expectedMetrics: []metric{}, + expectedSketches: sketches, + }, + { + name: "Distributions: do not send count & sum metrics, attribute tags", + histogramMode: HistogramModeDistributions, + sendCountSum: false, + tags: []string{"attribute_tag:attribute_value"}, + expectedMetrics: []metric{}, + expectedSketches: sketchesAttributeTags, + }, + { + name: "Distributions: send count & sum metrics, no tags", + histogramMode: HistogramModeDistributions, + sendCountSum: true, + tags: []string{}, + expectedMetrics: counts, + expectedSketches: sketches, + }, + { + name: "Distributions: send count & sum metrics, attribute tags", + histogramMode: HistogramModeDistributions, + sendCountSum: true, + tags: []string{"attribute_tag:attribute_value"}, + expectedMetrics: countsAttributeTags, + expectedSketches: sketchesAttributeTags, + }, + } + + for _, testInstance := range tests { + t.Run(testInstance.name, func(t *testing.T) { + tr := newTranslator(t, zap.NewNop()) + tr.cfg.HistMode = testInstance.histogramMode + tr.cfg.SendCountSum = testInstance.sendCountSum + consumer := &mockFullConsumer{} + + tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, testInstance.tags, "") + assert.ElementsMatch(t, consumer.metrics, testInstance.expectedMetrics) + assert.ElementsMatch(t, consumer.sketches, testInstance.expectedSketches) + }) + } } func TestMapCumulativeHistogramMetrics(t *testing.T) { @@ -638,30 +702,17 @@ func TestMapCumulativeHistogramMetrics(t *testing.T) { point.SetExplicitBounds([]float64{0}) point.SetTimestamp(seconds(2)) - expectedCounts := []metric{ + counts := []metric{ newCount("doubleHist.test.count", uint64(seconds(2)), 30, []string{}), newCount("doubleHist.test.sum", uint64(seconds(2)), 20, []string{}), } - expectedBuckets := []metric{ + bucketsCounts := []metric{ newCount("doubleHist.test.bucket", uint64(seconds(2)), 11, []string{"lower_bound:-inf", "upper_bound:0"}), newCount("doubleHist.test.bucket", uint64(seconds(2)), 19, []string{"lower_bound:0", "upper_bound:inf"}), } - ctx := context.Background() - tr := newTranslator(t, zap.NewNop()) - delta := false - - tr.cfg.HistMode = HistogramModeCounters - consumer := &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{}, "") - assert.Empty(t, consumer.sketches) - assert.ElementsMatch(t, - consumer.metrics, - append(expectedCounts, expectedBuckets...), - ) - - expectedSketches := []sketch{ + sketches := []sketch{ newSketch("doubleHist.test", uint64(seconds(2)), summary.Summary{ Min: 0, Max: 0, @@ -673,20 +724,65 @@ func TestMapCumulativeHistogramMetrics(t *testing.T) { ), } - tr = newTranslator(t, zap.NewNop()) - delta = false + ctx := context.Background() + delta := false - tr.cfg.HistMode = HistogramModeDistributions - consumer = &mockFullConsumer{} - tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{}, "") - assert.ElementsMatch(t, - consumer.metrics, - expectedCounts, - ) - assert.ElementsMatch(t, - consumer.sketches, - expectedSketches, - ) + tests := []struct { + name string + histogramMode HistogramMode + sendCountSum bool + expectedMetrics []metric + expectedSketches []sketch + }{ + { + name: "No buckets: send count & sum metrics", + histogramMode: HistogramModeNoBuckets, + sendCountSum: true, + expectedMetrics: counts, + expectedSketches: []sketch{}, + }, + { + name: "Counters: do not send count & sum metrics", + histogramMode: HistogramModeCounters, + sendCountSum: false, + expectedMetrics: bucketsCounts, + expectedSketches: []sketch{}, + }, + { + name: "Counters: send count & sum metrics", + histogramMode: HistogramModeCounters, + sendCountSum: true, + expectedMetrics: append(counts, bucketsCounts...), + expectedSketches: []sketch{}, + }, + { + name: "Distributions: do not send count & sum metrics", + histogramMode: HistogramModeDistributions, + sendCountSum: false, + expectedMetrics: []metric{}, + expectedSketches: sketches, + }, + { + name: "Distributions: send count & sum metrics", + histogramMode: HistogramModeDistributions, + sendCountSum: true, + expectedMetrics: counts, + expectedSketches: sketches, + }, + } + + for _, testInstance := range tests { + t.Run(testInstance.name, func(t *testing.T) { + tr := newTranslator(t, zap.NewNop()) + tr.cfg.HistMode = testInstance.histogramMode + tr.cfg.SendCountSum = testInstance.sendCountSum + consumer := &mockFullConsumer{} + + tr.mapHistogramMetrics(ctx, consumer, "doubleHist.test", slice, delta, []string{}, "") + assert.ElementsMatch(t, consumer.metrics, testInstance.expectedMetrics) + assert.ElementsMatch(t, consumer.sketches, testInstance.expectedSketches) + }) + } } func TestLegacyBucketsTags(t *testing.T) { @@ -1003,18 +1099,24 @@ func createTestMetrics() pdata.Metrics { return md } -func testGauge(name string, val float64) metric { +func newGaugeWithHostname(name string, val float64) metric { m := newGauge(name, 0, val, []string{}) m.host = testHostname return m } -func testCount(name string, val float64, seconds uint64) metric { +func newCountWithHostname(name string, val float64, seconds uint64) metric { m := newCount(name, seconds*1e9, val, []string{}) m.host = testHostname return m } +func newSketchWithHostname(name string, summary summary.Summary, seconds uint64) sketch { + s := newSketch(name, seconds, summary, []string{}) + s.host = testHostname + return s +} + func TestMapMetrics(t *testing.T) { md := createTestMetrics() @@ -1027,22 +1129,29 @@ func TestMapMetrics(t *testing.T) { require.NoError(t, err) assert.ElementsMatch(t, consumer.metrics, []metric{ - testGauge("int.gauge", 1), - testGauge("double.gauge", math.Pi), - testCount("int.delta.sum", 2, 0), - testCount("double.delta.sum", math.E, 0), - testCount("int.delta.monotonic.sum", 2, 0), - testCount("double.delta.monotonic.sum", math.E, 0), - testCount("double.histogram.sum", math.Phi, 0), - testCount("double.histogram.count", 20, 0), - testCount("summary.sum", 10_000, 2), - testCount("summary.count", 100, 2), - testGauge("int.cumulative.sum", 4), - testGauge("double.cumulative.sum", 4), - testCount("int.cumulative.monotonic.sum", 3, 2), - testCount("double.cumulative.monotonic.sum", math.Pi, 2), + newGaugeWithHostname("int.gauge", 1), + newGaugeWithHostname("double.gauge", math.Pi), + newCountWithHostname("int.delta.sum", 2, 0), + newCountWithHostname("double.delta.sum", math.E, 0), + newCountWithHostname("int.delta.monotonic.sum", 2, 0), + newCountWithHostname("double.delta.monotonic.sum", math.E, 0), + newCountWithHostname("summary.sum", 10_000, 2), + newCountWithHostname("summary.count", 100, 2), + newGaugeWithHostname("int.cumulative.sum", 4), + newGaugeWithHostname("double.cumulative.sum", 4), + newCountWithHostname("int.cumulative.monotonic.sum", 3, 2), + newCountWithHostname("double.cumulative.monotonic.sum", math.Pi, 2), + }) + + assert.ElementsMatch(t, consumer.sketches, []sketch{ + newSketchWithHostname("double.histogram", summary.Summary{ + Min: 0, + Max: 0, + Sum: 0, + Avg: 0, + Cnt: 20, + }, 0), }) - assert.Empty(t, consumer.sketches) // One metric type was unknown or unsupported assert.Equal(t, observed.FilterMessage("Unknown or unsupported metric type").Len(), 1) @@ -1153,12 +1262,19 @@ func TestNaNMetrics(t *testing.T) { require.NoError(t, err) assert.ElementsMatch(t, consumer.metrics, []metric{ - testCount("nan.histogram.count", 20, 0), - testCount("nan.summary.count", 100, 2), + newCountWithHostname("nan.summary.count", 100, 2), }) - assert.Empty(t, consumer.sketches) + assert.ElementsMatch(t, consumer.sketches, []sketch{ + newSketchWithHostname("nan.histogram", summary.Summary{ + Min: 0, + Max: 0, + Sum: 0, + Avg: 0, + Cnt: 20, + }, 0), + }) // One metric type was unknown or unsupported - assert.Equal(t, observed.FilterMessage("Unsupported metric value").Len(), 7) + assert.Equal(t, observed.FilterMessage("Unsupported metric value").Len(), 6) } diff --git a/exporter/datadogexporter/metrics_exporter_test.go b/exporter/datadogexporter/metrics_exporter_test.go index b7ad0eeeb96fa..45ee85d01a2a9 100644 --- a/exporter/datadogexporter/metrics_exporter_test.go +++ b/exporter/datadogexporter/metrics_exporter_test.go @@ -44,8 +44,8 @@ func TestNewExporter(t *testing.T) { }, DeltaTTL: 3600, HistConfig: config.HistogramConfig{ - Mode: string(translator.HistogramModeNoBuckets), - SendCountSum: true, + Mode: string(translator.HistogramModeDistributions), + SendCountSum: false, }, }, }