diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index b7f0cb5eec9..f10852d4cde 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -60,7 +60,6 @@ val DEPENDENCIES = listOf( "com.google.guava:guava-beta-checker:1.0", "com.sun.net.httpserver:http:20070405", "com.tngtech.archunit:archunit-junit5:1.2.1", - "io.prometheus:prometheus-metrics-exporter-httpserver:1.1.0", "com.uber.nullaway:nullaway:0.10.18", "edu.berkeley.cs.jqf:jqf-fuzz:1.7", // jqf-fuzz version 1.8+ requires Java 11+ "eu.rekawek.toxiproxy:toxiproxy-java:2.1.7", @@ -70,6 +69,7 @@ val DEPENDENCIES = listOf( "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.29.0-alpha", "io.opentracing:opentracing-api:0.33.0", "io.opentracing:opentracing-noop:0.33.0", + "io.prometheus:prometheus-metrics-exporter-httpserver:1.1.0", "junit:junit:4.13.2", "nl.jqno.equalsverifier:equalsverifier:3.15.4", "org.awaitility:awaitility:4.2.0", diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java index 5ed5b14d71f..67a5870015c 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java @@ -50,7 +50,6 @@ import io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot; import io.prometheus.metrics.model.snapshots.Unit; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -115,6 +114,10 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { @Nullable private MetricSnapshot convert(MetricData metricData) { + + // Note that AggregationTemporality.DELTA should never happen + // because PrometheusMetricReader#getAggregationTemporality returns CUMULATIVE. + MetricMetadata metadata = convertMetadata(metricData); InstrumentationScopeInfo scope = metricData.getInstrumentationScopeInfo(); switch (metricData.getType()) { @@ -165,87 +168,83 @@ private GaugeSnapshot convertLongGauge( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - GaugeDataPointSnapshot[] data = new GaugeDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = new ArrayList<>(dataPoints.size()); for (LongPointData longData : dataPoints) { - data[i++] = + data.add( new GaugeDataPointSnapshot( (double) longData.getValue(), convertAttributes(scope, longData.getAttributes()), - convertLongExemplar(longData.getExemplars())); + convertLongExemplar(longData.getExemplars()))); } - return new GaugeSnapshot(metadata, Arrays.asList(data)); + return new GaugeSnapshot(metadata, data); } private CounterSnapshot convertLongCounter( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - CounterDataPointSnapshot[] data = new CounterDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = + new ArrayList(dataPoints.size()); for (LongPointData longData : dataPoints) { - data[i++] = + data.add( new CounterDataPointSnapshot( (double) longData.getValue(), convertAttributes(scope, longData.getAttributes()), convertLongExemplar(longData.getExemplars()), - longData.getStartEpochNanos() / NANOS_PER_MILLISECOND); + longData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } - return new CounterSnapshot(metadata, Arrays.asList(data)); + return new CounterSnapshot(metadata, data); } private GaugeSnapshot convertDoubleGauge( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - GaugeDataPointSnapshot[] data = new GaugeDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = new ArrayList<>(dataPoints.size()); for (DoublePointData doubleData : dataPoints) { - data[i++] = + data.add( new GaugeDataPointSnapshot( doubleData.getValue(), convertAttributes(scope, doubleData.getAttributes()), - convertDoubleExemplar(doubleData.getExemplars())); + convertDoubleExemplar(doubleData.getExemplars()))); } - return new GaugeSnapshot(metadata, Arrays.asList(data)); + return new GaugeSnapshot(metadata, data); } private CounterSnapshot convertDoubleCounter( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - CounterDataPointSnapshot[] data = new CounterDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = new ArrayList<>(dataPoints.size()); for (DoublePointData doubleData : dataPoints) { - data[i++] = + data.add( new CounterDataPointSnapshot( doubleData.getValue(), convertAttributes(scope, doubleData.getAttributes()), convertDoubleExemplar(doubleData.getExemplars()), - doubleData.getStartEpochNanos() / NANOS_PER_MILLISECOND); + doubleData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } - return new CounterSnapshot(metadata, Arrays.asList(data)); + return new CounterSnapshot(metadata, data); } private HistogramSnapshot convertHistogram( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - HistogramDataPointSnapshot[] data = new HistogramDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = new ArrayList<>(dataPoints.size()); for (HistogramPointData histogramData : dataPoints) { List boundaries = new ArrayList<>(histogramData.getBoundaries().size() + 1); boundaries.addAll(histogramData.getBoundaries()); boundaries.add(Double.POSITIVE_INFINITY); - data[i++] = + data.add( new HistogramDataPointSnapshot( ClassicHistogramBuckets.of(boundaries, histogramData.getCounts()), histogramData.getSum(), convertAttributes(scope, histogramData.getAttributes()), convertDoubleExemplars(histogramData.getExemplars()), - histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND); + histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } - return new HistogramSnapshot(metadata, Arrays.asList(data)); + return new HistogramSnapshot(metadata, data); } @Nullable @@ -253,17 +252,22 @@ private HistogramSnapshot convertExponentialHistogram( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - HistogramDataPointSnapshot[] data = new HistogramDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = new ArrayList<>(dataPoints.size()); for (ExponentialHistogramPointData histogramData : dataPoints) { int scale = histogramData.getScale(); if (scale < -4) { - // Scale < -4 is not supported in Prometheus. Histograms with scale < -4 are dropped. + THROTTLING_LOGGER.log( + Level.WARNING, + "Dropping histogram " + + metadata.getName() + + " with attributes " + + histogramData.getAttributes() + + " because it has scale < -4 which is unsupported in Prometheus"); return null; } // Scale > 8 are not supported in Prometheus. Histograms with scale > 8 are scaled down to 8. int scaleDown = scale > 8 ? scale - 8 : 0; - data[i++] = + data.add( new HistogramDataPointSnapshot( scale - scaleDown, histogramData.getZeroCount(), @@ -273,13 +277,12 @@ private HistogramSnapshot convertExponentialHistogram( histogramData.getSum(), convertAttributes(scope, histogramData.getAttributes()), convertDoubleExemplars(histogramData.getExemplars()), - histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND); + histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } - return new HistogramSnapshot(metadata, Arrays.asList(data)); + return new HistogramSnapshot(metadata, data); } - @SuppressWarnings("MethodCanBeStatic") - private NativeHistogramBuckets convertExponentialHistogramBuckets( + private static NativeHistogramBuckets convertExponentialHistogramBuckets( ExponentialHistogramBuckets buckets, int scaleDown) { if (buckets.getBucketCounts().isEmpty()) { return NativeHistogramBuckets.EMPTY; @@ -308,33 +311,31 @@ private SummarySnapshot convertSummary( MetricMetadata metadata, InstrumentationScopeInfo scope, Collection dataPoints) { - SummaryDataPointSnapshot[] data = new SummaryDataPointSnapshot[dataPoints.size()]; - int i = 0; + List data = new ArrayList<>(dataPoints.size()); for (SummaryPointData summaryData : dataPoints) { - data[i++] = + data.add( new SummaryDataPointSnapshot( summaryData.getCount(), summaryData.getSum(), convertQuantiles(summaryData.getValues()), convertAttributes(scope, summaryData.getAttributes()), Exemplars.EMPTY, // Exemplars for Summaries not implemented yet. - summaryData.getStartEpochNanos() / NANOS_PER_MILLISECOND); + summaryData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } - return new SummarySnapshot(metadata, Arrays.asList(data)); + return new SummarySnapshot(metadata, data); } - @SuppressWarnings("MethodCanBeStatic") - private Quantiles convertQuantiles(List values) { - Quantile[] result = new Quantile[values.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = new Quantile(values.get(i).getQuantile(), values.get(i).getValue()); + private static Quantiles convertQuantiles(List values) { + List result = new ArrayList<>(values.size()); + for (ValueAtQuantile value : values) { + result.add(new Quantile(value.getQuantile(), value.getValue())); } return Quantiles.of(result); } @Nullable private Exemplar convertLongExemplar(List exemplars) { - if (exemplars.size() == 0) { + if (exemplars.isEmpty()) { return null; } else { LongExemplarData exemplar = exemplars.get(0); @@ -342,9 +343,10 @@ private Exemplar convertLongExemplar(List exemplars) { } } + /** Converts the first exemplar in the list if available, else returns {#code null}. */ @Nullable private Exemplar convertDoubleExemplar(List exemplars) { - if (exemplars.size() == 0) { + if (exemplars.isEmpty()) { return null; } else { DoubleExemplarData exemplar = exemplars.get(0); @@ -352,11 +354,11 @@ private Exemplar convertDoubleExemplar(List exemplars) { } } + /** Converts the first exemplar in the list if available, else returns {#code null}. */ private Exemplars convertDoubleExemplars(List exemplars) { - Exemplar[] result = new Exemplar[exemplars.size()]; - for (int i = 0; i < result.length; i++) { - DoubleExemplarData exemplar = exemplars.get(i); - result[i] = convertExemplar(exemplar.getValue(), exemplar); + List result = new ArrayList<>(exemplars.size()); + for (DoubleExemplarData exemplar : exemplars) { + result.add(convertExemplar(exemplar.getValue(), exemplar)); } return Exemplars.of(result); } @@ -398,6 +400,14 @@ private InfoSnapshot makeScopeInfo(Set scopes) { return new InfoSnapshot(new MetricMetadata("otel_scope"), prometheusScopeInfos); } + /** + * Convert OpenTelemetry attributes to Prometheus labels. + * + * @param scope will be converted to {@code otel_scope_*} labels if {@code otelScopeEnabled} is + * {@code true}. + * @param attributes the attributes to be converted. + * @param additionalAttributes optional list of key/value pairs, may be empty. + */ private Labels convertAttributes( @Nullable InstrumentationScopeInfo scope, Attributes attributes, @@ -434,8 +444,7 @@ private Labels convertAttributes( return Labels.of(names, values); } - @SuppressWarnings("MethodCanBeStatic") - private MetricMetadata convertMetadata(MetricData metricData) { + private static MetricMetadata convertMetadata(MetricData metricData) { String name = sanitizeMetricName(metricData.getName()); String help = metricData.getDescription(); Unit unit = PrometheusUnitsHelper.convertUnit(metricData.getUnit()); @@ -445,7 +454,8 @@ private MetricMetadata convertMetadata(MetricData metricData) { return new MetricMetadata(name, help, unit); } - private void putOrMerge(Map snapshotsByName, MetricSnapshot snapshot) { + private static void putOrMerge( + Map snapshotsByName, MetricSnapshot snapshot) { String name = snapshot.getMetadata().getName(); if (snapshotsByName.containsKey(name)) { MetricSnapshot merged = merge(snapshotsByName.get(name), snapshot); @@ -464,7 +474,7 @@ private void putOrMerge(Map snapshotsByName, MetricSnaps * type. If the type differs, we log a message and drop one of them. */ @Nullable - private MetricSnapshot merge(MetricSnapshot a, MetricSnapshot b) { + private static MetricSnapshot merge(MetricSnapshot a, MetricSnapshot b) { MetricMetadata metadata = mergeMetadata(a.getMetadata(), b.getMetadata()); if (metadata == null) { return null; @@ -512,8 +522,7 @@ private MetricSnapshot merge(MetricSnapshot a, MetricSnapshot b) { } @Nullable - @SuppressWarnings("MethodCanBeStatic") - private MetricMetadata mergeMetadata(MetricMetadata a, MetricMetadata b) { + private static MetricMetadata mergeMetadata(MetricMetadata a, MetricMetadata b) { String name = a.getPrometheusName(); if (a.getName().equals(b.getName())) { name = a.getName(); @@ -535,8 +544,7 @@ private MetricMetadata mergeMetadata(MetricMetadata a, MetricMetadata b) { return new MetricMetadata(name, help, unit); } - @SuppressWarnings("MethodCanBeStatic") - private String typeString(MetricSnapshot snapshot) { + private static String typeString(MetricSnapshot snapshot) { // Simple helper for a log message. return snapshot.getClass().getSimpleName().replace("Snapshot", "").toLowerCase(Locale.ENGLISH); } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java index d6330b50ecd..6b89884e6b7 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java @@ -66,7 +66,8 @@ public static PrometheusHttpServerBuilder builder() { .hostname(host) .port(port) .executorService(executor) - .defaultHandler(new MetricsHandler()) + .registry(prometheusRegistry) + .defaultHandler(new MetricsHandler(prometheusRegistry)) .buildAndStart(); } catch (IOException e) { throw new UncheckedIOException("Could not create Prometheus HTTP server", e); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java index db99362ad31..975091a93b5 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java @@ -20,7 +20,7 @@ public final class PrometheusHttpServerBuilder { private String host = DEFAULT_HOST; private int port = DEFAULT_PORT; - private PrometheusRegistry prometheusRegistry = PrometheusRegistry.defaultRegistry; + private PrometheusRegistry prometheusRegistry = new PrometheusRegistry(); private boolean otelScopeEnabled = true; @Nullable private ExecutorService executor; diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java index 41537635991..9f172e83804 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java @@ -9,7 +9,6 @@ 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.junit.jupiter.api.Assertions.assertEquals; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; @@ -114,16 +113,16 @@ void fetchPrometheus() { assertThat(response.status()).isEqualTo(HttpStatus.OK); assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) .isEqualTo("text/plain; version=0.0.4; charset=utf-8"); - assertEquals( - "# HELP grpc_name_total long_description\n" - + "# TYPE grpc_name_total counter\n" - + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" - + "# HELP http_name_total double_description\n" - + "# TYPE http_name_total counter\n" - + "http_name_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n" - + "# TYPE target_info gauge\n" - + "target_info{kr=\"vr\"} 1\n", - response.contentUtf8()); + assertThat(response.contentUtf8()) + .isEqualTo( + "# HELP grpc_name_total long_description\n" + + "# TYPE grpc_name_total counter\n" + + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" + + "# HELP http_name_total double_description\n" + + "# TYPE http_name_total counter\n" + + "http_name_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n" + + "# TYPE target_info gauge\n" + + "target_info{kr=\"vr\"} 1\n"); } @Test @@ -138,21 +137,20 @@ void fetchOpenMetrics() { "application/openmetrics-text")) .aggregate() .join(); - assertEquals(HttpStatus.OK, response.status()); - assertEquals( - "application/openmetrics-text; version=1.0.0; charset=utf-8", - response.headers().get(HttpHeaderNames.CONTENT_TYPE)); - assertEquals( - "# TYPE grpc_name counter\n" - + "# HELP grpc_name long_description\n" - + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" - + "# TYPE http_name counter\n" - + "# HELP http_name double_description\n" - + "http_name_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n" - + "# TYPE target info\n" - + "target_info{kr=\"vr\"} 1\n" - + "# EOF\n", - response.contentUtf8()); + assertThat(response.status()).isEqualTo(HttpStatus.OK); + assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) + .isEqualTo("application/openmetrics-text; version=1.0.0; charset=utf-8"); + assertThat(response.contentUtf8()) + .isEqualTo( + "# TYPE grpc_name counter\n" + + "# HELP grpc_name long_description\n" + + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" + + "# TYPE http_name counter\n" + + "# HELP http_name double_description\n" + + "http_name_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n" + + "# TYPE target info\n" + + "target_info{kr=\"vr\"} 1\n" + + "# EOF\n"); } @Test @@ -165,14 +163,14 @@ void fetchFiltered() { assertThat(response.status()).isEqualTo(HttpStatus.OK); assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) .isEqualTo("text/plain; version=0.0.4; charset=utf-8"); - assertEquals( - "" - + "# HELP grpc_name_total long_description\n" - + "# TYPE grpc_name_total counter\n" - + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" - + "# TYPE target_info gauge\n" - + "target_info{kr=\"vr\"} 1\n", - response.contentUtf8()); + assertThat(response.contentUtf8()) + .isEqualTo( + "" + + "# HELP grpc_name_total long_description\n" + + "# TYPE grpc_name_total counter\n" + + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" + + "# TYPE target_info gauge\n" + + "target_info{kr=\"vr\"} 1\n"); } @Test @@ -189,16 +187,16 @@ void fetchPrometheusCompressed() throws IOException { assertThat(response.headers().get(HttpHeaderNames.CONTENT_ENCODING)).isEqualTo("gzip"); GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(response.content().array())); String content = new String(ByteStreams.toByteArray(gis), StandardCharsets.UTF_8); - assertEquals( - "# HELP grpc_name_total long_description\n" - + "# TYPE grpc_name_total counter\n" - + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" - + "# HELP http_name_total double_description\n" - + "# TYPE http_name_total counter\n" - + "http_name_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n" - + "# TYPE target_info gauge\n" - + "target_info{kr=\"vr\"} 1\n", - content); + assertThat(content) + .isEqualTo( + "# HELP grpc_name_total long_description\n" + + "# TYPE grpc_name_total counter\n" + + "grpc_name_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n" + + "# HELP http_name_total double_description\n" + + "# TYPE http_name_total counter\n" + + "http_name_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n" + + "# TYPE target_info gauge\n" + + "target_info{kr=\"vr\"} 1\n"); } @Test @@ -263,14 +261,13 @@ void fetch_DuplicateMetrics() { assertThat(response.status()).isEqualTo(HttpStatus.OK); assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) .isEqualTo("text/plain; version=0.0.4; charset=utf-8"); - assertEquals( - "" - + "# TYPE foo_unit_total counter\n" - + "foo_unit_total{otel_scope_name=\"scope1\"} 1.0\n" - + "foo_unit_total{otel_scope_name=\"scope2\"} 2.0\n" - + "# TYPE target_info gauge\n" - + "target_info{kr=\"vr\"} 1\n", - response.contentUtf8()); + assertThat(response.contentUtf8()) + .isEqualTo( + "# TYPE foo_unit_total counter\n" + + "foo_unit_total{otel_scope_name=\"scope1\"} 1.0\n" + + "foo_unit_total{otel_scope_name=\"scope2\"} 2.0\n" + + "# TYPE target_info gauge\n" + + "target_info{kr=\"vr\"} 1\n"); // Validate conflict warning message assertThat(logs.getEvents()).hasSize(1); diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java index 426c3e66d7b..95a22379ef9 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java @@ -5,7 +5,8 @@ package io.opentelemetry.exporter.prometheus; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleCounter; @@ -33,17 +34,18 @@ import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; +import java.util.Arrays; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -public class PrometheusMetricReaderTest { +class PrometheusMetricReaderTest { private final TestClock testClock = TestClock.create(); private String createdTimestamp; @@ -52,7 +54,7 @@ public class PrometheusMetricReaderTest { private Tracer tracer; @BeforeEach - public void setUp() { + void setUp() { this.testClock.setTime(Instant.ofEpochMilli((System.currentTimeMillis() / 100) * 100)); this.createdTimestamp = convertTimestamp(testClock.now()); this.reader = new PrometheusMetricReader(true); @@ -75,7 +77,7 @@ public void setUp() { } @Test - public void testLongCounterComplete() throws IOException { + void longCounterComplete() throws IOException { LongCounter counter = meter .counterBuilder("requests.size") @@ -98,7 +100,7 @@ public void testLongCounterComplete() throws IOException { } @Test - public void testDoubleCounterComplete() throws IOException { + void doubleCounterComplete() throws IOException { DoubleCounter counter = meter .counterBuilder("requests.size") @@ -151,14 +153,14 @@ private void assertCounterComplete(MetricSnapshots snapshots, Span span1, Span s } @Test - public void testLongCounterMinimal() throws IOException { + void longCounterMinimal() throws IOException { LongCounter counter = meter.counterBuilder("requests").build(); counter.add(2); assertCounterMinimal(reader.collect()); } @Test - public void testDoubleCounterMinimal() throws IOException { + void doubleCounterMinimal() throws IOException { DoubleCounter counter = meter.counterBuilder("requests").ofDoubles().build(); counter.add(2.0); assertCounterMinimal(reader.collect()); @@ -175,11 +177,11 @@ private void assertCounterMinimal(MetricSnapshots snapshots) throws IOException + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(snapshots)); + assertThat(toOpenMetrics(snapshots)).isEqualTo(expected); } @Test - public void testLongUpDownCounterComplete() throws IOException { + void longUpDownCounterComplete() throws IOException { LongUpDownCounter counter = meter .upDownCounterBuilder("queue.size") @@ -202,7 +204,7 @@ public void testLongUpDownCounterComplete() throws IOException { } @Test - public void testDoubleUpDownCounterComplete() throws IOException { + void doubleUpDownCounterComplete() throws IOException { DoubleUpDownCounter counter = meter .upDownCounterBuilder("queue.size") @@ -225,7 +227,7 @@ public void testDoubleUpDownCounterComplete() throws IOException { assertUpDownCounterComplete(reader.collect(), span1, span2); } - private void assertUpDownCounterComplete(MetricSnapshots snapshots, Span span1, Span span2) + private static void assertUpDownCounterComplete(MetricSnapshots snapshots, Span span1, Span span2) throws IOException { String expected = "" @@ -249,20 +251,20 @@ private void assertUpDownCounterComplete(MetricSnapshots snapshots, Span span1, } @Test - public void testLongUpDownCounterMinimal() throws IOException { + void longUpDownCounterMinimal() throws IOException { LongUpDownCounter counter = meter.upDownCounterBuilder("users.active").build(); counter.add(27); assertUpDownCounterMinimal(reader.collect()); } @Test - public void testDoubleUpDownCounterMinimal() throws IOException { + void doubleUpDownCounterMinimal() throws IOException { DoubleUpDownCounter counter = meter.upDownCounterBuilder("users.active").ofDoubles().build(); counter.add(27.0); assertUpDownCounterMinimal(reader.collect()); } - private void assertUpDownCounterMinimal(MetricSnapshots snapshots) throws IOException { + private static void assertUpDownCounterMinimal(MetricSnapshots snapshots) throws IOException { String expected = "" + "# TYPE target info\n" @@ -270,11 +272,11 @@ private void assertUpDownCounterMinimal(MetricSnapshots snapshots) throws IOExce + "# TYPE users_active gauge\n" + "users_active{otel_scope_name=\"test\"} 27.0\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(snapshots)); + assertThat(toOpenMetrics(snapshots)).isEqualTo(expected); } @Test - public void testLongGaugeComplete() throws IOException { + void longGaugeComplete() throws IOException { meter .gaugeBuilder("temperature") .setUnit("Cel") @@ -289,7 +291,7 @@ public void testLongGaugeComplete() throws IOException { } @Test - public void testDoubleGaugeComplete() throws IOException { + void doubleGaugeComplete() throws IOException { meter .gaugeBuilder("temperature") .setUnit("Cel") @@ -302,7 +304,7 @@ public void testDoubleGaugeComplete() throws IOException { assertGaugeComplete(reader.collect()); } - private void assertGaugeComplete(MetricSnapshots snapshots) throws IOException { + private static void assertGaugeComplete(MetricSnapshots snapshots) throws IOException { String expected = "" + "# TYPE target info\n" @@ -313,22 +315,22 @@ private void assertGaugeComplete(MetricSnapshots snapshots) throws IOException { + "temperature_celsius{location=\"inside\",otel_scope_name=\"test\"} 23.0\n" + "temperature_celsius{location=\"outside\",otel_scope_name=\"test\"} 17.0\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(snapshots)); + assertThat(toOpenMetrics(snapshots)).isEqualTo(expected); } @Test - public void testLongGaugeMinimal() throws IOException { + void longGaugeMinimal() throws IOException { meter.gaugeBuilder("my_gauge").ofLongs().buildWithCallback(m -> m.record(2)); assertGaugeMinimal(reader.collect()); } @Test - public void testDoubleGaugeMinimal() throws IOException { + void doubleGaugeMinimal() throws IOException { meter.gaugeBuilder("my_gauge").buildWithCallback(m -> m.record(2.0)); assertGaugeMinimal(reader.collect()); } - private void assertGaugeMinimal(MetricSnapshots snapshots) throws IOException { + private static void assertGaugeMinimal(MetricSnapshots snapshots) throws IOException { String expected = "" + "# TYPE my_gauge gauge\n" @@ -336,11 +338,11 @@ private void assertGaugeMinimal(MetricSnapshots snapshots) throws IOException { + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(snapshots)); + assertThat(toOpenMetrics(snapshots)).isEqualTo(expected); } @Test - public void testLongHistogramComplete() throws IOException { + void longHistogramComplete() throws IOException { LongHistogram histogram = meter .histogramBuilder("request.size") @@ -370,7 +372,7 @@ public void testLongHistogramComplete() throws IOException { } @Test - public void testDoubleHistogramComplete() throws IOException { + void doubleHistogramComplete() throws IOException { DoubleHistogram histogram = meter .histogramBuilder("request.size") @@ -466,7 +468,7 @@ private void assertHistogramComplete( } @Test - public void testLongHistogramMinimal() throws IOException { + void longHistogramMinimal() throws IOException { LongHistogram histogram = meter.histogramBuilder("request.size").ofLongs().build(); histogram.record(173); histogram.record(173); @@ -475,7 +477,7 @@ public void testLongHistogramMinimal() throws IOException { } @Test - public void testDoubleHistogramMinimal() throws IOException { + void doubleHistogramMinimal() throws IOException { DoubleHistogram histogram = meter.histogramBuilder("request.size").build(); histogram.record(173.0); histogram.record(173.0); @@ -511,12 +513,12 @@ private void assertHistogramMinimal(MetricSnapshots snapshots) throws IOExceptio + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(snapshots)); + assertThat(toOpenMetrics(snapshots)).isEqualTo(expected); } @Test @Disabled("disabled until #6010 is fixed") - public void testExponentialLongHistogramComplete() throws IOException { + void exponentialLongHistogramComplete() throws IOException { LongHistogram histogram = meter .histogramBuilder("my.exponential.histogram") @@ -541,7 +543,7 @@ public void testExponentialLongHistogramComplete() throws IOException { } @Test - public void testExponentialDoubleHistogramComplete() throws IOException { + void exponentialDoubleHistogramComplete() throws IOException { DoubleHistogram histogram = meter .histogramBuilder("my.exponential.histogram") @@ -564,8 +566,8 @@ public void testExponentialDoubleHistogramComplete() throws IOException { assertExponentialHistogramComplete(reader.collect(), span1, span2); } - private void assertExponentialHistogramComplete(MetricSnapshots snapshots, Span span1, Span span2) - throws IOException { + private static void assertExponentialHistogramComplete( + MetricSnapshots snapshots, Span span1, Span span2) { String expected = "" + "name: \"my_exponential_histogram_bytes\"\n" @@ -688,20 +690,20 @@ private void assertExponentialHistogramComplete(MetricSnapshots snapshots, Span } @Test - public void testExponentialLongHistogramMinimal() throws IOException { + void exponentialLongHistogramMinimal() throws IOException { LongHistogram histogram = meter.histogramBuilder("my.exponential.histogram").ofLongs().build(); histogram.record(1, Attributes.builder().put("animal", "bear").build()); assertExponentialHistogramMinimal(reader.collect()); } @Test - public void testExponentialDoubleHistogramMinimal() throws IOException { + void exponentialDoubleHistogramMinimal() throws IOException { DoubleHistogram histogram = meter.histogramBuilder("my.exponential.histogram").build(); histogram.record(1.0, Attributes.builder().put("animal", "bear").build()); assertExponentialHistogramMinimal(reader.collect()); } - private void assertExponentialHistogramMinimal(MetricSnapshots snapshots) throws IOException { + private static void assertExponentialHistogramMinimal(MetricSnapshots snapshots) { String expected = "" + "name: \"my_exponential_histogram\"\n" @@ -756,7 +758,7 @@ private void assertExponentialHistogramMinimal(MetricSnapshots snapshots) throws } @Test - public void testExponentialHistogramBucketConversion() { + void exponentialHistogramBucketConversion() { Random random = new Random(); for (int i = 0; i < 100_000; i++) { int otelScale = random.nextInt(24) - 4; @@ -783,21 +785,20 @@ public void testExponentialHistogramBucketConversion() { MetricSnapshots snapshots = reader.collect(); HistogramSnapshot snapshot = (HistogramSnapshot) snapshots.get(0); HistogramDataPointSnapshot dataPoint = snapshot.getDataPoints().get(0); - Assertions.assertEquals(prometheusScale, dataPoint.getNativeSchema()); + assertThat(dataPoint.getNativeSchema()).isEqualTo(prometheusScale); NativeHistogramBuckets buckets = dataPoint.getNativeBucketsForPositiveValues(); - Assertions.assertEquals(1, buckets.size()); + assertThat(buckets.size()).isEqualTo(1); int index = buckets.getBucketIndex(0); double base = Math.pow(2, Math.pow(2, -prometheusScale)); double lowerBound = Math.pow(base, index - 1); double upperBound = Math.pow(base, index); - Assertions.assertTrue(lowerBound < observation); - Assertions.assertTrue(upperBound >= observation); + assertThat(lowerBound).isLessThan(observation); + assertThat(upperBound).isGreaterThanOrEqualTo(observation); } } @Test - @SuppressWarnings("SystemOut") - public void testExponentialLongHistogramScaleDown() throws IOException { + void exponentialLongHistogramScaleDown() throws IOException { // The following histogram will have the default scale, which is 20. DoubleHistogram histogram = meter.histogramBuilder("my.exponential.histogram").build(); double base = Math.pow(2, Math.pow(2, -20)); @@ -811,23 +812,23 @@ public void testExponentialLongHistogramScaleDown() throws IOException { MetricSnapshots snapshots = reader.collect(); HistogramSnapshot snapshot = (HistogramSnapshot) snapshots.get(0); HistogramDataPointSnapshot dataPoint = snapshot.getDataPoints().get(0); - Assertions.assertEquals(8, dataPoint.getNativeSchema()); // scaled down from 20 to 8. + assertThat(dataPoint.getNativeSchema()).isEqualTo(8); // scaled down from 20 to 8. NativeHistogramBuckets buckets = dataPoint.getNativeBucketsForPositiveValues(); - Assertions.assertEquals(3, buckets.size()); + assertThat(buckets.size()).isEqualTo(3); // In bucket 0 we have exactly one observation: the value 1.0 - Assertions.assertEquals(0, buckets.getBucketIndex(0)); - Assertions.assertEquals(1, buckets.getCount(0)); + assertThat(buckets.getBucketIndex(0)).isEqualTo(0); + assertThat(buckets.getCount(0)).isEqualTo(1); // In bucket 1 we have 4095 observations - Assertions.assertEquals(1, buckets.getBucketIndex(1)); - Assertions.assertEquals(4095, buckets.getCount(1)); + assertThat(buckets.getBucketIndex(1)).isEqualTo(1); + assertThat(buckets.getCount(1)).isEqualTo(4095); // In bucket 2 we have 10 observations (despite the empty buckets all observations fall into the // same bucket at scale 8) - Assertions.assertEquals(2, buckets.getBucketIndex(2)); - Assertions.assertEquals(10, buckets.getCount(2)); + assertThat(buckets.getBucketIndex(2)).isEqualTo(2); + assertThat(buckets.getCount(2)).isEqualTo(10); } @Test - public void testInstrumentationScope() throws IOException { + void instrumentationScope() throws IOException { SdkMeterProvider meterProvider = SdkMeterProvider.builder() .setClock(testClock) @@ -867,11 +868,11 @@ public void testInstrumentationScope() throws IOException { + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } @Test - public void testNameSuffix() throws IOException { + void nameSuffix() throws IOException { LongCounter unitAndTotal = meter.counterBuilder("request.duration.seconds.total").setUnit("s").build(); unitAndTotal.add(1); @@ -910,11 +911,11 @@ public void testNameSuffix() throws IOException { + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } @Test - public void testNameSuffixUnit() throws IOException { + void nameSuffixUnit() throws IOException { LongCounter counter = meter.counterBuilder("request.duration.seconds").setUnit("s").build(); counter.add(1); String expected = @@ -928,11 +929,11 @@ public void testNameSuffixUnit() throws IOException { + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } @Test - public void testIllegalCharacters() throws IOException { + void illegalCharacters() throws IOException { LongCounter counter = meter.counterBuilder("prod/request.count").build(); counter.add(1, Attributes.builder().put("user-count", 30).build()); String expected = @@ -945,11 +946,11 @@ public void testIllegalCharacters() throws IOException { + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } @Test - public void testCreatedTimestamp() throws IOException { + void createdTimestamp() throws IOException { LongCounter counter = meter.counterBuilder("requests").build(); testClock.advance(Duration.ofMillis(1)); @@ -980,12 +981,11 @@ public void testCreatedTimestamp() throws IOException { + "# TYPE target info\n" + "target_info{service_name=\"unknown_service:java\",telemetry_sdk_language=\"java\",telemetry_sdk_name=\"opentelemetry\",telemetry_sdk_version=\"1.x.x\"} 1\n" + "# EOF\n"; - - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } @Test - public void testOtelScopeComplete() throws IOException { + void otelScopeComplete() throws IOException { // There is currently no API for adding scope attributes. // However, we can at least test the otel_scope_version attribute. Meter meter = @@ -1010,11 +1010,11 @@ public void testOtelScopeComplete() throws IOException { + createdTimestamp + "\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } @Test - public void testOtelScopeDisabled() throws IOException { + void otelScopeDisabled() throws IOException { PrometheusMetricReader reader = new PrometheusMetricReader(false); Meter meter = SdkMeterProvider.builder() @@ -1038,43 +1038,37 @@ public void testOtelScopeDisabled() throws IOException { + createdTimestamp + "\n" + "# EOF\n"; - assertEquals(expected, toOpenMetrics(reader.collect())); + assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } /** - * Unfortunately there is no easy way to use {@link TestClock} for Exemplar timestamps. The - * following is like {@code assertEquals()}, but {@code } matches arbitrary timestamps. + * Unfortunately there is no easy way to use {@link TestClock} for Exemplar timestamps. Test if + * {@code expected} equals {@code actual} but {@code } matches arbitrary timestamps. */ - @SuppressWarnings("MethodCanBeStatic") - private void assertMatches(String expected, String actual) { + private static void assertMatches(String expected, String actual) { String[] parts = expected.split(Pattern.quote("")); - StringBuilder regex = new StringBuilder(); - for (int i = 0; i < parts.length; i++) { - regex.append(Pattern.quote(parts[i])); - if (i <= parts.length - 2) { - regex.append("[0-9]+(\\.[0-9]+)?"); - } - } - Assertions.assertTrue( - Pattern.matches(regex.toString(), actual), "Expected: " + expected + "\nActual: " + actual); + String timestampRegex = "[0-9]+(\\.[0-9]+)?"; + + String regex = Arrays.stream(parts).map(Pattern::quote).collect(joining(timestampRegex)); + + assertThat(actual) + .as("Expected: " + expected + "\nActual: " + actual) + .matches(Pattern.compile(regex)); } - @SuppressWarnings({"MethodCanBeStatic", "DefaultCharset"}) - private String toOpenMetrics(MetricSnapshots snapshots) throws IOException { + private static String toOpenMetrics(MetricSnapshots snapshots) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); writer.write(out, snapshots); - return out.toString(); + return out.toString(StandardCharsets.UTF_8.name()); } - @SuppressWarnings({"MethodCanBeStatic", "DefaultCharset"}) - private String toPrometheusProtobuf(MetricSnapshots snapshots) throws IOException { + private static String toPrometheusProtobuf(MetricSnapshots snapshots) { PrometheusProtobufWriter writer = new PrometheusProtobufWriter(); return writer.toDebugString(snapshots); } - @SuppressWarnings("MethodCanBeStatic") - private String convertTimestamp(long nanoTime) { + private static String convertTimestamp(long nanoTime) { String millis = Long.toString(TimeUnit.NANOSECONDS.toMillis(nanoTime)); return millis.substring(0, millis.length() - 3) + "." + millis.substring(millis.length() - 3); }