Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config to enable Default Exponential Histogram for Prometheus Exporter #6541

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.opentelemetry.sdk.internal.DaemonThreadFactory;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
Expand All @@ -41,6 +43,7 @@ public final class PrometheusHttpServer implements MetricReader {
private final PrometheusRegistry prometheusRegistry;
private final String host;
private final MemoryMode memoryMode;
private final DefaultAggregationSelector defaultAggregationSelector;

/**
* Returns a new {@link PrometheusHttpServer} which can be registered to an {@link
Expand All @@ -65,7 +68,8 @@ public static PrometheusHttpServerBuilder builder() {
boolean otelScopeEnabled,
@Nullable Predicate<String> allowedResourceAttributesFilter,
MemoryMode memoryMode,
@Nullable HttpHandler defaultHandler) {
@Nullable HttpHandler defaultHandler,
DefaultAggregationSelector defaultAggregationSelector) {
this.builder = builder;
this.prometheusMetricReader =
new PrometheusMetricReader(otelScopeEnabled, allowedResourceAttributesFilter);
Expand All @@ -92,13 +96,19 @@ public static PrometheusHttpServerBuilder builder() {
} catch (IOException e) {
throw new UncheckedIOException("Could not create Prometheus HTTP server", e);
}
this.defaultAggregationSelector = defaultAggregationSelector;
}

@Override
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
return prometheusMetricReader.getAggregationTemporality(instrumentType);
}

@Override
public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
return defaultAggregationSelector.getDefaultAggregation(instrumentType);
}

@Override
public MemoryMode getMemoryMode() {
return memoryMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

import com.sun.net.httpserver.HttpHandler;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -31,6 +34,8 @@ public final class PrometheusHttpServerBuilder {
@Nullable private ExecutorService executor;
private MemoryMode memoryMode = DEFAULT_MEMORY_MODE;
@Nullable private HttpHandler defaultHandler;
private DefaultAggregationSelector defaultAggregationSelector =
DefaultAggregationSelector.getDefault();

PrometheusHttpServerBuilder() {}

Expand All @@ -41,6 +46,7 @@ public final class PrometheusHttpServerBuilder {
this.otelScopeEnabled = builder.otelScopeEnabled;
this.allowedResourceAttributesFilter = builder.allowedResourceAttributesFilter;
this.executor = builder.executor;
this.defaultAggregationSelector = builder.defaultAggregationSelector;
}

/** Sets the host to bind to. If unset, defaults to {@value #DEFAULT_HOST}. */
Expand Down Expand Up @@ -126,6 +132,19 @@ public PrometheusHttpServerBuilder setDefaultHandler(HttpHandler defaultHandler)
return this;
}

/**
* Set the {@link DefaultAggregationSelector} used for {@link
* MetricExporter#getDefaultAggregation(InstrumentType)}.
*
* <p>If unset, defaults to {@link DefaultAggregationSelector#getDefault()}.
*/
public PrometheusHttpServerBuilder setDefaultAggregationSelector(
DefaultAggregationSelector defaultAggregationSelector) {
requireNonNull(defaultAggregationSelector, "defaultAggregationSelector");
this.defaultAggregationSelector = defaultAggregationSelector;
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}.
Expand All @@ -140,6 +159,7 @@ public PrometheusHttpServer build() {
otelScopeEnabled,
allowedResourceAttributesFilter,
memoryMode,
defaultHandler);
defaultHandler,
defaultAggregationSelector);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@

package io.opentelemetry.exporter.prometheus.internal;

import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram;

import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;

