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:
parent
619a1b1526
commit
65e7145e9c
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
Loading…
Reference in New Issue