diff --git a/custom/src/main/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizer.java b/custom/src/main/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizer.java index 7de21b287..9d7a8a412 100644 --- a/custom/src/main/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizer.java +++ b/custom/src/main/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizer.java @@ -72,6 +72,7 @@ public void customize(DeclarativeConfigurationCustomizer customizer) { customizeResources(model); customizeUserAgent(model); customizeExperimentalRuntimeTelemetryMetrics(model); + customizeMetricsTemporality(model); return model; }); } @@ -293,4 +294,40 @@ static List addUserAgent( result.add(new NameStringValuePairModel().withName(HEADER_NAME).withValue(value)); return result; } + + private void customizeMetricsTemporality(OpenTelemetryConfigurationModel model) { + // configure otlp metric exporters temporality preference if not explicitly set. + // for users, setting it explicitly to 'cumulative' restores default SDK behavior. + // + // meter_provider: + // readers: + // - periodic: + // exporter: + // # or 'otlp_grpc' for grpc + // otlp_http: + // endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/metrics + // temporality_preference: delta + + Optional.ofNullable(model.getMeterProvider()) + .map(MeterProviderModel::getReaders) + .orElse(Collections.emptyList()) + .forEach( + reader -> { + Optional.ofNullable(reader.getPeriodic()) + .map(PeriodicMetricReaderModel::getExporter) + .ifPresent( + metricExporterModel -> { + OtlpGrpcMetricExporterModel otlpGrpc = metricExporterModel.getOtlpGrpc(); + if (otlpGrpc != null && otlpGrpc.getTemporalityPreference() == null) { + otlpGrpc.withTemporalityPreference( + OtlpHttpMetricExporterModel.ExporterTemporalityPreference.DELTA); + } + OtlpHttpMetricExporterModel otlpHttp = metricExporterModel.getOtlpHttp(); + if (otlpHttp != null && otlpHttp.getTemporalityPreference() == null) { + otlpHttp.withTemporalityPreference( + OtlpHttpMetricExporterModel.ExporterTemporalityPreference.DELTA); + } + }); + }); + } } diff --git a/custom/src/test/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizerTest.java b/custom/src/test/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizerTest.java index 5be39cebf..01a02d2fd 100644 --- a/custom/src/test/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizerTest.java +++ b/custom/src/test/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizerTest.java @@ -53,6 +53,7 @@ import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -60,9 +61,13 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Stream; +import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; class ElasticDeclarativeConfigurationCustomizerTest { @@ -275,6 +280,69 @@ private static Map userAgentHeader(String value) { return header; } + @ParameterizedTest + @MethodSource("metricExporterTemporalityValues") + void metricExporterTemporality(String protocol, @Nullable String userSetTemporality) { + + OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel(); + PushMetricExporterModel metricExporterModel = new PushMetricExporterModel(); + + switch (protocol) { + case "grpc": + OtlpHttpMetricExporterModel.ExporterTemporalityPreference grpcUserPreference = null; + if (userSetTemporality != null) { + grpcUserPreference = + OtlpHttpMetricExporterModel.ExporterTemporalityPreference.fromValue( + userSetTemporality); + } + metricExporterModel.withOtlpGrpc( + new OtlpGrpcMetricExporterModel().withTemporalityPreference(grpcUserPreference)); + break; + case "http": + OtlpHttpMetricExporterModel.ExporterTemporalityPreference httpUserPreference = null; + if (userSetTemporality != null) { + httpUserPreference = + OtlpHttpMetricExporterModel.ExporterTemporalityPreference.fromValue( + userSetTemporality); + } + metricExporterModel.withOtlpHttp( + new OtlpHttpMetricExporterModel().withTemporalityPreference(httpUserPreference)); + break; + default: + throw new IllegalArgumentException("Unsupported protocol: " + protocol); + } + + model.withMeterProvider( + new MeterProviderModel() + .withReaders( + Collections.singletonList( + new MetricReaderModel() + .withPeriodic( + new PeriodicMetricReaderModel().withExporter(metricExporterModel))))); + + applyConfigCustomize(model, new ElasticDeclarativeConfigurationCustomizer()); + + assertThat(model.getMeterProvider()).isNotNull(); + assertThat(model.getMeterProvider().getReaders()).hasSize(1); + assertThatJson(json(model.getMeterProvider().getReaders().get(0))) + .inPath("periodic.exporter.otlp_" + protocol + ".temporality_preference") + .isEqualTo(userSetTemporality != null ? userSetTemporality : "delta"); + } + + public static Stream metricExporterTemporalityValues() { + ArrayList args = new ArrayList<>(); + for (String protocol : Arrays.asList("grpc", "http")) { + for (OtlpHttpMetricExporterModel.ExporterTemporalityPreference temporality : + OtlpHttpMetricExporterModel.ExporterTemporalityPreference.values()) { + String userValue = temporality.name().toLowerCase(); + args.add(Arguments.of(protocol, userValue)); + } + // test for default value when user does not set any value + args.add(Arguments.of(protocol, null)); + } + return args.stream(); + } + private OpenTelemetryConfigurationModel applyConfigCustomize( OpenTelemetryConfigurationModel originalModel, DeclarativeConfigurationCustomizerProvider customizerProvider) {