diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt index df26146497b..d8fcd994b8d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt @@ -1,2 +1,7 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode() +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode() diff --git a/exporters/common/build.gradle.kts b/exporters/common/build.gradle.kts index d6cb5ebe86f..b8868565287 100644 --- a/exporters/common/build.gradle.kts +++ b/exporters/common/build.gradle.kts @@ -11,6 +11,7 @@ otelJava.moduleName.set("io.opentelemetry.exporter.internal") val versions: Map by project dependencies { api(project(":api:all")) + api(project(":sdk-extensions:autoconfigure-spi")) compileOnly(project(":sdk:common")) diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java index d45bdb09283..b19aad8e131 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterBuilderUtil.java @@ -5,8 +5,13 @@ package io.opentelemetry.exporter.internal; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.common.export.MemoryMode; import java.net.URI; import java.net.URISyntaxException; +import java.util.Locale; +import java.util.function.Consumer; /** * Utilities for exporter builders. @@ -33,5 +38,21 @@ public static URI validateEndpoint(String endpoint) { return uri; } + /** Invoke the {@code memoryModeConsumer} with the configured {@link MemoryMode}. */ + public static void configureExporterMemoryMode( + ConfigProperties config, Consumer memoryModeConsumer) { + String memoryModeStr = config.getString("otel.java.experimental.exporter.memory_mode"); + if (memoryModeStr == null) { + return; + } + MemoryMode memoryMode; + try { + memoryMode = MemoryMode.valueOf(memoryModeStr.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Unrecognized memory mode: " + memoryModeStr, e); + } + memoryModeConsumer.accept(memoryMode); + } + private ExporterBuilderUtil() {} } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporter.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporter.java index 922baaaec5b..d9b0dd7d2c9 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporter.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporter.java @@ -9,6 +9,7 @@ import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -31,16 +32,19 @@ public final class OtlpHttpMetricExporter implements MetricExporter { private final HttpExporter delegate; private final AggregationTemporalitySelector aggregationTemporalitySelector; private final DefaultAggregationSelector defaultAggregationSelector; + private final MemoryMode memoryMode; OtlpHttpMetricExporter( HttpExporterBuilder builder, HttpExporter delegate, AggregationTemporalitySelector aggregationTemporalitySelector, - DefaultAggregationSelector defaultAggregationSelector) { + DefaultAggregationSelector defaultAggregationSelector, + MemoryMode memoryMode) { this.builder = builder; this.delegate = delegate; this.aggregationTemporalitySelector = aggregationTemporalitySelector; this.defaultAggregationSelector = defaultAggregationSelector; + this.memoryMode = memoryMode; } /** @@ -72,7 +76,7 @@ public static OtlpHttpMetricExporterBuilder builder() { * @since 1.29.0 */ public OtlpHttpMetricExporterBuilder toBuilder() { - return new OtlpHttpMetricExporterBuilder(builder.copy()); + return new OtlpHttpMetricExporterBuilder(builder.copy(), memoryMode); } @Override @@ -85,6 +89,11 @@ public Aggregation getDefaultAggregation(InstrumentType instrumentType) { return defaultAggregationSelector.getDefaultAggregation(instrumentType); } + @Override + public MemoryMode getMemoryMode() { + return memoryMode; + } + /** * Submits all the given metrics in a single batch to the OpenTelemetry collector. * diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java index 8fa21d1aa50..8390d5e03b3 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java @@ -15,6 +15,7 @@ import io.opentelemetry.exporter.internal.http.HttpExporterBuilder; import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.export.ProxyOptions; import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.metrics.InstrumentType; @@ -39,6 +40,7 @@ public final class OtlpHttpMetricExporterBuilder { private static final AggregationTemporalitySelector DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR = AggregationTemporalitySelector.alwaysCumulative(); + private static final MemoryMode DEFAULT_MEMORY_MODE = MemoryMode.IMMUTABLE_DATA; private final HttpExporterBuilder delegate; private AggregationTemporalitySelector aggregationTemporalitySelector = @@ -46,15 +48,18 @@ public final class OtlpHttpMetricExporterBuilder { private DefaultAggregationSelector defaultAggregationSelector = DefaultAggregationSelector.getDefault(); + private MemoryMode memoryMode; - OtlpHttpMetricExporterBuilder(HttpExporterBuilder delegate) { + OtlpHttpMetricExporterBuilder( + HttpExporterBuilder delegate, MemoryMode memoryMode) { this.delegate = delegate; + this.memoryMode = memoryMode; delegate.setMeterProvider(MeterProvider::noop); OtlpUserAgent.addUserAgentHeader(delegate::addConstantHeaders); } OtlpHttpMetricExporterBuilder() { - this(new HttpExporterBuilder<>("otlp", "metric", DEFAULT_ENDPOINT)); + this(new HttpExporterBuilder<>("otlp", "metric", DEFAULT_ENDPOINT), DEFAULT_MEMORY_MODE); } /** @@ -227,6 +232,13 @@ public OtlpHttpMetricExporterBuilder setProxyOptions(ProxyOptions proxyOptions) return this; } + /** Set the {@link MemoryMode}. */ + OtlpHttpMetricExporterBuilder setMemoryMode(MemoryMode memoryMode) { + requireNonNull(memoryMode, "memoryMode"); + this.memoryMode = memoryMode; + return this; + } + OtlpHttpMetricExporterBuilder exportAsJson() { delegate.exportAsJson(); return this; @@ -239,6 +251,10 @@ OtlpHttpMetricExporterBuilder exportAsJson() { */ public OtlpHttpMetricExporter build() { return new OtlpHttpMetricExporter( - delegate, delegate.build(), aggregationTemporalitySelector, defaultAggregationSelector); + delegate, + delegate.build(), + aggregationTemporalitySelector, + defaultAggregationSelector, + memoryMode); } } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java index da02c32dee5..5bd08742b09 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java @@ -7,8 +7,11 @@ import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; @@ -19,6 +22,8 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; @@ -206,6 +211,44 @@ public static void configureOtlpHistogramDefaultAggregation( } } + /** + * Calls {@code #setMemoryMode} on the {@code Otlp{Protocol}MetricExporterBuilder} with the {@code + * memoryMode}. + */ + public static void setMemoryModeOnOtlpMetricExporterBuilder( + Object builder, MemoryMode memoryMode) { + try { + if (builder instanceof OtlpGrpcMetricExporterBuilder) { + // Calling getDeclaredMethod causes all private methods to be read, which causes a + // ClassNotFoundException when running with the OkHttHttpProvider as the private + // setManagedChanel(io.grpc.ManagedChannel) is reached and io.grpc.ManagedChannel is not on + // the classpath. io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricUtil provides a layer + // of indirection which avoids scanning the OtlpGrpcMetricExporterBuilder private methods. + Class otlpGrpcMetricUtil = + Class.forName("io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricUtil"); + Method method = + otlpGrpcMetricUtil.getDeclaredMethod( + "setMemoryMode", OtlpGrpcMetricExporterBuilder.class, MemoryMode.class); + method.setAccessible(true); + method.invoke(null, builder, memoryMode); + } else if (builder instanceof OtlpHttpMetricExporterBuilder) { + Method method = + OtlpHttpMetricExporterBuilder.class.getDeclaredMethod( + "setMemoryMode", MemoryMode.class); + method.setAccessible(true); + method.invoke(builder, memoryMode); + } else { + throw new IllegalArgumentException( + "Can only set memory mode on OtlpHttpMetricExporterBuilder and OtlpGrpcMetricExporterBuilder."); + } + } catch (NoSuchMethodException + | InvocationTargetException + | IllegalAccessException + | ClassNotFoundException e) { + throw new IllegalStateException("Error calling setMemoryMode.", e); + } + } + private static URL createUrl(URL context, String spec) { try { return new URL(context, spec); diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java index 3d424b498f0..cfea314f028 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java @@ -9,6 +9,7 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_GRPC; import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; +import io.opentelemetry.exporter.internal.ExporterBuilderUtil; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; @@ -48,6 +49,10 @@ public MetricExporter createExporter(ConfigProperties config) { config, builder::setAggregationTemporalitySelector); OtlpConfigUtil.configureOtlpHistogramDefaultAggregation( config, builder::setDefaultAggregationSelector); + ExporterBuilderUtil.configureExporterMemoryMode( + config, + memoryMode -> + OtlpConfigUtil.setMemoryModeOnOtlpMetricExporterBuilder(builder, memoryMode)); return builder.build(); } else if (protocol.equals(PROTOCOL_GRPC)) { @@ -67,6 +72,10 @@ public MetricExporter createExporter(ConfigProperties config) { config, builder::setAggregationTemporalitySelector); OtlpConfigUtil.configureOtlpHistogramDefaultAggregation( config, builder::setDefaultAggregationSelector); + ExporterBuilderUtil.configureExporterMemoryMode( + config, + memoryMode -> + OtlpConfigUtil.setMemoryModeOnOtlpMetricExporterBuilder(builder, memoryMode)); return builder.build(); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporter.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporter.java index e006675f28c..f6e5f635ee9 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporter.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporter.java @@ -9,6 +9,7 @@ import io.opentelemetry.exporter.internal.grpc.GrpcExporterBuilder; import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -31,6 +32,7 @@ public final class OtlpGrpcMetricExporter implements MetricExporter { private final GrpcExporter delegate; private final AggregationTemporalitySelector aggregationTemporalitySelector; private final DefaultAggregationSelector defaultAggregationSelector; + private final MemoryMode memoryMode; /** * Returns a new {@link OtlpGrpcMetricExporter} using the default values. @@ -57,11 +59,13 @@ public static OtlpGrpcMetricExporterBuilder builder() { GrpcExporterBuilder builder, GrpcExporter delegate, AggregationTemporalitySelector aggregationTemporalitySelector, - DefaultAggregationSelector defaultAggregationSelector) { + DefaultAggregationSelector defaultAggregationSelector, + MemoryMode memoryMode) { this.builder = builder; this.delegate = delegate; this.aggregationTemporalitySelector = aggregationTemporalitySelector; this.defaultAggregationSelector = defaultAggregationSelector; + this.memoryMode = memoryMode; } /** @@ -72,7 +76,7 @@ public static OtlpGrpcMetricExporterBuilder builder() { * @since 1.29.0 */ public OtlpGrpcMetricExporterBuilder toBuilder() { - return new OtlpGrpcMetricExporterBuilder(builder.copy()); + return new OtlpGrpcMetricExporterBuilder(builder.copy(), memoryMode); } @Override @@ -85,6 +89,11 @@ public Aggregation getDefaultAggregation(InstrumentType instrumentType) { return defaultAggregationSelector.getDefaultAggregation(instrumentType); } + @Override + public MemoryMode getMemoryMode() { + return memoryMode; + } + /** * Submits all the given metrics in a single batch to the OpenTelemetry collector. * diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java index 2fc86bab6bd..782907cd27c 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java @@ -16,6 +16,7 @@ import io.opentelemetry.exporter.internal.grpc.GrpcExporterBuilder; import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; @@ -46,6 +47,7 @@ public final class OtlpGrpcMetricExporterBuilder { private static final long DEFAULT_TIMEOUT_SECS = 10; private static final AggregationTemporalitySelector DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR = AggregationTemporalitySelector.alwaysCumulative(); + private static final MemoryMode DEFAULT_MEMORY_MODE = MemoryMode.IMMUTABLE_DATA; // Visible for testing final GrpcExporterBuilder delegate; @@ -55,9 +57,12 @@ public final class OtlpGrpcMetricExporterBuilder { private DefaultAggregationSelector defaultAggregationSelector = DefaultAggregationSelector.getDefault(); + private MemoryMode memoryMode; - OtlpGrpcMetricExporterBuilder(GrpcExporterBuilder delegate) { + OtlpGrpcMetricExporterBuilder( + GrpcExporterBuilder delegate, MemoryMode memoryMode) { this.delegate = delegate; + this.memoryMode = memoryMode; delegate.setMeterProvider(MeterProvider::noop); OtlpUserAgent.addUserAgentHeader(delegate::addConstantHeader); } @@ -70,7 +75,8 @@ public final class OtlpGrpcMetricExporterBuilder { DEFAULT_TIMEOUT_SECS, DEFAULT_ENDPOINT, () -> MarshalerMetricsServiceGrpc::newFutureStub, - GRPC_ENDPOINT_PATH)); + GRPC_ENDPOINT_PATH), + DEFAULT_MEMORY_MODE); } /** @@ -259,6 +265,13 @@ public OtlpGrpcMetricExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { return this; } + /** Set the {@link MemoryMode}. */ + OtlpGrpcMetricExporterBuilder setMemoryMode(MemoryMode memoryMode) { + requireNonNull(memoryMode, "memoryMode"); + this.memoryMode = memoryMode; + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * @@ -266,6 +279,10 @@ public OtlpGrpcMetricExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { */ public OtlpGrpcMetricExporter build() { return new OtlpGrpcMetricExporter( - delegate, delegate.build(), aggregationTemporalitySelector, defaultAggregationSelector); + delegate, + delegate.build(), + aggregationTemporalitySelector, + defaultAggregationSelector, + memoryMode); } } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricUtil.java new file mode 100644 index 00000000000..f99243cb0ba --- /dev/null +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricUtil.java @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.otlp.metrics; + +import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil; +import io.opentelemetry.sdk.common.export.MemoryMode; + +final class OtlpGrpcMetricUtil { + + private OtlpGrpcMetricUtil() {} + + /** See {@link OtlpConfigUtil#setMemoryModeOnOtlpMetricExporterBuilder(Object, MemoryMode)}. */ + static void setMemoryMode(OtlpGrpcMetricExporterBuilder builder, MemoryMode memoryMode) { + builder.setMemoryMode(memoryMode); + } +} diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java index f1ff7a71f02..36e6b88cfa8 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java @@ -20,6 +20,7 @@ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.IOException; import java.nio.file.Files; @@ -127,6 +128,7 @@ void createExporter_GrpcDefaults() { verify(grpcBuilder, never()).setTrustedCertificates(any()); verify(grpcBuilder, never()).setClientTls(any(), any()); assertThat(grpcBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + assertThat(exporter.getMemoryMode()).isEqualTo(MemoryMode.IMMUTABLE_DATA); } Mockito.verifyNoInteractions(httpBuilder); } @@ -176,6 +178,7 @@ void createExporter_GrpcWithSignalConfiguration() throws CertificateEncodingExce config.put("otel.exporter.otlp.metrics.compression", "gzip"); config.put("otel.exporter.otlp.timeout", "1s"); config.put("otel.exporter.otlp.metrics.timeout", "15s"); + config.put("otel.java.experimental.exporter.memory_mode", "reusable_data"); try (MetricExporter exporter = provider.createExporter(DefaultConfigProperties.createFromMap(config))) { @@ -188,6 +191,7 @@ void createExporter_GrpcWithSignalConfiguration() throws CertificateEncodingExce verify(grpcBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); verify(grpcBuilder) .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(exporter.getMemoryMode()).isEqualTo(MemoryMode.REUSABLE_DATA); } Mockito.verifyNoInteractions(httpBuilder); } @@ -208,6 +212,7 @@ void createExporter_HttpDefaults() { verify(httpBuilder, never()).setTrustedCertificates(any()); verify(httpBuilder, never()).setClientTls(any(), any()); assertThat(httpBuilder).extracting("delegate").extracting("retryPolicy").isNull(); + assertThat(exporter.getMemoryMode()).isEqualTo(MemoryMode.IMMUTABLE_DATA); } Mockito.verifyNoInteractions(grpcBuilder); } @@ -260,6 +265,7 @@ void createExporter_HttpWithSignalConfiguration() throws CertificateEncodingExce config.put("otel.exporter.otlp.metrics.compression", "gzip"); config.put("otel.exporter.otlp.timeout", "1s"); config.put("otel.exporter.otlp.metrics.timeout", "15s"); + config.put("otel.java.experimental.exporter.memory_mode", "reusable_data"); try (MetricExporter exporter = provider.createExporter(DefaultConfigProperties.createFromMap(config))) { @@ -272,6 +278,7 @@ void createExporter_HttpWithSignalConfiguration() throws CertificateEncodingExce verify(httpBuilder).setTrustedCertificates(serverTls.certificate().getEncoded()); verify(httpBuilder) .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()); + assertThat(exporter.getMemoryMode()).isEqualTo(MemoryMode.REUSABLE_DATA); } Mockito.verifyNoInteractions(grpcBuilder); } diff --git a/exporters/prometheus/build.gradle.kts b/exporters/prometheus/build.gradle.kts index 31044c70b23..a1da9e7e243 100644 --- a/exporters/prometheus/build.gradle.kts +++ b/exporters/prometheus/build.gradle.kts @@ -11,6 +11,7 @@ otelJava.moduleName.set("io.opentelemetry.exporter.prometheus") dependencies { api(project(":sdk:metrics")) + implementation(project(":exporters:common")) implementation(project(":sdk-extensions:autoconfigure-spi")) implementation("io.prometheus:prometheus-metrics-exporter-httpserver") diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java index 388fe1a4f6b..e4a15612856 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java @@ -11,6 +11,7 @@ package io.opentelemetry.exporter.prometheus; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.export.CollectionRegistration; @@ -32,11 +33,12 @@ */ public final class PrometheusHttpServer implements MetricReader { + private final PrometheusHttpServerBuilder builder; private final HTTPServer httpServer; private final PrometheusMetricReader prometheusMetricReader; private final PrometheusRegistry prometheusRegistry; private final String host; - private final PrometheusHttpServerBuilder builder; + private final MemoryMode memoryMode; /** * Returns a new {@link PrometheusHttpServer} which can be registered to an {@link @@ -59,11 +61,13 @@ public static PrometheusHttpServerBuilder builder() { @Nullable ExecutorService executor, PrometheusRegistry prometheusRegistry, boolean otelScopeEnabled, - @Nullable Predicate allowedResourceAttributesFilter) { + @Nullable Predicate allowedResourceAttributesFilter, + MemoryMode memoryMode) { this.builder = builder; this.prometheusMetricReader = new PrometheusMetricReader(otelScopeEnabled, allowedResourceAttributesFilter); this.host = host; + this.memoryMode = memoryMode; this.prometheusRegistry = prometheusRegistry; prometheusRegistry.register(prometheusMetricReader); try { @@ -85,6 +89,11 @@ public AggregationTemporality getAggregationTemporality(InstrumentType instrumen return prometheusMetricReader.getAggregationTemporality(instrumentType); } + @Override + public MemoryMode getMemoryMode() { + return memoryMode; + } + @Override public void register(CollectionRegistration registration) { prometheusMetricReader.register(registration); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java index 1d53679b52b..3c20eea3a2f 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java @@ -8,6 +8,7 @@ import static io.opentelemetry.api.internal.Utils.checkArgument; import static java.util.Objects.requireNonNull; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.util.concurrent.ExecutorService; import java.util.function.Predicate; @@ -18,6 +19,7 @@ public final class PrometheusHttpServerBuilder { static final int DEFAULT_PORT = 9464; private static final String DEFAULT_HOST = "0.0.0.0"; + private static final MemoryMode DEFAULT_MEMORY_MODE = MemoryMode.IMMUTABLE_DATA; private String host = DEFAULT_HOST; private int port = DEFAULT_PORT; @@ -25,6 +27,18 @@ public final class PrometheusHttpServerBuilder { private boolean otelScopeEnabled = true; @Nullable private Predicate allowedResourceAttributesFilter; @Nullable private ExecutorService executor; + private MemoryMode memoryMode = DEFAULT_MEMORY_MODE; + + PrometheusHttpServerBuilder() {} + + PrometheusHttpServerBuilder(PrometheusHttpServerBuilder builder) { + this.host = builder.host; + this.port = builder.port; + this.prometheusRegistry = builder.prometheusRegistry; + this.otelScopeEnabled = builder.otelScopeEnabled; + this.allowedResourceAttributesFilter = builder.allowedResourceAttributesFilter; + this.executor = builder.executor; + } /** Sets the host to bind to. If unset, defaults to {@value #DEFAULT_HOST}. */ public PrometheusHttpServerBuilder setHost(String host) { @@ -79,6 +93,13 @@ public PrometheusHttpServerBuilder setAllowedResourceAttributesFilter( return this; } + /** Set the {@link MemoryMode}. */ + public PrometheusHttpServerBuilder setMemoryMode(MemoryMode memoryMode) { + requireNonNull(memoryMode, "memoryMode"); + this.memoryMode = memoryMode; + return this; + } + /** * Returns a new {@link PrometheusHttpServer} with the configuration of this builder which can be * registered with a {@link io.opentelemetry.sdk.metrics.SdkMeterProvider}. @@ -91,17 +112,7 @@ public PrometheusHttpServer build() { executor, prometheusRegistry, otelScopeEnabled, - allowedResourceAttributesFilter); - } - - PrometheusHttpServerBuilder() {} - - PrometheusHttpServerBuilder(PrometheusHttpServerBuilder builder) { - this.host = builder.host; - this.port = builder.port; - this.prometheusRegistry = builder.prometheusRegistry; - this.otelScopeEnabled = builder.otelScopeEnabled; - this.allowedResourceAttributesFilter = builder.allowedResourceAttributesFilter; - this.executor = builder.executor; + allowedResourceAttributesFilter, + memoryMode); } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProvider.java index d7393dabf8b..39d6c755e17 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProvider.java @@ -5,6 +5,7 @@ package io.opentelemetry.exporter.prometheus.internal; +import io.opentelemetry.exporter.internal.ExporterBuilderUtil; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -32,6 +33,8 @@ public MetricReader createMetricReader(ConfigProperties config) { prometheusBuilder.setHost(host); } + ExporterBuilderUtil.configureExporterMemoryMode(config, prometheusBuilder::setMemoryMode); + return prometheusBuilder.build(); } diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProviderTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProviderTest.java index 58504e2e7e9..5f450ab751f 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProviderTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusMetricReaderProviderTest.java @@ -14,6 +14,7 @@ import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.prometheus.metrics.exporter.httpserver.HTTPServer; import java.io.IOException; @@ -57,6 +58,7 @@ void createMetricReader_Default() throws IOException { assertThat(server.getAddress().getHostName()).isEqualTo("0:0:0:0:0:0:0:0"); assertThat(server.getAddress().getPort()).isEqualTo(9464); }); + assertThat(metricReader.getMemoryMode()).isEqualTo(MemoryMode.IMMUTABLE_DATA); } } @@ -73,6 +75,7 @@ void createMetricReader_WithConfiguration() throws IOException { Map config = new HashMap<>(); config.put("otel.exporter.prometheus.host", "localhost"); config.put("otel.exporter.prometheus.port", String.valueOf(port)); + config.put("otel.java.experimental.exporter.memory_mode", "reusable_data"); when(configProperties.getInt(any())).thenReturn(null); when(configProperties.getString(any())).thenReturn(null); @@ -87,6 +90,7 @@ void createMetricReader_WithConfiguration() throws IOException { assertThat(server.getAddress().getHostName()).isEqualTo("localhost"); assertThat(server.getAddress().getPort()).isEqualTo(port); }); + assertThat(metricReader.getMemoryMode()).isEqualTo(MemoryMode.REUSABLE_DATA); } } } diff --git a/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java b/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java index 141159c3d8d..1339734f5b8 100644 --- a/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java +++ b/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java @@ -38,9 +38,12 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; @@ -63,6 +66,7 @@ import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span.Link; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; import io.opentelemetry.sdk.logs.export.LogRecordExporter; @@ -93,6 +97,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.LoggerFactory; import org.testcontainers.containers.BindMode; @@ -195,7 +200,7 @@ void afterEach() {} @ParameterizedTest @ValueSource(strings = {"gzip", "none"}) void testOtlpGrpcTraceExport(String compression) { - SpanExporter otlpGrpcTraceExporter = + SpanExporter exporter = OtlpGrpcSpanExporter.builder() .setEndpoint( "http://" @@ -205,7 +210,7 @@ void testOtlpGrpcTraceExport(String compression) { .setCompression(compression) .build(); - testTraceExport(otlpGrpcTraceExporter); + testTraceExport(exporter); } @Test @@ -227,7 +232,7 @@ void testOtlpGrpcTraceExport_mtls() throws Exception { @ParameterizedTest @ValueSource(strings = {"gzip", "none"}) void testOtlpHttpTraceExport(String compression) { - SpanExporter otlpGrpcTraceExporter = + SpanExporter exporter = OtlpHttpSpanExporter.builder() .setEndpoint( "http://" @@ -238,7 +243,7 @@ void testOtlpHttpTraceExport(String compression) { .setCompression(compression) .build(); - testTraceExport(otlpGrpcTraceExporter); + testTraceExport(exporter); } @Test @@ -328,7 +333,7 @@ private static void testTraceExport(SpanExporter spanExporter) { @ParameterizedTest @ValueSource(strings = {"gzip", "none"}) void testOtlpGrpcMetricExport(String compression) { - MetricExporter otlpGrpcMetricExporter = + MetricExporter exporter = OtlpGrpcMetricExporter.builder() .setEndpoint( "http://" @@ -338,7 +343,26 @@ void testOtlpGrpcMetricExport(String compression) { .setCompression(compression) .build(); - testMetricExport(otlpGrpcMetricExporter); + testMetricExport(exporter); + } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void testOtlpGrpcMetricExport_memoryMode(MemoryMode memoryMode) { + OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder(); + OtlpConfigUtil.setMemoryModeOnOtlpMetricExporterBuilder(builder, memoryMode); + + MetricExporter exporter = + builder + .setEndpoint( + "http://" + + collector.getHost() + + ":" + + collector.getMappedPort(COLLECTOR_OTLP_GRPC_PORT)) + .build(); + assertThat(exporter.getMemoryMode()).isEqualTo(memoryMode); + + testMetricExport(exporter); } @Test @@ -360,7 +384,8 @@ void testOtlpGrpcMetricExport_mtls() throws Exception { @ParameterizedTest @ValueSource(strings = {"gzip", "none"}) void testOtlpHttpMetricExport(String compression) { - MetricExporter otlpGrpcMetricExporter = + + MetricExporter exporter = OtlpHttpMetricExporter.builder() .setEndpoint( "http://" @@ -371,7 +396,27 @@ void testOtlpHttpMetricExport(String compression) { .setCompression(compression) .build(); - testMetricExport(otlpGrpcMetricExporter); + testMetricExport(exporter); + } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void testOtlpHttpMetricExport_memoryMode(MemoryMode memoryMode) { + OtlpHttpMetricExporterBuilder builder = OtlpHttpMetricExporter.builder(); + OtlpConfigUtil.setMemoryModeOnOtlpMetricExporterBuilder(builder, memoryMode); + + MetricExporter exporter = + builder + .setEndpoint( + "http://" + + collector.getHost() + + ":" + + collector.getMappedPort(COLLECTOR_OTLP_HTTP_PORT) + + "/v1/metrics") + .build(); + assertThat(exporter.getMemoryMode()).isEqualTo(memoryMode); + + testMetricExport(exporter); } @Test diff --git a/sdk-extensions/autoconfigure/README.md b/sdk-extensions/autoconfigure/README.md index 92def7c0b3c..a064dd79be2 100644 --- a/sdk-extensions/autoconfigure/README.md +++ b/sdk-extensions/autoconfigure/README.md @@ -72,11 +72,18 @@ The OpenTelemetry SDK can be disabled entirely. If disabled, `AutoConfiguredOpen The following configuration properties are common to all exporters: -| System property | Environment variable | Purpose | -|-----------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------| -| otel.traces.exporter | OTEL_TRACES_EXPORTER | List of exporters to be used for tracing, separated by commas. Default is `otlp`. `none` means no autoconfigured exporter. | -| otel.metrics.exporter | OTEL_METRICS_EXPORTER | List of exporters to be used for metrics, separated by commas. Default is `otlp`. `none` means no autoconfigured exporter. | -| otel.logs.exporter | OTEL_LOGS_EXPORTER | List of exporters to be used for logging, separated by commas. Default is `otlp`. `none` means no autoconfigured exporter. | +| System property | Environment variable | Purpose | +|---------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| otel.traces.exporter | OTEL_TRACES_EXPORTER | List of exporters to be used for tracing, separated by commas. Default is `otlp`. `none` means no autoconfigured exporter. | +| otel.metrics.exporter | OTEL_METRICS_EXPORTER | List of exporters to be used for metrics, separated by commas. Default is `otlp`. `none` means no autoconfigured exporter. | +| otel.logs.exporter | OTEL_LOGS_EXPORTER | List of exporters to be used for logging, separated by commas. Default is `otlp`. `none` means no autoconfigured exporter. | +| otel.java.experimental.exporter.memory_mode | OTEL_JAVA_EXPERIMENTAL_EXPORTER_MEMORY_MODE | If `reusable_data`, enable reusable memory mode (on exporters which support it) to reduce allocations. Default is `immutable_data`. This option is experimental and subject to change or removal.**[1]** | + +**[1]**: NOTE: The exporters which adhere +to `otel.java.experimental.exporter.memory_mode=reusable_data` +are `OtlpGrpcMetricExporter`, `OtlpHttpMetricExporter`, and `PrometheusHttpServer`. Support for +additional exporters may be added in the future. + #### OTLP exporter (span, metric, and log exporters)