Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
*** 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<io.opentelemetry.api.metrics.MeterProvider>)
*** 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<io.opentelemetry.api.metrics.MeterProvider>)
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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}".
Expand Down
Original file line number Diff line number Diff line change
@@ -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<MeterProvider> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<MeterProvider> meterProviderSupplier) {
requireNonNull(meterProviderSupplier, "meterProvider");
delegate.setMeterProvider(meterProviderSupplier);
return this;
}

/**
* Set the {@link MemoryMode}. If unset, defaults to {@link #DEFAULT_MEMORY_MODE}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<MeterProvider> meterProviderSupplier) {
requireNonNull(meterProviderSupplier, "meterProvider");
delegate.setMeterProvider(meterProviderSupplier);
return this;
}

/**
* Set the {@link MemoryMode}. If unset, defaults to {@link #DEFAULT_MEMORY_MODE}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MetricData> 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<MeterProvider>) 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<MeterProvider> 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<SdkMeterProvider> 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 =
Expand Down
Loading
Loading