Skip to content

Commit

Permalink
Add ConfigurableMetricReaderProvider SPI (#5755)
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-berg committed Aug 24, 2023
1 parent 4fdd2ed commit e0a0b77
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 240 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.prometheus.internal;

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.internal.ConfigurableMetricReaderProvider;
import io.opentelemetry.sdk.metrics.export.MetricReader;

/**
* SPI implementation for {@link PrometheusHttpServer}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class PrometheusMetricReaderProvider implements ConfigurableMetricReaderProvider {

@Override
public MetricReader createMetricReader(ConfigProperties config) {
PrometheusHttpServerBuilder prometheusBuilder = PrometheusHttpServer.builder();

Integer port = config.getInt("otel.exporter.prometheus.port");
if (port != null) {
prometheusBuilder.setPort(port);
}
String host = config.getString("otel.exporter.prometheus.host");
if (host != null) {
prometheusBuilder.setHost(host);
}
return prometheusBuilder.build();
}

@Override
public String getName() {
return "prometheus";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.opentelemetry.exporter.prometheus.internal.PrometheusMetricReaderProvider

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.prometheus.internal;

import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;
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.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class PrometheusMetricReaderProviderTest {

private static final PrometheusMetricReaderProvider provider =
new PrometheusMetricReaderProvider();

@Mock private ConfigProperties configProperties;

@Test
void getName() {
assertThat(provider.getName()).isEqualTo("prometheus");
}

@Test
void createMetricReader_Default() throws IOException {
when(configProperties.getInt(any())).thenReturn(null);
when(configProperties.getString(any())).thenReturn(null);

try (MetricReader metricReader = provider.createMetricReader(configProperties)) {
assertThat(metricReader)
.isInstanceOf(PrometheusHttpServer.class)
.extracting("server", as(InstanceOfAssertFactories.type(HttpServer.class)))
.satisfies(
server -> {
assertThat(server.getAddress().getHostName()).isEqualTo("0:0:0:0:0:0:0:0");
assertThat(server.getAddress().getPort()).isEqualTo(9464);
});
}
}

@Test
void createMetricReader_WithConfiguration() throws IOException {
// Find a random unused port. There's a small race if another process takes it before we
// initialize. Consider adding retries to this test if it flakes, presumably it never will on
// CI since there's no prometheus there blocking the well-known port.
int port;
try (ServerSocket socket2 = new ServerSocket(0)) {
port = socket2.getLocalPort();
}

Map<String, String> config = new HashMap<>();
config.put("otel.exporter.prometheus.host", "localhost");
config.put("otel.exporter.prometheus.port", String.valueOf(port));

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

try (MetricReader metricReader =
provider.createMetricReader(DefaultConfigProperties.createForTest(config))) {
assertThat(metricReader)
.extracting("server", as(InstanceOfAssertFactories.type(HttpServer.class)))
.satisfies(
server -> {
assertThat(server.getAddress().getHostName()).isEqualTo("localhost");
assertThat(server.getAddress().getPort()).isEqualTo(port);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.autoconfigure.spi.internal;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;

/**
* A service provider interface (SPI) for providing additional metric readers that can be used with
* the autoconfigured SDK. If the {@code otel.metrics.exporter} property contains a value equal to
* what is returned by {@link #getName()}, the exporter returned by {@link
* #createMetricReader(ConfigProperties)} will be enabled and added to the SDK.
*
* <p>Where as {@link ConfigurableMetricExporterProvider} provides push-based {@link
* MetricExporter}s to be paired with {@link PeriodicMetricReader}, this SPI facilitates pull-based
* {@link MetricReader}s.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public interface ConfigurableMetricReaderProvider {

/**
* Returns a {@link MetricReader} that can be registered to OpenTelemetry by providing the
* property value specified by {@link #getName()}.
*/
MetricReader createMetricReader(ConfigProperties config);

/**
* Returns the name of this reader, which can be specified with the {@code otel.metrics.exporter}
* property to enable it. The name returned should NOT be the same as any other reader / exporter
* name, either from other implementations of this SPI or {@link
* ConfigurableMetricExporterProvider}. If the name does conflict with another reader / exporter
* name, the resulting behavior is undefined and it is explicitly unspecified which reader /
* exporter will actually be used.
*/
String getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
package io.opentelemetry.sdk.autoconfigure.spi.metrics;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ConfigurableMetricReaderProvider;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.MetricReader;

/**
* A service provider interface (SPI) for providing additional exporters that can be used with the
* autoconfigured SDK. If the {@code otel.metrics.exporter} property contains a value equal to what
* is returned by {@link #getName()}, the exporter returned by {@link
* #createExporter(ConfigProperties)} will be enabled and added to the SDK.
*
* <p>The push-based {@link MetricExporter}s supplied by this SPI are paired with a {@link
* io.opentelemetry.sdk.metrics.export.PeriodicMetricReader}. See {@link
* ConfigurableMetricReaderProvider} for providing pull-based {@link MetricReader}s.
*
* @since 1.15.0
*/
public interface ConfigurableMetricExporterProvider {
Expand All @@ -27,8 +33,10 @@ public interface ConfigurableMetricExporterProvider {
/**
* Returns the name of this exporter, which can be specified with the {@code
* otel.metrics.exporter} property to enable it. The name returned should NOT be the same as any
* other exporter name. If the name does conflict with another exporter name, the resulting
* behavior is undefined and it is explicitly unspecified which exporter will actually be used.
* other exporter / reader name, either from other implementations of this SPI or {@link
* ConfigurableMetricReaderProvider}. If the name does conflict with another exporter / reader
* name, the resulting behavior is undefined and it is explicitly unspecified which exporter /
* reader will actually be used.
*/
String getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -87,7 +86,6 @@ static List<MetricReader> configureMetricReaders(
exporterName ->
MetricExporterConfiguration.configureReader(
exporterName, config, spiHelper, metricExporterCustomizer, closeables))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

Expand Down

0 comments on commit e0a0b77

Please sign in to comment.