OTLP metric temporality configuration (#3847)

* Add ability to configure preferred temporality to OTLP grpc and http metric exporters

* Make OTLP metric preferred temporality configurable

* Make temporality configuration case insensitive
This commit is contained in:
jack-berg 2021-11-11 20:03:20 -06:00 committed by GitHub
parent 619a1b1526
commit 65e7145e9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 143 additions and 15 deletions

View File

@ -12,7 +12,6 @@ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import java.util.Collection;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/** Exports metrics using OTLP via HTTP, using OpenTelemetry's protobuf model. */
@ -20,9 +19,13 @@ import javax.annotation.concurrent.ThreadSafe;
public final class OtlpHttpMetricExporter implements MetricExporter {
private final OkHttpExporter<MetricsRequestMarshaler> delegate;
private final AggregationTemporality preferredTemporality;
OtlpHttpMetricExporter(OkHttpExporter<MetricsRequestMarshaler> delegate) {
OtlpHttpMetricExporter(
OkHttpExporter<MetricsRequestMarshaler> delegate,
AggregationTemporality preferredTemporality) {
this.delegate = delegate;
this.preferredTemporality = preferredTemporality;
}
/**
@ -43,12 +46,9 @@ public final class OtlpHttpMetricExporter implements MetricExporter {
return new OtlpHttpMetricExporterBuilder();
}
@Nullable
@Override
public final AggregationTemporality getPreferredTemporality() {
// TODO: Lookup based on specification, or constructor
// https://github.com/open-telemetry/opentelemetry-java/issues/3790
return null;
public AggregationTemporality getPreferredTemporality() {
return preferredTemporality;
}
/**

View File

@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNull;
import io.opentelemetry.exporter.otlp.internal.metrics.MetricsRequestMarshaler;
import io.opentelemetry.exporter.otlp.internal.okhttp.OkHttpExporterBuilder;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@ -18,7 +19,11 @@ public final class OtlpHttpMetricExporterBuilder {
private static final String DEFAULT_ENDPOINT = "http://localhost:4318/v1/metrics";
private static final AggregationTemporality DEFAULT_TEMPORALITY =
AggregationTemporality.CUMULATIVE;
private final OkHttpExporterBuilder<MetricsRequestMarshaler> delegate;
private AggregationTemporality preferredTemporality = DEFAULT_TEMPORALITY;
OtlpHttpMetricExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("metric", DEFAULT_ENDPOINT);
@ -83,12 +88,23 @@ public final class OtlpHttpMetricExporterBuilder {
return this;
}
/**
* Set the preferred aggregation temporality. If unset, defaults to {@link
* AggregationTemporality#CUMULATIVE}.
*/
public OtlpHttpMetricExporterBuilder setPreferredTemporality(
AggregationTemporality preferredTemporality) {
requireNonNull(preferredTemporality, "preferredTemporality");
this.preferredTemporality = preferredTemporality;
return this;
}
/**
* Constructs a new instance of the exporter based on the builder's values.
*
* @return a new exporter's instance
*/
public OtlpHttpMetricExporter build() {
return new OtlpHttpMetricExporter(delegate.build());
return new OtlpHttpMetricExporter(delegate.build(), preferredTemporality);
}
}

View File

@ -133,6 +133,20 @@ class OtlpHttpMetricExporterTest {
OtlpHttpMetricExporter.builder()
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
.doesNotThrowAnyException();
assertThatCode(
() ->
OtlpHttpMetricExporter.builder()
.setPreferredTemporality(AggregationTemporality.DELTA))
.doesNotThrowAnyException();
assertThat(
OtlpHttpMetricExporter.builder()
.setPreferredTemporality(AggregationTemporality.DELTA)
.build()
.getPreferredTemporality())
.isEqualTo(AggregationTemporality.DELTA);
assertThat(OtlpHttpMetricExporter.builder().build().getPreferredTemporality())
.isEqualTo(AggregationTemporality.CUMULATIVE);
}
@Test
@ -168,6 +182,10 @@ class OtlpHttpMetricExporterTest {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Unsupported compression method. Supported compression methods include: gzip, none.");
assertThatThrownBy(() -> OtlpHttpMetricExporter.builder().setPreferredTemporality(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("preferredTemporality");
}
@Test

View File

@ -12,7 +12,6 @@ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import java.util.Collection;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/** Exports metrics using OTLP via gRPC, using OpenTelemetry's protobuf model. */
@ -20,6 +19,7 @@ import javax.annotation.concurrent.ThreadSafe;
public final class OtlpGrpcMetricExporter implements MetricExporter {
private final GrpcExporter<MetricsRequestMarshaler> delegate;
private final AggregationTemporality preferredTemporality;
/**
* Returns a new {@link OtlpGrpcMetricExporter} reading the configuration values from the
@ -41,16 +41,15 @@ public final class OtlpGrpcMetricExporter implements MetricExporter {
return new OtlpGrpcMetricExporterBuilder();
}
OtlpGrpcMetricExporter(GrpcExporter<MetricsRequestMarshaler> delegate) {
OtlpGrpcMetricExporter(
GrpcExporter<MetricsRequestMarshaler> delegate, AggregationTemporality preferredTemporality) {
this.delegate = delegate;
this.preferredTemporality = preferredTemporality;
}
@Nullable
@Override
public AggregationTemporality getPreferredTemporality() {
// TODO: Lookup based on specification, or constructor
// https://github.com/open-telemetry/opentelemetry-java/issues/3790
return null;
return preferredTemporality;
}
/**

View File

@ -12,6 +12,7 @@ import io.grpc.ManagedChannel;
import io.opentelemetry.exporter.otlp.internal.grpc.GrpcExporter;
import io.opentelemetry.exporter.otlp.internal.grpc.GrpcExporterBuilder;
import io.opentelemetry.exporter.otlp.internal.metrics.MetricsRequestMarshaler;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@ -27,10 +28,14 @@ public final class OtlpGrpcMetricExporterBuilder {
private static final String DEFAULT_ENDPOINT_URL = "http://localhost:4317";
private static final URI DEFAULT_ENDPOINT = URI.create(DEFAULT_ENDPOINT_URL);
private static final long DEFAULT_TIMEOUT_SECS = 10;
private static final AggregationTemporality DEFAULT_TEMPORALITY =
AggregationTemporality.CUMULATIVE;
// Visible for testing
final GrpcExporterBuilder<MetricsRequestMarshaler> delegate;
private AggregationTemporality preferredTemporality = DEFAULT_TEMPORALITY;
OtlpGrpcMetricExporterBuilder() {
delegate =
GrpcExporter.builder(
@ -121,12 +126,23 @@ public final class OtlpGrpcMetricExporterBuilder {
return this;
}
/**
* Set the preferred aggregation temporality. If unset, defaults to {@link
* AggregationTemporality#CUMULATIVE}.
*/
public OtlpGrpcMetricExporterBuilder setPreferredTemporality(
AggregationTemporality preferredTemporality) {
requireNonNull(preferredTemporality, "preferredTemporality");
this.preferredTemporality = preferredTemporality;
return this;
}
/**
* Constructs a new instance of the exporter based on the builder's values.
*
* @return a new exporter's instance
*/
public OtlpGrpcMetricExporter build() {
return new OtlpGrpcMetricExporter(delegate.build());
return new OtlpGrpcMetricExporter(delegate.build(), preferredTemporality);
}
}

View File

@ -119,6 +119,20 @@ class OtlpGrpcMetricExporterTest {
OtlpGrpcMetricExporter.builder()
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
.doesNotThrowAnyException();
assertThatCode(
() ->
OtlpGrpcMetricExporter.builder()
.setPreferredTemporality(AggregationTemporality.DELTA))
.doesNotThrowAnyException();
assertThat(
OtlpGrpcMetricExporter.builder()
.setPreferredTemporality(AggregationTemporality.DELTA)
.build()
.getPreferredTemporality())
.isEqualTo(AggregationTemporality.DELTA);
assertThat(OtlpGrpcMetricExporter.builder().build().getPreferredTemporality())
.isEqualTo(AggregationTemporality.CUMULATIVE);
}
@Test
@ -155,6 +169,10 @@ class OtlpGrpcMetricExporterTest {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Unsupported compression method. Supported compression methods include: gzip, none.");
assertThatThrownBy(() -> OtlpGrpcMetricExporter.builder().setPreferredTemporality(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("preferredTemporality");
}
@Test

View File

@ -70,6 +70,7 @@ The [OpenTelemetry Protocol (OTLP)](https://github.com/open-telemetry/openteleme
| otel.exporter.otlp.protocol | OTEL_EXPORTER_OTLP_PROTOCOL | The transport protocol to use on OTLP trace and metrics requests. Options include `grpc` and `http/protobuf`. Default is `grpc`. |
| otel.exporter.otlp.traces.protocol | OTEL_EXPORTER_OTLP_TRACES_PROTOCOL | The transport protocol to use on OTLP trace requests. Options include `grpc` and `http/protobuf`. Default is `grpc`. |
| otel.exporter.otlp.metrics.protocol | OTEL_EXPORTER_OTLP_METRICS_PROTOCOL | The transport protocol to use on OTLP metrics requests. Options include `grpc` and `http/protobuf`. Default is `grpc`. |
| otel.exporter.otlp.metrics.temporality | OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY | The preferred output aggregation temporality. Options include `DELTA` and `CUMULATIVE`. Default is `CUMULATIVE`. |
| otel.experimental.exporter.otlp.retry.enabled | OTEL_EXPERIMENTAL_EXPORTER_OTLP_RETRY_ENABLED | If `true`, enable [experimental retry support](#otlp-exporter-retry). Default is `false`. |
To configure the service name for the OTLP exporter, add the `service.name` key

View File

@ -106,6 +106,7 @@ final class MetricExporterConfiguration {
builder::setTimeout,
builder::setTrustedCertificates,
(unused) -> {});
OtlpConfigUtil.configureOtlpAggregationTemporality(config, builder::setPreferredTemporality);
exporter = builder.build();
} else if (protocol.equals(PROTOCOL_GRPC)) {
@ -134,6 +135,7 @@ final class MetricExporterConfiguration {
DefaultGrpcExporterBuilder.getDelegateBuilder(
OtlpGrpcMetricExporterBuilder.class, builder)
.addRetryPolicy(retryPolicy));
OtlpConfigUtil.configureOtlpAggregationTemporality(config, builder::setPreferredTemporality);
exporter = builder.build();
} else {

View File

@ -8,6 +8,7 @@ package io.opentelemetry.sdk.autoconfigure;
import io.opentelemetry.exporter.otlp.internal.RetryPolicy;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@ -114,6 +115,22 @@ final class OtlpConfigUtil {
}
}
static void configureOtlpAggregationTemporality(
ConfigProperties config, Consumer<AggregationTemporality> setAggregationTemporality) {
String temporalityStr = config.getString("otel.exporter.otlp.metrics.temporality");
if (temporalityStr == null) {
return;
}
AggregationTemporality temporality;
try {
temporality = AggregationTemporality.valueOf(temporalityStr.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ConfigurationException(
"Unrecognized aggregation temporality: " + temporalityStr, e);
}
setAggregationTemporality.accept(temporality);
}
private static URL createUrl(URL context, String spec) {
try {
return new URL(context, spec);

View File

@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@ -266,4 +267,40 @@ class OtlpConfigUtilTest {
return endpoint.get();
}
@Test
void configureOtlpAggregationTemporality() {
assertThatThrownBy(
() ->
configureAggregationTemporality(
ImmutableMap.of("otel.exporter.otlp.metrics.temporality", "foo")))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("Unrecognized aggregation temporality:");
assertThat(
configureAggregationTemporality(
ImmutableMap.of("otel.exporter.otlp.metrics.temporality", "CUMULATIVE")))
.isEqualTo(AggregationTemporality.CUMULATIVE);
assertThat(
configureAggregationTemporality(
ImmutableMap.of("otel.exporter.otlp.metrics.temporality", "cumulative")))
.isEqualTo(AggregationTemporality.CUMULATIVE);
assertThat(
configureAggregationTemporality(
ImmutableMap.of("otel.exporter.otlp.metrics.temporality", "DELTA")))
.isEqualTo(AggregationTemporality.DELTA);
assertThat(
configureAggregationTemporality(
ImmutableMap.of("otel.exporter.otlp.metrics.temporality", "delta")))
.isEqualTo(AggregationTemporality.DELTA);
}
/** Configure and return the aggregation temporality using the given properties. */
private static AggregationTemporality configureAggregationTemporality(
Map<String, String> properties) {
AtomicReference<AggregationTemporality> temporalityRef = new AtomicReference<>();
OtlpConfigUtil.configureOtlpAggregationTemporality(
DefaultConfigProperties.createForTest(properties), temporalityRef::set);
return temporalityRef.get();
}
}

View File

@ -184,6 +184,7 @@ class OtlpGrpcConfigTest {
props.put("otel.exporter.otlp.metrics.headers", "header-key=header-value");
props.put("otel.exporter.otlp.metrics.compression", "gzip");
props.put("otel.exporter.otlp.metrics.timeout", "15s");
props.put("otel.exporter.otlp.metrics.temporality", "DELTA");
MetricExporter metricExporter =
MetricExporterConfiguration.configureOtlpMetrics(
DefaultConfigProperties.createForTest(props), SdkMeterProvider.builder());
@ -191,6 +192,7 @@ class OtlpGrpcConfigTest {
assertThat(metricExporter)
.extracting("delegate.timeoutNanos")
.isEqualTo(TimeUnit.SECONDS.toNanos(15));
assertThat(metricExporter.getPreferredTemporality()).isEqualTo(AggregationTemporality.DELTA);
assertThat(metricExporter.export(METRIC_DATA).join(15, TimeUnit.SECONDS).isSuccess()).isTrue();
assertThat(server.metricRequests).hasSize(1);
assertThat(server.requestHeaders)

View File

@ -281,6 +281,7 @@ class OtlpHttpConfigTest {
props.put("otel.exporter.otlp.metrics.headers", "header-key=header-value");
props.put("otel.exporter.otlp.metrics.compression", "gzip");
props.put("otel.exporter.otlp.metrics.timeout", "15s");
props.put("otel.exporter.otlp.metrics.temporality", "DELTA");
MetricExporter metricExporter =
MetricExporterConfiguration.configureOtlpMetrics(
DefaultConfigProperties.createForTest(props), SdkMeterProvider.builder());
@ -289,6 +290,7 @@ class OtlpHttpConfigTest {
.extracting("delegate.client", as(InstanceOfAssertFactories.type(OkHttpClient.class)))
.extracting(OkHttpClient::callTimeoutMillis)
.isEqualTo((int) TimeUnit.SECONDS.toMillis(15));
assertThat(metricExporter.getPreferredTemporality()).isEqualTo(AggregationTemporality.DELTA);
assertThat(
metricExporter
.export(Lists.newArrayList(generateFakeMetric()))