diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt index 611a382e666..f42f38022a6 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt @@ -1,2 +1,9 @@ Comparing source compatibility of opentelemetry-exporter-otlp-1.50.0-SNAPSHOT.jar against opentelemetry-exporter-otlp-1.49.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder setMeterProvider(java.util.function.Supplier) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder setMeterProvider(java.util.function.Supplier) diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterMetrics.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterMetrics.java index 0c12f9d6fc0..f2cee481311 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterMetrics.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterMetrics.java @@ -70,7 +70,7 @@ public void addFailed(long value) { private LongCounter seen() { LongCounter seen = this.seen; - if (seen == null) { + if (seen == null || isNoop(seen)) { seen = meter().counterBuilder(exporterName + ".exporter.seen").build(); this.seen = seen; } @@ -79,7 +79,7 @@ private LongCounter seen() { private LongCounter exported() { LongCounter exported = this.exported; - if (exported == null) { + if (exported == null || isNoop(exported)) { exported = meter().counterBuilder(exporterName + ".exporter.exported").build(); this.exported = exported; } @@ -92,6 +92,12 @@ private Meter meter() { .get("io.opentelemetry.exporters." + exporterName + "-" + transportName); } + private static boolean isNoop(LongCounter counter) { + // This is a poor way to identify a Noop implementation, but the API doesn't provide a better + // way. Perhaps we could add a common "Noop" interface to allow for an instanceof check? + return counter.getClass().getSimpleName().startsWith("Noop"); + } + /** * Create an instance for recording exporter metrics under the meter {@code * "io.opentelemetry.exporters." + exporterName + "-grpc}". diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/ExporterMetricsTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/ExporterMetricsTest.java new file mode 100644 index 00000000000..30651ed8831 --- /dev/null +++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/ExporterMetricsTest.java @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.export.CollectionRegistration; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + +class ExporterMetricsTest { + + @SuppressWarnings("unchecked") + Supplier meterProviderSupplier = mock(Supplier.class); + + @Test + void createHttpProtobuf_validMeterProvider() { + when(meterProviderSupplier.get()) + .thenReturn( + SdkMeterProvider.builder() + // Have to provide a valid reader. + .registerMetricReader( + new MetricReader() { + @Override + public void register(CollectionRegistration registration) {} + + @Override + public CompletableResultCode forceFlush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public AggregationTemporality getAggregationTemporality( + InstrumentType instrumentType) { + return AggregationTemporality.CUMULATIVE; + } + }) + .build()); + ExporterMetrics exporterMetrics = + ExporterMetrics.createHttpProtobuf("test", "test", meterProviderSupplier); + verifyNoInteractions(meterProviderSupplier); // Ensure lazy + + // Verify the supplier is only called once per underlying meter. + exporterMetrics.addSeen(10); + exporterMetrics.addSeen(20); + verify(meterProviderSupplier, times(1)).get(); + exporterMetrics.addSuccess(30); + exporterMetrics.addSuccess(40); + verify(meterProviderSupplier, times(2)).get(); + exporterMetrics.addFailed(50); + exporterMetrics.addFailed(60); + verify(meterProviderSupplier, times(2)).get(); + } + + @Test + void createHttpProtobuf_noopMeterProvider() { + when(meterProviderSupplier.get()).thenReturn(MeterProvider.noop()); + ExporterMetrics exporterMetrics = + ExporterMetrics.createHttpProtobuf("test", "test", meterProviderSupplier); + verifyNoInteractions(meterProviderSupplier); // Ensure lazy + + // Verify the supplier is invoked multiple times since it returns a noop meter. + exporterMetrics.addSeen(10); + exporterMetrics.addSeen(20); + verify(meterProviderSupplier, times(2)).get(); + exporterMetrics.addSuccess(30); + exporterMetrics.addSuccess(40); + verify(meterProviderSupplier, times(4)).get(); + exporterMetrics.addFailed(50); + exporterMetrics.addFailed(60); + verify(meterProviderSupplier, times(6)).get(); + } +} 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 88f935bccbd..e75acfad1de 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 @@ -8,6 +8,7 @@ import static io.opentelemetry.api.internal.Utils.checkArgument; import static java.util.Objects.requireNonNull; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.compression.Compressor; import io.opentelemetry.exporter.internal.compression.CompressorProvider; @@ -61,7 +62,6 @@ public final class OtlpHttpMetricExporterBuilder { this.aggregationTemporalitySelector = aggregationTemporalitySelector; this.defaultAggregationSelector = defaultAggregationSelector; this.memoryMode = memoryMode; - delegate.setMeterProvider(MeterProvider::noop); OtlpUserAgent.addUserAgentHeader(delegate::addConstantHeaders); } @@ -243,6 +243,27 @@ public OtlpHttpMetricExporterBuilder setProxyOptions(ProxyOptions proxyOptions) return this; } + /** + * Sets the {@link MeterProvider} to use to collect metrics related to export. If not set, uses + * {@link GlobalOpenTelemetry#getMeterProvider()}. + */ + public OtlpHttpMetricExporterBuilder setMeterProvider(MeterProvider meterProvider) { + requireNonNull(meterProvider, "meterProvider"); + delegate.setMeterProvider(() -> meterProvider); + return this; + } + + /** + * Sets the {@link MeterProvider} supplier to use to collect metrics related to export. If not + * set, uses {@link GlobalOpenTelemetry#getMeterProvider()}. + */ + public OtlpHttpMetricExporterBuilder setMeterProvider( + Supplier meterProviderSupplier) { + requireNonNull(meterProviderSupplier, "meterProvider"); + delegate.setMeterProvider(meterProviderSupplier); + return this; + } + /** * Set the {@link MemoryMode}. If unset, defaults to {@link #DEFAULT_MEMORY_MODE}. * 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 4946aba137c..eb316c6d0c7 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 @@ -9,6 +9,7 @@ import static java.util.Objects.requireNonNull; import io.grpc.ManagedChannel; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.compression.Compressor; import io.opentelemetry.exporter.internal.compression.CompressorProvider; @@ -69,7 +70,6 @@ public final class OtlpGrpcMetricExporterBuilder { this.aggregationTemporalitySelector = aggregationTemporalitySelector; this.defaultAggregationSelector = defaultAggregationSelector; this.memoryMode = memoryMode; - delegate.setMeterProvider(MeterProvider::noop); OtlpUserAgent.addUserAgentHeader(delegate::addConstantHeader); } @@ -273,6 +273,27 @@ public OtlpGrpcMetricExporterBuilder setRetryPolicy(@Nullable RetryPolicy retryP return this; } + /** + * Sets the {@link MeterProvider} to use to collect metrics related to export. If not set, uses + * {@link GlobalOpenTelemetry#getMeterProvider()}. + */ + public OtlpGrpcMetricExporterBuilder setMeterProvider(MeterProvider meterProvider) { + requireNonNull(meterProvider, "meterProvider"); + delegate.setMeterProvider(() -> meterProvider); + return this; + } + + /** + * Sets the {@link MeterProvider} supplier to use to collect metrics related to export. If not + * set, uses {@link GlobalOpenTelemetry#getMeterProvider()}. + */ + public OtlpGrpcMetricExporterBuilder setMeterProvider( + Supplier meterProviderSupplier) { + requireNonNull(meterProviderSupplier, "meterProvider"); + delegate.setMeterProvider(meterProviderSupplier); + return this; + } + /** * Set the {@link MemoryMode}. If unset, defaults to {@link #DEFAULT_MEMORY_MODE}. * diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilderTest.java index d5886b2ca46..fefd30d44d0 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilderTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilderTest.java @@ -5,16 +5,176 @@ package io.opentelemetry.exporter.otlp.http.metrics; +import static io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData.createDoubleGauge; +import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class OtlpHttpMetricExporterBuilderTest { + private static final Collection DATA_SET = + singleton( + createDoubleGauge( + Resource.empty(), + InstrumentationScopeInfo.create("test"), + "test", + "test", + "test", + ImmutableGaugeData.empty())); + + private final SdkMeterProvider meterProvider = mock(SdkMeterProvider.class); + private final Meter meter = mock(Meter.class); + private final LongCounterBuilder counterBuilder = mock(LongCounterBuilder.class); + private final LongCounter counter = mock(LongCounter.class); + + @Test + void setMeterProvider_null() { + OtlpHttpMetricExporterBuilder builder = OtlpHttpMetricExporter.builder(); + assertThrows( + NullPointerException.class, + () -> builder.setMeterProvider((MeterProvider) null), + "MeterProvider must not be null"); + assertThrows( + NullPointerException.class, + () -> builder.setMeterProvider((Supplier) null), + "MeterProvider must not be null"); + } + + @Test + void setMeterProvider() { + when(meterProvider.get(any())).thenReturn(meter); + when(meter.counterBuilder(eq("otlp.exporter.seen"))).thenReturn(counterBuilder); + when(counterBuilder.build()).thenReturn(counter); + + try (OtlpHttpMetricExporter exporter = + OtlpHttpMetricExporter.builder().setMeterProvider(meterProvider).build()) { + verifyNoInteractions(meterProvider, meter, counterBuilder, counter); + + // Collection before MeterProvider is initialized. + when(meterProvider.get(any())).thenReturn(MeterProvider.noop().get("test")); + exporter.export(DATA_SET); + + verifyNoInteractions(meter, counterBuilder, counter); + + // Collection after MeterProvider is initialized. + when(meterProvider.get(any())).thenReturn(meter); + exporter.export(DATA_SET); + + verify(meter).counterBuilder(eq("otlp.exporter.seen")); + verify(counter).add(eq(1L), any()); + verifyNoMoreInteractions(meter, counter); + } + } + + @Test + void setMeterProvider_supplier() { + when(meterProvider.get(any())).thenReturn(meter); + when(meter.counterBuilder(eq("otlp.exporter.seen"))).thenReturn(counterBuilder); + when(counterBuilder.build()).thenReturn(counter); + + @SuppressWarnings("unchecked") + Supplier provider = mock(Supplier.class); + try (OtlpHttpMetricExporter exporter = + OtlpHttpMetricExporter.builder().setMeterProvider(provider).build()) { + verifyNoInteractions(provider, meterProvider, meter, counterBuilder, counter); + + // Collection before MeterProvider is initialized. + when(provider.get()).thenReturn(MeterProvider.noop()); + exporter.export(DATA_SET); + + verifyNoInteractions(meterProvider, meter, counterBuilder, counter); + + // Collection after MeterProvider is initialized. + when(provider.get()).thenReturn(meterProvider); + exporter.export(DATA_SET); + + verify(meter).counterBuilder(eq("otlp.exporter.seen")); + verify(counter).add(eq(1L), any()); + verifyNoMoreInteractions(meter, counter); + } + } + + @Test + void setMeterProvider_defaultGlobal() { + GlobalOpenTelemetry.set( + new OpenTelemetry() { + @Override + public MeterProvider getMeterProvider() { + return meterProvider; + } + + @Override + public TracerProvider getTracerProvider() { + return TracerProvider.noop(); + } + + @Override + public ContextPropagators getPropagators() { + return ContextPropagators.noop(); + } + }); + when(meterProvider.get(any())).thenReturn(meter); + when(meter.counterBuilder(eq("otlp.exporter.seen"))).thenReturn(counterBuilder); + when(counterBuilder.build()).thenReturn(counter); + + try (OtlpHttpMetricExporter exporter = OtlpHttpMetricExporter.builder().build()) { + verifyNoInteractions(meterProvider, meter, counterBuilder, counter); + + exporter.export(DATA_SET); + + verify(meter).counterBuilder(eq("otlp.exporter.seen")); + verify(counter).add(eq(1L), any()); + verifyNoMoreInteractions(meter, counter); + } finally { + GlobalOpenTelemetry.resetForTest(); + } + } + + @Test + void setMeterProvider_noMocks() { + AtomicReference meterProviderAtomicReference = new AtomicReference<>(); + SdkMeterProviderBuilder builder = + SdkMeterProvider.builder() + .registerMetricReader( + PeriodicMetricReader.create( + OtlpHttpMetricExporter.builder() + .setMeterProvider(meterProviderAtomicReference::get) + .build())); + meterProviderAtomicReference.set(builder.build()); + meterProviderAtomicReference.get().close(); + } + @Test void verifyToBuilderPreservesSettings() { AggregationTemporalitySelector temporalitySelector = diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilderTest.java index 4f8bb8e9ac7..c12d8c4c6b5 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilderTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilderTest.java @@ -5,16 +5,176 @@ package io.opentelemetry.exporter.otlp.metrics; +import static io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData.createDoubleGauge; +import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; import org.mockito.Mockito; class OtlpGrpcMetricExporterBuilderTest { + private static final Collection DATA_SET = + singleton( + createDoubleGauge( + Resource.empty(), + InstrumentationScopeInfo.create("test"), + "test", + "test", + "test", + ImmutableGaugeData.empty())); + + private final MeterProvider meterProvider = mock(MeterProvider.class); + private final Meter meter = mock(Meter.class); + private final LongCounterBuilder counterBuilder = mock(LongCounterBuilder.class); + private final LongCounter counter = mock(LongCounter.class); + + @Test + void setMeterProvider_null() { + OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder(); + assertThrows( + NullPointerException.class, + () -> builder.setMeterProvider((MeterProvider) null), + "MeterProvider must not be null"); + assertThrows( + NullPointerException.class, + () -> builder.setMeterProvider((Supplier) null), + "MeterProvider must not be null"); + } + + @Test + void setMeterProvider() { + when(meterProvider.get(any())).thenReturn(meter); + when(meter.counterBuilder(eq("otlp.exporter.seen"))).thenReturn(counterBuilder); + when(counterBuilder.build()).thenReturn(counter); + + try (OtlpGrpcMetricExporter exporter = + OtlpGrpcMetricExporter.builder().setMeterProvider(meterProvider).build()) { + verifyNoInteractions(meterProvider, meter, counterBuilder, counter); + + // Collection before MeterProvider is initialized. + when(meterProvider.get(any())).thenReturn(MeterProvider.noop().get("test")); + exporter.export(DATA_SET); + + verifyNoInteractions(meter, counterBuilder, counter); + + // Collection after MeterProvider is initialized. + when(meterProvider.get(any())).thenReturn(meter); + exporter.export(DATA_SET); + + verify(meter).counterBuilder(eq("otlp.exporter.seen")); + verify(counter).add(eq(1L), any()); + verifyNoMoreInteractions(meter, counter); + } + } + + @Test + void setMeterProvider_supplier() { + when(meterProvider.get(any())).thenReturn(meter); + when(meter.counterBuilder(eq("otlp.exporter.seen"))).thenReturn(counterBuilder); + when(counterBuilder.build()).thenReturn(counter); + + @SuppressWarnings("unchecked") + Supplier provider = mock(Supplier.class); + try (OtlpGrpcMetricExporter exporter = + OtlpGrpcMetricExporter.builder().setMeterProvider(provider).build()) { + verifyNoInteractions(provider, meterProvider, meter, counterBuilder, counter); + + // Collection before MeterProvider is initialized. + when(provider.get()).thenReturn(MeterProvider.noop()); + exporter.export(DATA_SET); + + verifyNoInteractions(meterProvider, meter, counterBuilder, counter); + + // Collection after MeterProvider is initialized. + when(provider.get()).thenReturn(meterProvider); + exporter.export(DATA_SET); + + verify(meter).counterBuilder(eq("otlp.exporter.seen")); + verify(counter).add(eq(1L), any()); + verifyNoMoreInteractions(meter, counter); + } + } + + @Test + void setMeterProvider_defaultGlobal() { + GlobalOpenTelemetry.set( + new OpenTelemetry() { + @Override + public MeterProvider getMeterProvider() { + return meterProvider; + } + + @Override + public TracerProvider getTracerProvider() { + return TracerProvider.noop(); + } + + @Override + public ContextPropagators getPropagators() { + return ContextPropagators.noop(); + } + }); + when(meterProvider.get(any())).thenReturn(meter); + when(meter.counterBuilder(eq("otlp.exporter.seen"))).thenReturn(counterBuilder); + when(counterBuilder.build()).thenReturn(counter); + + try (OtlpGrpcMetricExporter exporter = OtlpGrpcMetricExporter.builder().build()) { + verifyNoInteractions(meterProvider, meter, counterBuilder, counter); + + exporter.export(DATA_SET); + + verify(meter).counterBuilder(eq("otlp.exporter.seen")); + verify(counter).add(eq(1L), any()); + verifyNoMoreInteractions(meter, counter); + } finally { + GlobalOpenTelemetry.resetForTest(); + } + } + + @Test + void setMeterProvider_noMocks() { + AtomicReference meterProviderAtomicReference = new AtomicReference<>(); + SdkMeterProviderBuilder builder = + SdkMeterProvider.builder() + .registerMetricReader( + PeriodicMetricReader.create( + OtlpGrpcMetricExporter.builder() + .setMeterProvider(meterProviderAtomicReference::get) + .build())); + meterProviderAtomicReference.set(builder.build()); + meterProviderAtomicReference.get().close(); + } + @Test void verifyToBuilderPreservesSettings() { AggregationTemporalitySelector temporalitySelector =