From 199dc34a09c057fd30da227d689ca04dacc8cecc Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 14 Aug 2023 07:21:04 -0700 Subject: [PATCH] Support default histogram selection in OTLP exporter (#4437) * Support default histogram selection in OTLP exporter * Add changes to changelog --- CHANGELOG.md | 1 + .../internal/oconf/envconfig.go | 26 +++++++ .../internal/oconf/envconfig_test.go | 71 +++++++++++++++++++ .../internal/oconf/envconfig.go | 26 +++++++ .../internal/oconf/envconfig_test.go | 71 +++++++++++++++++++ .../otlp/otlpmetric/oconf/envconfig.go.tmpl | 26 +++++++ .../otlpmetric/oconf/envconfig_test.go.tmpl | 71 +++++++++++++++++++ 7 files changed, 292 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f801e722f2..c93742eb56a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Accept 201 to 299 HTTP status as success in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` and `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#4365) - Document the `Temporality` and `Aggregation` methods of the `"go.opentelemetry.io/otel/sdk/metric".Exporter"` need to be concurrent safe. (#4381) - Expand the set of units supported by the prometheus exporter, and don't add unit suffixes if they are already present in `go.opentelemetry.op/otel/exporters/prometheus` (#4374) +- The exporters in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` support the `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` environment variable. (#4437) ### Changed diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go index a133a60e402..c08b0b6d483 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go @@ -29,6 +29,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig" "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -103,6 +104,7 @@ func getOptionsFromEnv() []GenericOption { envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), envconfig.WithDuration("METRICS_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), withEnvTemporalityPreference("METRICS_TEMPORALITY_PREFERENCE", func(t metric.TemporalitySelector) { opts = append(opts, WithTemporalitySelector(t)) }), + withEnvAggPreference("METRICS_DEFAULT_HISTOGRAM_AGGREGATION", func(a metric.AggregationSelector) { opts = append(opts, WithAggregationSelector(a)) }), ) return opts @@ -194,3 +196,27 @@ func lowMemory(ik metric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } } + +func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) { + return func(e *envconfig.EnvOptionsReader) { + if s, ok := e.GetEnvValue(n); ok { + switch strings.ToLower(s) { + case "explicit_bucket_histogram": + fn(metric.DefaultAggregationSelector) + case "base2_exponential_bucket_histogram": + fn(func(kind metric.InstrumentKind) aggregation.Aggregation { + if kind == metric.InstrumentKindHistogram { + return aggregation.Base2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + NoMinMax: false, + } + } + return metric.DefaultAggregationSelector(kind) + }) + default: + global.Warn("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION is set to an invalid value, ignoring.", "value", s) + } + } + } +} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go index d497c8e4b6c..559d9f3bc23 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -104,3 +105,73 @@ func TestWithEnvTemporalityPreference(t *testing.T) { } DefaultEnvOptionsReader.GetEnv = origReader } + +func TestWithEnvAggPreference(t *testing.T) { + origReader := DefaultEnvOptionsReader.GetEnv + tests := []struct { + name string + envValue string + want map[metric.InstrumentKind]aggregation.Aggregation + }{ + { + name: "default do not set the selector", + envValue: "", + }, + { + name: "non-normative do not set the selector", + envValue: "non-normative", + }, + { + name: "explicit_bucket_histogram", + envValue: "explicit_bucket_histogram", + want: map[metric.InstrumentKind]aggregation.Aggregation{ + metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), + metric.InstrumentKindHistogram: metric.DefaultAggregationSelector(metric.InstrumentKindHistogram), + metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), + metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), + metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), + metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), + }, + }, + { + name: "base2_exponential_bucket_histogram", + envValue: "base2_exponential_bucket_histogram", + want: map[metric.InstrumentKind]aggregation.Aggregation{ + metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), + metric.InstrumentKindHistogram: aggregation.Base2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + NoMinMax: false, + }, + metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), + metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), + metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), + metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + DefaultEnvOptionsReader.GetEnv = func(key string) string { + if key == "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION" { + return tt.envValue + } + return origReader(key) + } + cfg := Config{} + cfg = ApplyGRPCEnvConfigs(cfg) + + if tt.want == nil { + // There is no function set, the SDK's default is used. + assert.Nil(t, cfg.Metrics.AggregationSelector) + return + } + + require.NotNil(t, cfg.Metrics.AggregationSelector) + for ik, want := range tt.want { + assert.Equal(t, want, cfg.Metrics.AggregationSelector(ik)) + } + }) + } + DefaultEnvOptionsReader.GetEnv = origReader +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go index 0b8e033ace3..67f3b32f618 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go @@ -29,6 +29,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig" "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -103,6 +104,7 @@ func getOptionsFromEnv() []GenericOption { envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), envconfig.WithDuration("METRICS_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), withEnvTemporalityPreference("METRICS_TEMPORALITY_PREFERENCE", func(t metric.TemporalitySelector) { opts = append(opts, WithTemporalitySelector(t)) }), + withEnvAggPreference("METRICS_DEFAULT_HISTOGRAM_AGGREGATION", func(a metric.AggregationSelector) { opts = append(opts, WithAggregationSelector(a)) }), ) return opts @@ -194,3 +196,27 @@ func lowMemory(ik metric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } } + +func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) { + return func(e *envconfig.EnvOptionsReader) { + if s, ok := e.GetEnvValue(n); ok { + switch strings.ToLower(s) { + case "explicit_bucket_histogram": + fn(metric.DefaultAggregationSelector) + case "base2_exponential_bucket_histogram": + fn(func(kind metric.InstrumentKind) aggregation.Aggregation { + if kind == metric.InstrumentKindHistogram { + return aggregation.Base2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + NoMinMax: false, + } + } + return metric.DefaultAggregationSelector(kind) + }) + default: + global.Warn("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION is set to an invalid value, ignoring.", "value", s) + } + } + } +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go index d497c8e4b6c..559d9f3bc23 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -104,3 +105,73 @@ func TestWithEnvTemporalityPreference(t *testing.T) { } DefaultEnvOptionsReader.GetEnv = origReader } + +func TestWithEnvAggPreference(t *testing.T) { + origReader := DefaultEnvOptionsReader.GetEnv + tests := []struct { + name string + envValue string + want map[metric.InstrumentKind]aggregation.Aggregation + }{ + { + name: "default do not set the selector", + envValue: "", + }, + { + name: "non-normative do not set the selector", + envValue: "non-normative", + }, + { + name: "explicit_bucket_histogram", + envValue: "explicit_bucket_histogram", + want: map[metric.InstrumentKind]aggregation.Aggregation{ + metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), + metric.InstrumentKindHistogram: metric.DefaultAggregationSelector(metric.InstrumentKindHistogram), + metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), + metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), + metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), + metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), + }, + }, + { + name: "base2_exponential_bucket_histogram", + envValue: "base2_exponential_bucket_histogram", + want: map[metric.InstrumentKind]aggregation.Aggregation{ + metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), + metric.InstrumentKindHistogram: aggregation.Base2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + NoMinMax: false, + }, + metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), + metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), + metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), + metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + DefaultEnvOptionsReader.GetEnv = func(key string) string { + if key == "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION" { + return tt.envValue + } + return origReader(key) + } + cfg := Config{} + cfg = ApplyGRPCEnvConfigs(cfg) + + if tt.want == nil { + // There is no function set, the SDK's default is used. + assert.Nil(t, cfg.Metrics.AggregationSelector) + return + } + + require.NotNil(t, cfg.Metrics.AggregationSelector) + for ik, want := range tt.want { + assert.Equal(t, want, cfg.Metrics.AggregationSelector(ik)) + } + }) + } + DefaultEnvOptionsReader.GetEnv = origReader +} diff --git a/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl index 33e97069ce5..e1dc855b631 100644 --- a/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl +++ b/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl @@ -29,6 +29,7 @@ import ( "{{ .envconfigImportPath }}" "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -103,6 +104,7 @@ func getOptionsFromEnv() []GenericOption { envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), envconfig.WithDuration("METRICS_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), withEnvTemporalityPreference("METRICS_TEMPORALITY_PREFERENCE", func(t metric.TemporalitySelector) { opts = append(opts, WithTemporalitySelector(t)) }), + withEnvAggPreference("METRICS_DEFAULT_HISTOGRAM_AGGREGATION", func(a metric.AggregationSelector) { opts = append(opts, WithAggregationSelector(a)) }), ) return opts @@ -194,3 +196,27 @@ func lowMemory(ik metric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } } + +func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) { + return func(e *envconfig.EnvOptionsReader) { + if s, ok := e.GetEnvValue(n); ok { + switch strings.ToLower(s) { + case "explicit_bucket_histogram": + fn(metric.DefaultAggregationSelector) + case "base2_exponential_bucket_histogram": + fn(func(kind metric.InstrumentKind) aggregation.Aggregation { + if kind == metric.InstrumentKindHistogram { + return aggregation.Base2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + NoMinMax: false, + } + } + return metric.DefaultAggregationSelector(kind) + }) + default: + global.Warn("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION is set to an invalid value, ignoring.", "value", s) + } + } + } +} diff --git a/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl index d497c8e4b6c..559d9f3bc23 100644 --- a/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -104,3 +105,73 @@ func TestWithEnvTemporalityPreference(t *testing.T) { } DefaultEnvOptionsReader.GetEnv = origReader } + +func TestWithEnvAggPreference(t *testing.T) { + origReader := DefaultEnvOptionsReader.GetEnv + tests := []struct { + name string + envValue string + want map[metric.InstrumentKind]aggregation.Aggregation + }{ + { + name: "default do not set the selector", + envValue: "", + }, + { + name: "non-normative do not set the selector", + envValue: "non-normative", + }, + { + name: "explicit_bucket_histogram", + envValue: "explicit_bucket_histogram", + want: map[metric.InstrumentKind]aggregation.Aggregation{ + metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), + metric.InstrumentKindHistogram: metric.DefaultAggregationSelector(metric.InstrumentKindHistogram), + metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), + metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), + metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), + metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), + }, + }, + { + name: "base2_exponential_bucket_histogram", + envValue: "base2_exponential_bucket_histogram", + want: map[metric.InstrumentKind]aggregation.Aggregation{ + metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), + metric.InstrumentKindHistogram: aggregation.Base2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + NoMinMax: false, + }, + metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), + metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), + metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), + metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + DefaultEnvOptionsReader.GetEnv = func(key string) string { + if key == "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION" { + return tt.envValue + } + return origReader(key) + } + cfg := Config{} + cfg = ApplyGRPCEnvConfigs(cfg) + + if tt.want == nil { + // There is no function set, the SDK's default is used. + assert.Nil(t, cfg.Metrics.AggregationSelector) + return + } + + require.NotNil(t, cfg.Metrics.AggregationSelector) + for ik, want := range tt.want { + assert.Equal(t, want, cfg.Metrics.AggregationSelector(ik)) + } + }) + } + DefaultEnvOptionsReader.GetEnv = origReader +}