/**
* File configuration SPI implementation for {@link PrometheusHttpServer}.
Expand Down Expand Up @@ -41,6 +48,19 @@
if (host != null) {
prometheusBuilder.setHost(host);
}
String defaultHistogramAggregation = config.getString("metrics.default.histogram.aggregation");
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
if (defaultHistogramAggregation != null) {
if (AggregationUtil.aggregationName(Aggregation.base2ExponentialBucketHistogram())

Check warning on line 53 in exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java

View check run for this annotation

Codecov / codecov/patch

exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java#L53

Added line #L53 was not covered by tests
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
.equalsIgnoreCase(defaultHistogramAggregation)) {
prometheusBuilder.setDefaultAggregationSelector(
DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram()));
} else if (!AggregationUtil.aggregationName(explicitBucketHistogram())

Check warning on line 58 in exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java

View check run for this annotation

Codecov / codecov/patch

exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java#L55-L58

Added lines #L55 - L58 were not covered by tests
.equalsIgnoreCase(defaultHistogramAggregation)) {
throw new ConfigurationException(

Check warning on line 60 in exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java

View check run for this annotation

Codecov / codecov/patch

exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java#L60

Added line #L60 was not covered by tests
"Unrecognized default histogram aggregation: " + defaultHistogramAggregation);
}
}

return prometheusBuilder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@

package io.opentelemetry.exporter.prometheus.internal;

import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram;

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;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ConfigurableMetricReaderProvider;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;

/**
* SPI implementation for {@link PrometheusHttpServer}.
Expand All @@ -32,6 +39,20 @@ public MetricReader createMetricReader(ConfigProperties config) {
if (host != null) {
prometheusBuilder.setHost(host);
}
String defaultHistogramAggregation =
config.getString("otel.exporter.prometheus.metrics.default.histogram.aggregation");
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
if (defaultHistogramAggregation != null) {
if (AggregationUtil.aggregationName(Aggregation.base2ExponentialBucketHistogram())
.equalsIgnoreCase(defaultHistogramAggregation)) {
prometheusBuilder.setDefaultAggregationSelector(
DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram()));
} else if (!AggregationUtil.aggregationName(explicitBucketHistogram())
.equalsIgnoreCase(defaultHistogramAggregation)) {
throw new ConfigurationException(
"Unrecognized default histogram aggregation: " + defaultHistogramAggregation);
}
}

ExporterBuilderUtil.configureExporterMemoryMode(config, prometheusBuilder::setMemoryMode);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
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;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData;
Expand Down Expand Up @@ -113,6 +116,9 @@ void invalidConfig() {
assertThatThrownBy(() -> PrometheusHttpServer.builder().setHost(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("host must not be empty");
assertThatThrownBy(() -> PrometheusHttpServer.builder().setDefaultAggregationSelector(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("defaultAggregationSelector");
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
Expand Down Expand Up @@ -500,10 +506,14 @@ private static List<MetricData> generateTestData() {

@Test
void toBuilder() {
DefaultAggregationSelector defaultAggregationSelector =
DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram());
PrometheusHttpServerBuilder builder = PrometheusHttpServer.builder();
builder.setHost("localhost");
builder.setPort(1234);
builder.setOtelScopeEnabled(false);
builder.setDefaultAggregationSelector(defaultAggregationSelector);

Predicate<String> resourceAttributesFilter = s -> false;
builder.setAllowedResourceAttributesFilter(resourceAttributesFilter);
Expand All @@ -524,6 +534,7 @@ void toBuilder() {
.hasFieldOrPropertyWithValue("otelScopeEnabled", false)
.hasFieldOrPropertyWithValue("allowedResourceAttributesFilter", resourceAttributesFilter)
.hasFieldOrPropertyWithValue("executor", executor)
.hasFieldOrPropertyWithValue("prometheusRegistry", prometheusRegistry);
.hasFieldOrPropertyWithValue("prometheusRegistry", prometheusRegistry)
.hasFieldOrPropertyWithValue("defaultAggregationSelector", defaultAggregationSelector);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@

import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import com.sun.net.httpserver.HttpServer;
import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
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.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
import java.io.IOException;
Expand Down Expand Up @@ -59,6 +63,8 @@ void createMetricReader_Default() throws IOException {
assertThat(server.getAddress().getPort()).isEqualTo(9464);
});
assertThat(metricReader.getMemoryMode()).isEqualTo(MemoryMode.IMMUTABLE_DATA);
assertThat(metricReader.getDefaultAggregation(InstrumentType.HISTOGRAM))
.isEqualTo(Aggregation.defaultAggregation());
}
}

Expand All @@ -76,6 +82,9 @@ void createMetricReader_WithConfiguration() throws IOException {
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");
config.put(
"otel.exporter.prometheus.metrics.default.histogram.aggregation",
"BASE2_EXPONENTIAL_BUCKET_HISTOGRAM");

when(configProperties.getInt(any())).thenReturn(null);
when(configProperties.getString(any())).thenReturn(null);
Expand All @@ -91,6 +100,22 @@ void createMetricReader_WithConfiguration() throws IOException {
assertThat(server.getAddress().getPort()).isEqualTo(port);
});
assertThat(metricReader.getMemoryMode()).isEqualTo(MemoryMode.REUSABLE_DATA);
assertThat(metricReader.getDefaultAggregation(InstrumentType.HISTOGRAM))
.isEqualTo(Aggregation.base2ExponentialBucketHistogram());
}
}

@Test
void createMetricReader_WithWrongConfiguration() throws IOException {
Map<String, String> config = new HashMap<>();
config.put("otel.exporter.prometheus.metrics.default.histogram.aggregation", "foo");

when(configProperties.getInt(any())).thenReturn(null);
when(configProperties.getString(any())).thenReturn(null);

assertThatThrownBy(
() -> provider.createMetricReader(DefaultConfigProperties.createFromMap(config)))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("Unrecognized default histogram aggregation:");
}
}
Loading