From 47168b25e16012a64abbd0257b29ccaa55cf0a89 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 6 Jun 2025 16:28:52 +0200 Subject: [PATCH 01/14] add option to skip scope_info metric --- .../prometheus/Otel2PrometheusConverter.java | 18 ++++++------- .../exporter/prometheus/OtelScopeMode.java | 26 +++++++++++++++++++ .../prometheus/PrometheusHttpServer.java | 4 +-- .../PrometheusHttpServerBuilder.java | 10 +++---- .../prometheus/PrometheusMetricReader.java | 6 ++--- .../Otel2PrometheusConverterTest.java | 6 ++--- .../prometheus/PrometheusHttpServerTest.java | 4 +-- .../PrometheusMetricReaderTest.java | 8 +++--- 8 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java 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 afa04a81394..9d78f25610f 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 @@ -82,7 +82,7 @@ final class Otel2PrometheusConverter { private static final long NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1); static final int MAX_CACHE_SIZE = 10; - private final boolean otelScopeEnabled; + private final OtelScopeMode otelScopeMode; @Nullable private final Predicate allowedResourceAttributesFilter; /** @@ -94,14 +94,14 @@ final class Otel2PrometheusConverter { /** * Constructor with feature flag parameter. * - * @param otelScopeEnabled enable generation of the OpenTelemetry instrumentation scope info + * @param otelScopeMode enable generation of the OpenTelemetry instrumentation scope info * metric and labels. * @param allowedResourceAttributesFilter if not {@code null}, resource attributes with keys * matching this predicate will be added as labels on each exported metric */ Otel2PrometheusConverter( - boolean otelScopeEnabled, @Nullable Predicate allowedResourceAttributesFilter) { - this.otelScopeEnabled = otelScopeEnabled; + OtelScopeMode otelScopeMode, @Nullable Predicate allowedResourceAttributesFilter) { + this.otelScopeMode = otelScopeMode; this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.resourceAttributesToAllowedKeysCache = allowedResourceAttributesFilter != null @@ -125,14 +125,14 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeEnabled && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { + if (otelScopeMode.isEnabled() && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } if (resource != null) { putOrMerge(snapshotsByName, makeTargetInfo(resource)); } - if (otelScopeEnabled && !scopes.isEmpty()) { + if (otelScopeMode.isScopeInfoEnabled() && !scopes.isEmpty()) { putOrMerge(snapshotsByName, makeScopeInfo(scopes)); } return new MetricSnapshots(snapshotsByName.values()); @@ -457,8 +457,8 @@ private InfoSnapshot makeScopeInfo(Set scopes) { * Convert OpenTelemetry attributes to Prometheus labels. * * @param resource optional resource (attributes) to be converted. - * @param scope will be converted to {@code otel_scope_*} labels if {@code otelScopeEnabled} is - * {@code true}. + * @param scope will be converted to {@code otel_scope_*} labels if {@code otelScopeMode} is + * {@link OtelScopeMode#isEnabled()}. * @param attributes the attributes to be converted. * @param additionalAttributes optional list of key/value pairs, may be empty. */ @@ -485,7 +485,7 @@ private Labels convertAttributes( requireNonNull(additionalAttributes[i]), additionalAttributes[i + 1]); } - if (otelScopeEnabled && scope != null) { + if (otelScopeMode.isEnabled() && scope != null) { labelNameToValue.putIfAbsent(OTEL_SCOPE_NAME, scope.getName()); if (scope.getVersion() != null) { labelNameToValue.putIfAbsent(OTEL_SCOPE_VERSION, scope.getVersion()); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java new file mode 100644 index 00000000000..af8ef137b4d --- /dev/null +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java @@ -0,0 +1,26 @@ +package io.opentelemetry.exporter.prometheus; + +public enum OtelScopeMode { + /** No scope information is exported. */ + DISABLED, + /** + * Only the labels of the metrics are exported, but not the scope_info info metrics. + * + *

This will eventually be the default mode, but is opt-in for now. + */ + LABELS_ONLY, + /** + * Both labels and scope_info are exported. + * + *

This is the default mode. + */ + LABELS_AND_SCOPE_INFO; + + boolean isEnabled() { + return this != DISABLED; + } + + boolean isScopeInfoEnabled() { + return this == LABELS_AND_SCOPE_INFO; + } +} 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 e7b28e42acf..8a69c234f16 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 @@ -72,7 +72,7 @@ public static PrometheusHttpServerBuilder builder() { int port, @Nullable ExecutorService executor, PrometheusRegistry prometheusRegistry, - boolean otelScopeEnabled, + OtelScopeMode otelScopeMode, @Nullable Predicate allowedResourceAttributesFilter, MemoryMode memoryMode, @Nullable HttpHandler defaultHandler, @@ -86,7 +86,7 @@ public static PrometheusHttpServerBuilder builder() { this.defaultAggregationSelector = defaultAggregationSelector; this.builder = builder; this.prometheusMetricReader = - new PrometheusMetricReader(otelScopeEnabled, allowedResourceAttributesFilter); + new PrometheusMetricReader(otelScopeMode, allowedResourceAttributesFilter); this.prometheusRegistry = prometheusRegistry; prometheusRegistry.register(prometheusMetricReader); // When memory mode is REUSABLE_DATA, concurrent reads lead to data corruption. To prevent this, 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 9487ae61583..1f1f0cbe08d 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 @@ -30,7 +30,7 @@ public final class PrometheusHttpServerBuilder { private String host = DEFAULT_HOST; private int port = DEFAULT_PORT; private PrometheusRegistry prometheusRegistry = new PrometheusRegistry(); - private boolean otelScopeEnabled = true; + private OtelScopeMode otelScopeMode = OtelScopeMode.LABELS_AND_SCOPE_INFO; @Nullable private Predicate allowedResourceAttributesFilter; @Nullable private ExecutorService executor; private MemoryMode memoryMode = DEFAULT_MEMORY_MODE; @@ -45,7 +45,7 @@ public final class PrometheusHttpServerBuilder { this.host = builder.host; this.port = builder.port; this.prometheusRegistry = builder.prometheusRegistry; - this.otelScopeEnabled = builder.otelScopeEnabled; + this.otelScopeMode = builder.otelScopeMode; this.allowedResourceAttributesFilter = builder.allowedResourceAttributesFilter; this.executor = builder.executor; this.memoryMode = builder.memoryMode; @@ -85,8 +85,8 @@ public PrometheusHttpServerBuilder setPrometheusRegistry(PrometheusRegistry prom /** Set if the {@code otel_scope_*} attributes are generated. Default is {@code true}. */ @SuppressWarnings("UnusedReturnValue") - public PrometheusHttpServerBuilder setOtelScopeEnabled(boolean otelScopeEnabled) { - this.otelScopeEnabled = otelScopeEnabled; + public PrometheusHttpServerBuilder setOtelScopeMode(OtelScopeMode otelScopeMode) { + this.otelScopeMode = otelScopeMode; return this; } @@ -176,7 +176,7 @@ public PrometheusHttpServer build() { port, executor, prometheusRegistry, - otelScopeEnabled, + otelScopeMode, allowedResourceAttributesFilter, memoryMode, defaultHandler, diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index b51c83ab6f7..bc96bc8fbf1 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -29,11 +29,11 @@ public class PrometheusMetricReader implements MetricReader, MultiCollector { private final Otel2PrometheusConverter converter; // TODO: refactor to public static create or builder pattern to align with project style - /** See {@link Otel2PrometheusConverter#Otel2PrometheusConverter(boolean, Predicate)}. */ + /** See {@link Otel2PrometheusConverter#Otel2PrometheusConverter(OtelScopeMode, Predicate)}. */ public PrometheusMetricReader( - boolean otelScopeEnabled, @Nullable Predicate allowedResourceAttributesFilter) { + OtelScopeMode otelScopeMode, @Nullable Predicate allowedResourceAttributesFilter) { this.converter = - new Otel2PrometheusConverter(otelScopeEnabled, allowedResourceAttributesFilter); + new Otel2PrometheusConverter(otelScopeMode, allowedResourceAttributesFilter); } @Override diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java index 4e718c78629..1908ea726c9 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java @@ -71,7 +71,7 @@ class Otel2PrometheusConverterTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final Otel2PrometheusConverter converter = - new Otel2PrometheusConverter(true, /* allowedResourceAttributesFilter= */ null); + new Otel2PrometheusConverter(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); @ParameterizedTest @MethodSource("metricMetadataArgs") @@ -201,7 +201,7 @@ void resourceAttributesAddition( throws IOException { Otel2PrometheusConverter converter = - new Otel2PrometheusConverter(true, allowedResourceAttributesFilter); + new Otel2PrometheusConverter(OtelScopeMode.LABELS_AND_SCOPE_INFO, allowedResourceAttributesFilter); ByteArrayOutputStream out = new ByteArrayOutputStream(); MetricSnapshots snapshots = converter.convert(Collections.singletonList(metricData)); @@ -501,7 +501,7 @@ void validateCacheIsBounded() { }; Otel2PrometheusConverter otel2PrometheusConverter = - new Otel2PrometheusConverter(true, /* allowedResourceAttributesFilter= */ countPredicate); + new Otel2PrometheusConverter(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ countPredicate); // Create 20 different metric data objects with 2 different resource attributes; Resource resource1 = Resource.builder().put("cluster", "cluster1").build(); 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 2b1f27b8a3e..38a86b30a53 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 @@ -528,7 +528,7 @@ void toBuilder() { PrometheusHttpServerBuilder builder = PrometheusHttpServer.builder(); builder.setHost("localhost"); builder.setPort(1234); - builder.setOtelScopeEnabled(false); + builder.setOtelScopeMode(OtelScopeMode.DISABLED); Predicate resourceAttributesFilter = s -> false; builder.setAllowedResourceAttributesFilter(resourceAttributesFilter); @@ -555,7 +555,7 @@ public Result authenticate(HttpExchange exchange) { .isInstanceOf(PrometheusHttpServerBuilder.class) .hasFieldOrPropertyWithValue("host", "localhost") .hasFieldOrPropertyWithValue("port", 1234) - .hasFieldOrPropertyWithValue("otelScopeEnabled", false) + .hasFieldOrPropertyWithValue("otelScopeMode", OtelScopeMode.DISABLED) .hasFieldOrPropertyWithValue("allowedResourceAttributesFilter", resourceAttributesFilter) .hasFieldOrPropertyWithValue("executor", executor) .hasFieldOrPropertyWithValue("prometheusRegistry", prometheusRegistry) 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 20076cc9c9d..87f5681ca41 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 @@ -61,7 +61,7 @@ class PrometheusMetricReaderTest { void setUp() { this.testClock.setTime(Instant.ofEpochMilli((System.currentTimeMillis() / 100) * 100)); this.createdTimestamp = convertTimestamp(testClock.now()); - this.reader = new PrometheusMetricReader(true, /* allowedResourceAttributesFilter= */ null); + this.reader = new PrometheusMetricReader(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); this.meter = SdkMeterProvider.builder() .setClock(testClock) @@ -776,7 +776,7 @@ void exponentialHistogramBucketConversion() { int otelScale = random.nextInt(24) - 4; int prometheusScale = Math.min(otelScale, 8); PrometheusMetricReader reader = - new PrometheusMetricReader(true, /* allowedResourceAttributesFilter= */ null); + new PrometheusMetricReader(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .registerMetricReader(reader) @@ -1029,7 +1029,7 @@ void otelScopeComplete() throws IOException { @Test void otelScopeDisabled() throws IOException { PrometheusMetricReader reader = - new PrometheusMetricReader(false, /* allowedResourceAttributesFilter= */ null); + new PrometheusMetricReader(OtelScopeMode.DISABLED, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .setClock(testClock) @@ -1060,7 +1060,7 @@ void otelScopeDisabled() throws IOException { void addResourceAttributesWorks() throws IOException { PrometheusMetricReader reader = new PrometheusMetricReader( - true, /* allowedResourceAttributesFilter= */ Predicates.is("cluster")); + OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ Predicates.is("cluster")); Meter meter = SdkMeterProvider.builder() .setClock(testClock) From da186d6af5346d8e87f40eb855febba54fc95c8a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 10 Jun 2025 12:17:20 +0200 Subject: [PATCH 02/14] format --- .../prometheus/Otel2PrometheusConverter.java | 9 +++++---- .../exporter/prometheus/OtelScopeMode.java | 5 +++++ .../exporter/prometheus/PrometheusMetricReader.java | 3 +-- .../prometheus/Otel2PrometheusConverterTest.java | 10 +++++++--- .../prometheus/PrometheusMetricReaderTest.java | 13 +++++++++---- 5 files changed, 27 insertions(+), 13 deletions(-) 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 9d78f25610f..ff4dfaf524d 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 @@ -94,8 +94,8 @@ final class Otel2PrometheusConverter { /** * Constructor with feature flag parameter. * - * @param otelScopeMode enable generation of the OpenTelemetry instrumentation scope info - * metric and labels. + * @param otelScopeMode enable generation of the OpenTelemetry instrumentation scope info metric + * and labels. * @param allowedResourceAttributesFilter if not {@code null}, resource attributes with keys * matching this predicate will be added as labels on each exported metric */ @@ -125,7 +125,8 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeMode.isEnabled() && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { + if (otelScopeMode.isEnabled() + && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } @@ -458,7 +459,7 @@ private InfoSnapshot makeScopeInfo(Set scopes) { * * @param resource optional resource (attributes) to be converted. * @param scope will be converted to {@code otel_scope_*} labels if {@code otelScopeMode} is - * {@link OtelScopeMode#isEnabled()}. + * {@link OtelScopeMode#isEnabled()}. * @param attributes the attributes to be converted. * @param additionalAttributes optional list of key/value pairs, may be empty. */ diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java index af8ef137b4d..a569bf65801 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.exporter.prometheus; public enum OtelScopeMode { diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index bc96bc8fbf1..64ce2cef0df 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -32,8 +32,7 @@ public class PrometheusMetricReader implements MetricReader, MultiCollector { /** See {@link Otel2PrometheusConverter#Otel2PrometheusConverter(OtelScopeMode, Predicate)}. */ public PrometheusMetricReader( OtelScopeMode otelScopeMode, @Nullable Predicate allowedResourceAttributesFilter) { - this.converter = - new Otel2PrometheusConverter(otelScopeMode, allowedResourceAttributesFilter); + this.converter = new Otel2PrometheusConverter(otelScopeMode, allowedResourceAttributesFilter); } @Override diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java index 1908ea726c9..e4680ed4dff 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java @@ -71,7 +71,8 @@ class Otel2PrometheusConverterTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final Otel2PrometheusConverter converter = - new Otel2PrometheusConverter(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); + new Otel2PrometheusConverter( + OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); @ParameterizedTest @MethodSource("metricMetadataArgs") @@ -201,7 +202,8 @@ void resourceAttributesAddition( throws IOException { Otel2PrometheusConverter converter = - new Otel2PrometheusConverter(OtelScopeMode.LABELS_AND_SCOPE_INFO, allowedResourceAttributesFilter); + new Otel2PrometheusConverter( + OtelScopeMode.LABELS_AND_SCOPE_INFO, allowedResourceAttributesFilter); ByteArrayOutputStream out = new ByteArrayOutputStream(); MetricSnapshots snapshots = converter.convert(Collections.singletonList(metricData)); @@ -501,7 +503,9 @@ void validateCacheIsBounded() { }; Otel2PrometheusConverter otel2PrometheusConverter = - new Otel2PrometheusConverter(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ countPredicate); + new Otel2PrometheusConverter( + OtelScopeMode.LABELS_AND_SCOPE_INFO, + /* allowedResourceAttributesFilter= */ countPredicate); // Create 20 different metric data objects with 2 different resource attributes; Resource resource1 = Resource.builder().put("cluster", "cluster1").build(); 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 87f5681ca41..afa51af4bac 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 @@ -61,7 +61,9 @@ class PrometheusMetricReaderTest { void setUp() { this.testClock.setTime(Instant.ofEpochMilli((System.currentTimeMillis() / 100) * 100)); this.createdTimestamp = convertTimestamp(testClock.now()); - this.reader = new PrometheusMetricReader(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); + this.reader = + new PrometheusMetricReader( + OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); this.meter = SdkMeterProvider.builder() .setClock(testClock) @@ -776,7 +778,8 @@ void exponentialHistogramBucketConversion() { int otelScale = random.nextInt(24) - 4; int prometheusScale = Math.min(otelScale, 8); PrometheusMetricReader reader = - new PrometheusMetricReader(OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); + new PrometheusMetricReader( + OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .registerMetricReader(reader) @@ -1029,7 +1032,8 @@ void otelScopeComplete() throws IOException { @Test void otelScopeDisabled() throws IOException { PrometheusMetricReader reader = - new PrometheusMetricReader(OtelScopeMode.DISABLED, /* allowedResourceAttributesFilter= */ null); + new PrometheusMetricReader( + OtelScopeMode.DISABLED, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .setClock(testClock) @@ -1060,7 +1064,8 @@ void otelScopeDisabled() throws IOException { void addResourceAttributesWorks() throws IOException { PrometheusMetricReader reader = new PrometheusMetricReader( - OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ Predicates.is("cluster")); + OtelScopeMode.LABELS_AND_SCOPE_INFO, + /* allowedResourceAttributesFilter= */ Predicates.is("cluster")); Meter meter = SdkMeterProvider.builder() .setClock(testClock) From 0fe99ce8737b7a1d9d54e697864f44211a446b06 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 11 Jul 2025 10:23:10 +0200 Subject: [PATCH 03/14] rename --- .../exporter/prometheus/Otel2PrometheusConverter.java | 6 +++--- .../io/opentelemetry/exporter/prometheus/OtelScopeMode.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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 ff4dfaf524d..3b9b4577dae 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 @@ -125,7 +125,7 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeMode.isEnabled() + if (otelScopeMode.isLabelOrInfo() && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } @@ -459,7 +459,7 @@ private InfoSnapshot makeScopeInfo(Set scopes) { * * @param resource optional resource (attributes) to be converted. * @param scope will be converted to {@code otel_scope_*} labels if {@code otelScopeMode} is - * {@link OtelScopeMode#isEnabled()}. + * {@link OtelScopeMode#isLabelOrInfo()}. * @param attributes the attributes to be converted. * @param additionalAttributes optional list of key/value pairs, may be empty. */ @@ -486,7 +486,7 @@ private Labels convertAttributes( requireNonNull(additionalAttributes[i]), additionalAttributes[i + 1]); } - if (otelScopeMode.isEnabled() && scope != null) { + if (otelScopeMode.isLabelOrInfo() && scope != null) { labelNameToValue.putIfAbsent(OTEL_SCOPE_NAME, scope.getName()); if (scope.getVersion() != null) { labelNameToValue.putIfAbsent(OTEL_SCOPE_VERSION, scope.getVersion()); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java index a569bf65801..c4238ca6189 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java @@ -21,7 +21,7 @@ public enum OtelScopeMode { */ LABELS_AND_SCOPE_INFO; - boolean isEnabled() { + boolean isLabelOrInfo() { return this != DISABLED; } From 6a5cad57d1c43496c45bbd87f6c3a1d34aa5d6ca Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Jul 2025 12:28:08 +0200 Subject: [PATCH 04/14] remove disabled option --- .../exporter/prometheus/Otel2PrometheusConverter.java | 8 +++----- .../opentelemetry/exporter/prometheus/OtelScopeMode.java | 6 ------ .../exporter/prometheus/PrometheusHttpServer.java | 6 +++--- .../prometheus/internal/PrometheusComponentProvider.java | 6 ++++-- .../exporter/prometheus/PrometheusHttpServerTest.java | 6 +++--- .../exporter/prometheus/PrometheusMetricReaderTest.java | 8 ++++---- 6 files changed, 17 insertions(+), 23 deletions(-) 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 3b9b4577dae..789d3b70344 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 @@ -125,8 +125,7 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeMode.isLabelOrInfo() - && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { + if (!metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } @@ -458,8 +457,7 @@ private InfoSnapshot makeScopeInfo(Set scopes) { * Convert OpenTelemetry attributes to Prometheus labels. * * @param resource optional resource (attributes) to be converted. - * @param scope will be converted to {@code otel_scope_*} labels if {@code otelScopeMode} is - * {@link OtelScopeMode#isLabelOrInfo()}. + * @param scope will be converted to {@code otel_scope_*} labels. * @param attributes the attributes to be converted. * @param additionalAttributes optional list of key/value pairs, may be empty. */ @@ -486,7 +484,7 @@ private Labels convertAttributes( requireNonNull(additionalAttributes[i]), additionalAttributes[i + 1]); } - if (otelScopeMode.isLabelOrInfo() && scope != null) { + if (scope != null) { labelNameToValue.putIfAbsent(OTEL_SCOPE_NAME, scope.getName()); if (scope.getVersion() != null) { labelNameToValue.putIfAbsent(OTEL_SCOPE_VERSION, scope.getVersion()); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java index c4238ca6189..8c7125b2e23 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java @@ -6,8 +6,6 @@ package io.opentelemetry.exporter.prometheus; public enum OtelScopeMode { - /** No scope information is exported. */ - DISABLED, /** * Only the labels of the metrics are exported, but not the scope_info info metrics. * @@ -21,10 +19,6 @@ public enum OtelScopeMode { */ LABELS_AND_SCOPE_INFO; - boolean isLabelOrInfo() { - return this != DISABLED; - } - boolean isScopeInfoEnabled() { return this == LABELS_AND_SCOPE_INFO; } 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 8a69c234f16..d9de311b5fd 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 @@ -42,7 +42,7 @@ public final class PrometheusHttpServer implements MetricReader { private final String host; private final int port; - private final boolean otelScopeEnabled; + private final OtelScopeMode otelScopeMode; @Nullable private final Predicate allowedResourceAttributesFilter; private final MemoryMode memoryMode; private final DefaultAggregationSelector defaultAggregationSelector; @@ -80,7 +80,7 @@ public static PrometheusHttpServerBuilder builder() { @Nullable Authenticator authenticator) { this.host = host; this.port = port; - this.otelScopeEnabled = otelScopeEnabled; + this.otelScopeMode = otelScopeMode; this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.memoryMode = memoryMode; this.defaultAggregationSelector = defaultAggregationSelector; @@ -171,7 +171,7 @@ public String toString() { StringJoiner joiner = new StringJoiner(",", "PrometheusHttpServer{", "}"); joiner.add("host=" + host); joiner.add("port=" + port); - joiner.add("otelScopeEnabled=" + otelScopeEnabled); + joiner.add("otelScopeMode=" + otelScopeMode); joiner.add("allowedResourceAttributesFilter=" + allowedResourceAttributesFilter); joiner.add("memoryMode=" + memoryMode); joiner.add( diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java index 25d069db7a8..c9e184ac56a 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java @@ -6,12 +6,14 @@ package io.opentelemetry.exporter.prometheus.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.exporter.prometheus.OtelScopeMode; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; import io.opentelemetry.sdk.internal.IncludeExcludePredicate; import io.opentelemetry.sdk.metrics.export.MetricReader; import java.util.List; +import java.util.Objects; /** * Declarative configuration SPI implementation for {@link PrometheusHttpServer}. @@ -46,8 +48,8 @@ public MetricReader create(DeclarativeConfigProperties config) { } Boolean withoutScopeInfo = config.getBoolean("without_scope_info"); - if (withoutScopeInfo != null) { - prometheusBuilder.setOtelScopeEnabled(!withoutScopeInfo); + if (Objects.equals(withoutScopeInfo, true)) { + prometheusBuilder.setOtelScopeMode(OtelScopeMode.LABELS_ONLY); } DeclarativeConfigProperties withResourceConstantLabels = 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 38a86b30a53..245c12c20f0 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 @@ -413,7 +413,7 @@ void stringRepresentation() { "PrometheusHttpServer{" + "host=localhost," + "port=0," - + "otelScopeEnabled=true," + + "otelScopeMode=LABELS_AND_SCOPE_INFO," + "allowedResourceAttributesFilter=null," + "memoryMode=REUSABLE_DATA," + "defaultAggregationSelector=DefaultAggregationSelector{COUNTER=default, UP_DOWN_COUNTER=default, HISTOGRAM=default, OBSERVABLE_COUNTER=default, OBSERVABLE_UP_DOWN_COUNTER=default, OBSERVABLE_GAUGE=default, GAUGE=default}" @@ -528,7 +528,7 @@ void toBuilder() { PrometheusHttpServerBuilder builder = PrometheusHttpServer.builder(); builder.setHost("localhost"); builder.setPort(1234); - builder.setOtelScopeMode(OtelScopeMode.DISABLED); + builder.setOtelScopeMode(OtelScopeMode.LABELS_ONLY); Predicate resourceAttributesFilter = s -> false; builder.setAllowedResourceAttributesFilter(resourceAttributesFilter); @@ -555,7 +555,7 @@ public Result authenticate(HttpExchange exchange) { .isInstanceOf(PrometheusHttpServerBuilder.class) .hasFieldOrPropertyWithValue("host", "localhost") .hasFieldOrPropertyWithValue("port", 1234) - .hasFieldOrPropertyWithValue("otelScopeMode", OtelScopeMode.DISABLED) + .hasFieldOrPropertyWithValue("otelScopeMode", OtelScopeMode.LABELS_ONLY) .hasFieldOrPropertyWithValue("allowedResourceAttributesFilter", resourceAttributesFilter) .hasFieldOrPropertyWithValue("executor", executor) .hasFieldOrPropertyWithValue("prometheusRegistry", prometheusRegistry) 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 afa51af4bac..c5bd054d5c3 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 @@ -1030,10 +1030,10 @@ void otelScopeComplete() throws IOException { } @Test - void otelScopeDisabled() throws IOException { + void otelScopeLabelsOnly() throws IOException { PrometheusMetricReader reader = new PrometheusMetricReader( - OtelScopeMode.DISABLED, /* allowedResourceAttributesFilter= */ null); + OtelScopeMode.LABELS_ONLY, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .setClock(testClock) @@ -1051,8 +1051,8 @@ void otelScopeDisabled() 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" + "# TYPE test_count counter\n" - + "test_count_total 1.0\n" - + "test_count_created " + + "test_count_total{otel_scope_name=\"test-scope\",otel_scope_version=\"a.b.c\"} 1.0\n" + + "test_count_created{otel_scope_name=\"test-scope\",otel_scope_version=\"a.b.c\"} " + createdTimestamp + "\n" + "# EOF\n"; From a0d06088d44a1e6bf9bc1ff1a890041f76316a9b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Jul 2025 12:57:03 +0200 Subject: [PATCH 05/14] remove disabled option --- .../incubator/fileconfig/MetricReaderFactoryTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java index b0e546ea702..244b93d9be5 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java @@ -16,6 +16,7 @@ import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.prometheus.OtelScopeMode; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; @@ -165,7 +166,7 @@ void create_PullPrometheusConfigured() throws IOException { PrometheusHttpServer.builder() .setHost("localhost") .setPort(port) - .setOtelScopeEnabled(false) + .setOtelScopeMode(OtelScopeMode.LABELS_ONLY) .setAllowedResourceAttributesFilter( IncludeExcludePredicate.createPatternMatching( singletonList("foo"), singletonList("bar"))) From 66cdf5b6699a2740d5bdfe682d5fdae5b3866ccd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 6 Aug 2025 13:30:32 +0200 Subject: [PATCH 06/14] Update exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java Co-authored-by: Trask Stalnaker --- .../prometheus/internal/PrometheusComponentProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java index c9e184ac56a..27dbec49b45 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java @@ -48,7 +48,7 @@ public MetricReader create(DeclarativeConfigProperties config) { } Boolean withoutScopeInfo = config.getBoolean("without_scope_info"); - if (Objects.equals(withoutScopeInfo, true)) { + if (Boolean.TRUE.equals(withoutScopeInfo)) { prometheusBuilder.setOtelScopeMode(OtelScopeMode.LABELS_ONLY); } From 8952eb579d940f6634348b4a3cf7614e4f7e7976 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 6 Aug 2025 13:51:14 +0200 Subject: [PATCH 07/14] pr review --- .../exporter/prometheus/PrometheusMetricReader.java | 11 +++++++++++ .../internal/PrometheusComponentProvider.java | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index 64ce2cef0df..b84672bfed8 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -28,6 +28,17 @@ public class PrometheusMetricReader implements MetricReader, MultiCollector { private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); private final Otel2PrometheusConverter converter; + /** + * @deprecated use {@link PrometheusMetricReader#PrometheusMetricReader(OtelScopeMode, Predicate)} + * instead. + */ + @SuppressWarnings("unused") + @Deprecated + public PrometheusMetricReader( + boolean otelScopeEnabled, @Nullable Predicate allowedResourceAttributesFilter) { + this(OtelScopeMode.LABELS_AND_SCOPE_INFO, allowedResourceAttributesFilter); + } + // TODO: refactor to public static create or builder pattern to align with project style /** See {@link Otel2PrometheusConverter#Otel2PrometheusConverter(OtelScopeMode, Predicate)}. */ public PrometheusMetricReader( diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java index 27dbec49b45..9c43ac24014 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java @@ -13,7 +13,6 @@ import io.opentelemetry.sdk.internal.IncludeExcludePredicate; import io.opentelemetry.sdk.metrics.export.MetricReader; import java.util.List; -import java.util.Objects; /** * Declarative configuration SPI implementation for {@link PrometheusHttpServer}. From 30d010eec87adeedb146da5a75756f70651b5e3f Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 6 Aug 2025 14:08:02 +0200 Subject: [PATCH 08/14] pr review --- .../exporter/prometheus/PrometheusMetricReader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index b84672bfed8..1bcbd911953 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -29,6 +29,8 @@ public class PrometheusMetricReader implements MetricReader, MultiCollector { private final Otel2PrometheusConverter converter; /** + * This constructor is deprecated and will be removed in a future release. + * * @deprecated use {@link PrometheusMetricReader#PrometheusMetricReader(OtelScopeMode, Predicate)} * instead. */ From 29f377f5a8da7a917c96fa9fc0764d9f2264138b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 12:26:41 +0200 Subject: [PATCH 09/14] pr review --- .../exporter/prometheus/Otel2PrometheusConverter.java | 2 +- .../io/opentelemetry/exporter/prometheus/OtelScopeMode.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 789d3b70344..23571f3f230 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 @@ -125,7 +125,7 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (!metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { + if (otelScopeMode.isScopeInfoEnabled() && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java index 8c7125b2e23..2129d19248d 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java @@ -12,6 +12,7 @@ public enum OtelScopeMode { *

This will eventually be the default mode, but is opt-in for now. */ LABELS_ONLY, + /** * Both labels and scope_info are exported. * From 15d7fed15f7a7bbfa86361bc52f2ddc06db090d3 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 12:46:31 +0200 Subject: [PATCH 10/14] pr review --- .../exporter/prometheus/Otel2PrometheusConverter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 23571f3f230..44ceb749027 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 @@ -125,7 +125,8 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeMode.isScopeInfoEnabled() && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { + if (otelScopeMode.isScopeInfoEnabled() + && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } From 69cb51798d00ff95df71d965a6319f1cb5c90877 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 20 Aug 2025 10:06:43 +0200 Subject: [PATCH 11/14] use boolean, use without_scope_info_metric in declarative config --- .../prometheus/Otel2PrometheusConverter.java | 15 ++++++----- .../exporter/prometheus/OtelScopeMode.java | 26 ------------------- .../prometheus/PrometheusHttpServer.java | 10 +++---- .../PrometheusHttpServerBuilder.java | 13 +++++----- .../prometheus/PrometheusMetricReader.java | 20 +++----------- .../internal/PrometheusComponentProvider.java | 7 +++-- .../Otel2PrometheusConverterTest.java | 6 ++--- .../prometheus/PrometheusHttpServerTest.java | 6 ++--- .../PrometheusMetricReaderTest.java | 8 +++--- .../fileconfig/MetricReaderFactoryTest.java | 3 +-- 10 files changed, 38 insertions(+), 76 deletions(-) delete mode 100644 exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java 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 44ceb749027..fd538ed2727 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 @@ -82,7 +82,7 @@ final class Otel2PrometheusConverter { private static final long NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1); static final int MAX_CACHE_SIZE = 10; - private final OtelScopeMode otelScopeMode; + private final boolean otelScopeInfoMetricEnabled; @Nullable private final Predicate allowedResourceAttributesFilter; /** @@ -94,14 +94,15 @@ final class Otel2PrometheusConverter { /** * Constructor with feature flag parameter. * - * @param otelScopeMode enable generation of the OpenTelemetry instrumentation scope info metric - * and labels. + * @param otelScopeInfoMetricEnabled enable generation of the OpenTelemetry instrumentation scope + * info metric and labels. * @param allowedResourceAttributesFilter if not {@code null}, resource attributes with keys * matching this predicate will be added as labels on each exported metric */ Otel2PrometheusConverter( - OtelScopeMode otelScopeMode, @Nullable Predicate allowedResourceAttributesFilter) { - this.otelScopeMode = otelScopeMode; + boolean otelScopeInfoMetricEnabled, + @Nullable Predicate allowedResourceAttributesFilter) { + this.otelScopeInfoMetricEnabled = otelScopeInfoMetricEnabled; this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.resourceAttributesToAllowedKeysCache = allowedResourceAttributesFilter != null @@ -125,7 +126,7 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeMode.isScopeInfoEnabled() + if (otelScopeInfoMetricEnabled && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } @@ -133,7 +134,7 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource != null) { putOrMerge(snapshotsByName, makeTargetInfo(resource)); } - if (otelScopeMode.isScopeInfoEnabled() && !scopes.isEmpty()) { + if (otelScopeInfoMetricEnabled && !scopes.isEmpty()) { putOrMerge(snapshotsByName, makeScopeInfo(scopes)); } return new MetricSnapshots(snapshotsByName.values()); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java deleted file mode 100644 index 2129d19248d..00000000000 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/OtelScopeMode.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.prometheus; - -public enum OtelScopeMode { - /** - * Only the labels of the metrics are exported, but not the scope_info info metrics. - * - *

This will eventually be the default mode, but is opt-in for now. - */ - LABELS_ONLY, - - /** - * Both labels and scope_info are exported. - * - *

This is the default mode. - */ - LABELS_AND_SCOPE_INFO; - - boolean isScopeInfoEnabled() { - return this == LABELS_AND_SCOPE_INFO; - } -} 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 d9de311b5fd..ebd50dcca19 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 @@ -42,7 +42,7 @@ public final class PrometheusHttpServer implements MetricReader { private final String host; private final int port; - private final OtelScopeMode otelScopeMode; + private final boolean otelScopeInfoMetricEnabled; @Nullable private final Predicate allowedResourceAttributesFilter; private final MemoryMode memoryMode; private final DefaultAggregationSelector defaultAggregationSelector; @@ -72,7 +72,7 @@ public static PrometheusHttpServerBuilder builder() { int port, @Nullable ExecutorService executor, PrometheusRegistry prometheusRegistry, - OtelScopeMode otelScopeMode, + boolean otelScopeInfoMetricEnabled, @Nullable Predicate allowedResourceAttributesFilter, MemoryMode memoryMode, @Nullable HttpHandler defaultHandler, @@ -80,13 +80,13 @@ public static PrometheusHttpServerBuilder builder() { @Nullable Authenticator authenticator) { this.host = host; this.port = port; - this.otelScopeMode = otelScopeMode; + this.otelScopeInfoMetricEnabled = otelScopeInfoMetricEnabled; this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.memoryMode = memoryMode; this.defaultAggregationSelector = defaultAggregationSelector; this.builder = builder; this.prometheusMetricReader = - new PrometheusMetricReader(otelScopeMode, allowedResourceAttributesFilter); + new PrometheusMetricReader(otelScopeInfoMetricEnabled, allowedResourceAttributesFilter); this.prometheusRegistry = prometheusRegistry; prometheusRegistry.register(prometheusMetricReader); // When memory mode is REUSABLE_DATA, concurrent reads lead to data corruption. To prevent this, @@ -171,7 +171,7 @@ public String toString() { StringJoiner joiner = new StringJoiner(",", "PrometheusHttpServer{", "}"); joiner.add("host=" + host); joiner.add("port=" + port); - joiner.add("otelScopeMode=" + otelScopeMode); + joiner.add("otelScopeInfoMetricEnabled=" + otelScopeInfoMetricEnabled); joiner.add("allowedResourceAttributesFilter=" + allowedResourceAttributesFilter); joiner.add("memoryMode=" + memoryMode); joiner.add( 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 1f1f0cbe08d..fd54775c2a8 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 @@ -30,7 +30,7 @@ public final class PrometheusHttpServerBuilder { private String host = DEFAULT_HOST; private int port = DEFAULT_PORT; private PrometheusRegistry prometheusRegistry = new PrometheusRegistry(); - private OtelScopeMode otelScopeMode = OtelScopeMode.LABELS_AND_SCOPE_INFO; + private boolean otelScopeInfoMetricEnabled = true; @Nullable private Predicate allowedResourceAttributesFilter; @Nullable private ExecutorService executor; private MemoryMode memoryMode = DEFAULT_MEMORY_MODE; @@ -45,7 +45,7 @@ public final class PrometheusHttpServerBuilder { this.host = builder.host; this.port = builder.port; this.prometheusRegistry = builder.prometheusRegistry; - this.otelScopeMode = builder.otelScopeMode; + this.otelScopeInfoMetricEnabled = builder.otelScopeInfoMetricEnabled; this.allowedResourceAttributesFilter = builder.allowedResourceAttributesFilter; this.executor = builder.executor; this.memoryMode = builder.memoryMode; @@ -83,10 +83,11 @@ public PrometheusHttpServerBuilder setPrometheusRegistry(PrometheusRegistry prom return this; } - /** Set if the {@code otel_scope_*} attributes are generated. Default is {@code true}. */ + /** Set if the {@code otel_scope_info} metric is emitted. Default is {@code true}. */ @SuppressWarnings("UnusedReturnValue") - public PrometheusHttpServerBuilder setOtelScopeMode(OtelScopeMode otelScopeMode) { - this.otelScopeMode = otelScopeMode; + public PrometheusHttpServerBuilder setOtelScopeInfoMetricEnabled( + boolean otelScopeInfoMetricEnabled) { + this.otelScopeInfoMetricEnabled = otelScopeInfoMetricEnabled; return this; } @@ -176,7 +177,7 @@ public PrometheusHttpServer build() { port, executor, prometheusRegistry, - otelScopeMode, + otelScopeInfoMetricEnabled, allowedResourceAttributesFilter, memoryMode, defaultHandler, diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index 1bcbd911953..7a2bcae47de 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -28,24 +28,12 @@ public class PrometheusMetricReader implements MetricReader, MultiCollector { private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); private final Otel2PrometheusConverter converter; - /** - * This constructor is deprecated and will be removed in a future release. - * - * @deprecated use {@link PrometheusMetricReader#PrometheusMetricReader(OtelScopeMode, Predicate)} - * instead. - */ - @SuppressWarnings("unused") - @Deprecated - public PrometheusMetricReader( - boolean otelScopeEnabled, @Nullable Predicate allowedResourceAttributesFilter) { - this(OtelScopeMode.LABELS_AND_SCOPE_INFO, allowedResourceAttributesFilter); - } - // TODO: refactor to public static create or builder pattern to align with project style - /** See {@link Otel2PrometheusConverter#Otel2PrometheusConverter(OtelScopeMode, Predicate)}. */ public PrometheusMetricReader( - OtelScopeMode otelScopeMode, @Nullable Predicate allowedResourceAttributesFilter) { - this.converter = new Otel2PrometheusConverter(otelScopeMode, allowedResourceAttributesFilter); + boolean otelScopeInfoMetricEnabled, + @Nullable Predicate allowedResourceAttributesFilter) { + this.converter = + new Otel2PrometheusConverter(otelScopeInfoMetricEnabled, allowedResourceAttributesFilter); } @Override diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java index 9c43ac24014..604361203f3 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java @@ -6,7 +6,6 @@ package io.opentelemetry.exporter.prometheus.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.exporter.prometheus.OtelScopeMode; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @@ -46,9 +45,9 @@ public MetricReader create(DeclarativeConfigProperties config) { prometheusBuilder.setHost(host); } - Boolean withoutScopeInfo = config.getBoolean("without_scope_info"); - if (Boolean.TRUE.equals(withoutScopeInfo)) { - prometheusBuilder.setOtelScopeMode(OtelScopeMode.LABELS_ONLY); + Boolean withoutScopeInfo = config.getBoolean("without_scope_info_metric"); + if (withoutScopeInfo != null) { + prometheusBuilder.setOtelScopeInfoMetricEnabled(!withoutScopeInfo); } DeclarativeConfigProperties withResourceConstantLabels = diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java index e4680ed4dff..8326d3fabc8 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java @@ -72,7 +72,7 @@ class Otel2PrometheusConverterTest { private final Otel2PrometheusConverter converter = new Otel2PrometheusConverter( - OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); + /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ null); @ParameterizedTest @MethodSource("metricMetadataArgs") @@ -203,7 +203,7 @@ void resourceAttributesAddition( Otel2PrometheusConverter converter = new Otel2PrometheusConverter( - OtelScopeMode.LABELS_AND_SCOPE_INFO, allowedResourceAttributesFilter); + /* otelScopeInfoMetricEnabled */ true, allowedResourceAttributesFilter); ByteArrayOutputStream out = new ByteArrayOutputStream(); MetricSnapshots snapshots = converter.convert(Collections.singletonList(metricData)); @@ -504,7 +504,7 @@ void validateCacheIsBounded() { Otel2PrometheusConverter otel2PrometheusConverter = new Otel2PrometheusConverter( - OtelScopeMode.LABELS_AND_SCOPE_INFO, + /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ countPredicate); // Create 20 different metric data objects with 2 different resource attributes; 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 245c12c20f0..72888ea5821 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 @@ -413,7 +413,7 @@ void stringRepresentation() { "PrometheusHttpServer{" + "host=localhost," + "port=0," - + "otelScopeMode=LABELS_AND_SCOPE_INFO," + + "otelScopeInfoMetricEnabled=true," + "allowedResourceAttributesFilter=null," + "memoryMode=REUSABLE_DATA," + "defaultAggregationSelector=DefaultAggregationSelector{COUNTER=default, UP_DOWN_COUNTER=default, HISTOGRAM=default, OBSERVABLE_COUNTER=default, OBSERVABLE_UP_DOWN_COUNTER=default, OBSERVABLE_GAUGE=default, GAUGE=default}" @@ -528,7 +528,7 @@ void toBuilder() { PrometheusHttpServerBuilder builder = PrometheusHttpServer.builder(); builder.setHost("localhost"); builder.setPort(1234); - builder.setOtelScopeMode(OtelScopeMode.LABELS_ONLY); + builder.setOtelScopeInfoMetricEnabled(false); Predicate resourceAttributesFilter = s -> false; builder.setAllowedResourceAttributesFilter(resourceAttributesFilter); @@ -555,7 +555,7 @@ public Result authenticate(HttpExchange exchange) { .isInstanceOf(PrometheusHttpServerBuilder.class) .hasFieldOrPropertyWithValue("host", "localhost") .hasFieldOrPropertyWithValue("port", 1234) - .hasFieldOrPropertyWithValue("otelScopeMode", OtelScopeMode.LABELS_ONLY) + .hasFieldOrPropertyWithValue("otelScopeInfoMetricEnabled", false) .hasFieldOrPropertyWithValue("allowedResourceAttributesFilter", resourceAttributesFilter) .hasFieldOrPropertyWithValue("executor", executor) .hasFieldOrPropertyWithValue("prometheusRegistry", prometheusRegistry) 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 c5bd054d5c3..4ed42415541 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 @@ -63,7 +63,7 @@ void setUp() { this.createdTimestamp = convertTimestamp(testClock.now()); this.reader = new PrometheusMetricReader( - OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); + /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ null); this.meter = SdkMeterProvider.builder() .setClock(testClock) @@ -779,7 +779,7 @@ void exponentialHistogramBucketConversion() { int prometheusScale = Math.min(otelScale, 8); PrometheusMetricReader reader = new PrometheusMetricReader( - OtelScopeMode.LABELS_AND_SCOPE_INFO, /* allowedResourceAttributesFilter= */ null); + /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .registerMetricReader(reader) @@ -1033,7 +1033,7 @@ void otelScopeComplete() throws IOException { void otelScopeLabelsOnly() throws IOException { PrometheusMetricReader reader = new PrometheusMetricReader( - OtelScopeMode.LABELS_ONLY, /* allowedResourceAttributesFilter= */ null); + /* otelScopeInfoMetricEnabled */ false, /* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .setClock(testClock) @@ -1064,7 +1064,7 @@ void otelScopeLabelsOnly() throws IOException { void addResourceAttributesWorks() throws IOException { PrometheusMetricReader reader = new PrometheusMetricReader( - OtelScopeMode.LABELS_AND_SCOPE_INFO, + /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ Predicates.is("cluster")); Meter meter = SdkMeterProvider.builder() diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java index 244b93d9be5..648f64fd361 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java @@ -16,7 +16,6 @@ import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; -import io.opentelemetry.exporter.prometheus.OtelScopeMode; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; @@ -166,7 +165,7 @@ void create_PullPrometheusConfigured() throws IOException { PrometheusHttpServer.builder() .setHost("localhost") .setPort(port) - .setOtelScopeMode(OtelScopeMode.LABELS_ONLY) + .setOtelScopeInfoMetricEnabled(false) .setAllowedResourceAttributesFilter( IncludeExcludePredicate.createPatternMatching( singletonList("foo"), singletonList("bar"))) From edc2e56492d6cb2419569253dde1a2c70f04c71b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 2 Sep 2025 15:43:44 +0200 Subject: [PATCH 12/14] fix --- .../prometheus/PrometheusHttpServerBuilder.java | 13 +++++++++++++ .../fileconfig/MetricReaderFactoryTest.java | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) 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 fd54775c2a8..75704d7afb7 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 @@ -83,6 +83,19 @@ public PrometheusHttpServerBuilder setPrometheusRegistry(PrometheusRegistry prom return this; } + /** + * Set if the {@code otel_scope_*} attributes are generated. Default is {@code true}. + * + * @deprecated {@code otel_scope_*} attributes are always generated. Use {@link + * #setOtelScopeInfoMetricEnabled(boolean)} to control emission of the {@code otel_scope_info} + * metric. + */ + @SuppressWarnings("UnusedReturnValue") + @Deprecated + public PrometheusHttpServerBuilder setOtelScopeEnabled(boolean otelScopeEnabled) { + return this; + } + /** Set if the {@code otel_scope_info} metric is emitted. Default is {@code true}. */ @SuppressWarnings("UnusedReturnValue") public PrometheusHttpServerBuilder setOtelScopeInfoMetricEnabled( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java index 648f64fd361..68a23b11b5a 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java @@ -165,7 +165,7 @@ void create_PullPrometheusConfigured() throws IOException { PrometheusHttpServer.builder() .setHost("localhost") .setPort(port) - .setOtelScopeInfoMetricEnabled(false) + .setOtelScopeInfoMetricEnabled(true) // always enabled for declarative config .setAllowedResourceAttributesFilter( IncludeExcludePredicate.createPatternMatching( singletonList("foo"), singletonList("bar"))) From 50b9b3c999fe2d08e64644b479c2c57b332fa2a3 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 4 Sep 2025 14:33:28 +0200 Subject: [PATCH 13/14] remove option to keep scope info metric --- .../prometheus/Otel2PrometheusConverter.java | 31 +------------------ .../prometheus/PrometheusHttpServer.java | 7 +---- .../PrometheusHttpServerBuilder.java | 15 +-------- .../prometheus/PrometheusMetricReader.java | 19 +++++++++--- .../internal/PrometheusComponentProvider.java | 5 --- .../Otel2PrometheusConverterTest.java | 10 ++---- .../prometheus/PrometheusHttpServerTest.java | 3 -- .../PrometheusMetricReaderTest.java | 14 +++------ .../fileconfig/MetricReaderFactoryTest.java | 1 - 9 files changed, 24 insertions(+), 81 deletions(-) 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 fd538ed2727..1e952b36a09 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 @@ -56,11 +56,9 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -82,7 +80,6 @@ final class Otel2PrometheusConverter { private static final long NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1); static final int MAX_CACHE_SIZE = 10; - private final boolean otelScopeInfoMetricEnabled; @Nullable private final Predicate allowedResourceAttributesFilter; /** @@ -94,15 +91,10 @@ final class Otel2PrometheusConverter { /** * Constructor with feature flag parameter. * - * @param otelScopeInfoMetricEnabled enable generation of the OpenTelemetry instrumentation scope - * info metric and labels. * @param allowedResourceAttributesFilter if not {@code null}, resource attributes with keys * matching this predicate will be added as labels on each exported metric */ - Otel2PrometheusConverter( - boolean otelScopeInfoMetricEnabled, - @Nullable Predicate allowedResourceAttributesFilter) { - this.otelScopeInfoMetricEnabled = otelScopeInfoMetricEnabled; + Otel2PrometheusConverter(@Nullable Predicate allowedResourceAttributesFilter) { this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.resourceAttributesToAllowedKeysCache = allowedResourceAttributesFilter != null @@ -116,7 +108,6 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { } Map snapshotsByName = new HashMap<>(metricDataCollection.size()); Resource resource = null; - Set scopes = new LinkedHashSet<>(); for (MetricData metricData : metricDataCollection) { MetricSnapshot snapshot = convert(metricData); if (snapshot == null) { @@ -126,17 +117,10 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeInfoMetricEnabled - && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { - scopes.add(metricData.getInstrumentationScopeInfo()); - } } if (resource != null) { putOrMerge(snapshotsByName, makeTargetInfo(resource)); } - if (otelScopeInfoMetricEnabled && !scopes.isEmpty()) { - putOrMerge(snapshotsByName, makeScopeInfo(scopes)); - } return new MetricSnapshots(snapshotsByName.values()); } @@ -442,19 +426,6 @@ private InfoSnapshot makeTargetInfo(Resource resource) { resource.getAttributes())))); } - private InfoSnapshot makeScopeInfo(Set scopes) { - List prometheusScopeInfos = new ArrayList<>(scopes.size()); - for (InstrumentationScopeInfo scope : scopes) { - prometheusScopeInfos.add( - new InfoDataPointSnapshot( - convertAttributes( - null, // resource attributes are only copied for point's attributes - scope, - scope.getAttributes()))); - } - return new InfoSnapshot(new MetricMetadata("otel_scope"), prometheusScopeInfos); - } - /** * Convert OpenTelemetry attributes to Prometheus labels. * 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 ebd50dcca19..caecae015d3 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 @@ -42,7 +42,6 @@ public final class PrometheusHttpServer implements MetricReader { private final String host; private final int port; - private final boolean otelScopeInfoMetricEnabled; @Nullable private final Predicate allowedResourceAttributesFilter; private final MemoryMode memoryMode; private final DefaultAggregationSelector defaultAggregationSelector; @@ -72,7 +71,6 @@ public static PrometheusHttpServerBuilder builder() { int port, @Nullable ExecutorService executor, PrometheusRegistry prometheusRegistry, - boolean otelScopeInfoMetricEnabled, @Nullable Predicate allowedResourceAttributesFilter, MemoryMode memoryMode, @Nullable HttpHandler defaultHandler, @@ -80,13 +78,11 @@ public static PrometheusHttpServerBuilder builder() { @Nullable Authenticator authenticator) { this.host = host; this.port = port; - this.otelScopeInfoMetricEnabled = otelScopeInfoMetricEnabled; this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.memoryMode = memoryMode; this.defaultAggregationSelector = defaultAggregationSelector; this.builder = builder; - this.prometheusMetricReader = - new PrometheusMetricReader(otelScopeInfoMetricEnabled, allowedResourceAttributesFilter); + this.prometheusMetricReader = new PrometheusMetricReader(allowedResourceAttributesFilter); this.prometheusRegistry = prometheusRegistry; prometheusRegistry.register(prometheusMetricReader); // When memory mode is REUSABLE_DATA, concurrent reads lead to data corruption. To prevent this, @@ -171,7 +167,6 @@ public String toString() { StringJoiner joiner = new StringJoiner(",", "PrometheusHttpServer{", "}"); joiner.add("host=" + host); joiner.add("port=" + port); - joiner.add("otelScopeInfoMetricEnabled=" + otelScopeInfoMetricEnabled); joiner.add("allowedResourceAttributesFilter=" + allowedResourceAttributesFilter); joiner.add("memoryMode=" + memoryMode); joiner.add( 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 75704d7afb7..defe4091c73 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 @@ -30,7 +30,6 @@ public final class PrometheusHttpServerBuilder { private String host = DEFAULT_HOST; private int port = DEFAULT_PORT; private PrometheusRegistry prometheusRegistry = new PrometheusRegistry(); - private boolean otelScopeInfoMetricEnabled = true; @Nullable private Predicate allowedResourceAttributesFilter; @Nullable private ExecutorService executor; private MemoryMode memoryMode = DEFAULT_MEMORY_MODE; @@ -45,7 +44,6 @@ public final class PrometheusHttpServerBuilder { this.host = builder.host; this.port = builder.port; this.prometheusRegistry = builder.prometheusRegistry; - this.otelScopeInfoMetricEnabled = builder.otelScopeInfoMetricEnabled; this.allowedResourceAttributesFilter = builder.allowedResourceAttributesFilter; this.executor = builder.executor; this.memoryMode = builder.memoryMode; @@ -86,9 +84,7 @@ public PrometheusHttpServerBuilder setPrometheusRegistry(PrometheusRegistry prom /** * Set if the {@code otel_scope_*} attributes are generated. Default is {@code true}. * - * @deprecated {@code otel_scope_*} attributes are always generated. Use {@link - * #setOtelScopeInfoMetricEnabled(boolean)} to control emission of the {@code otel_scope_info} - * metric. + * @deprecated {@code otel_scope_*} attributes are always generated. */ @SuppressWarnings("UnusedReturnValue") @Deprecated @@ -96,14 +92,6 @@ public PrometheusHttpServerBuilder setOtelScopeEnabled(boolean otelScopeEnabled) return this; } - /** Set if the {@code otel_scope_info} metric is emitted. Default is {@code true}. */ - @SuppressWarnings("UnusedReturnValue") - public PrometheusHttpServerBuilder setOtelScopeInfoMetricEnabled( - boolean otelScopeInfoMetricEnabled) { - this.otelScopeInfoMetricEnabled = otelScopeInfoMetricEnabled; - return this; - } - /** * Set if the resource attributes should be added as labels on each exported metric. * @@ -190,7 +178,6 @@ public PrometheusHttpServer build() { port, executor, prometheusRegistry, - otelScopeInfoMetricEnabled, allowedResourceAttributesFilter, memoryMode, defaultHandler, diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index 7a2bcae47de..ad607390023 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -28,12 +28,21 @@ public class PrometheusMetricReader implements MetricReader, MultiCollector { private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); private final Otel2PrometheusConverter converter; - // TODO: refactor to public static create or builder pattern to align with project style + /** + * Deprecated. Use {@link #PrometheusMetricReader(Predicate)}. + * + * @deprecated use {@link #PrometheusMetricReader(Predicate)}. + */ + @Deprecated + @SuppressWarnings({"unused", "InconsistentOverloads"}) public PrometheusMetricReader( - boolean otelScopeInfoMetricEnabled, - @Nullable Predicate allowedResourceAttributesFilter) { - this.converter = - new Otel2PrometheusConverter(otelScopeInfoMetricEnabled, allowedResourceAttributesFilter); + boolean otelScopeEnabled, @Nullable Predicate allowedResourceAttributesFilter) { + this.converter = new Otel2PrometheusConverter(allowedResourceAttributesFilter); + } + + // TODO: refactor to public static create or builder pattern to align with project style + public PrometheusMetricReader(@Nullable Predicate allowedResourceAttributesFilter) { + this.converter = new Otel2PrometheusConverter(allowedResourceAttributesFilter); } @Override diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java index 604361203f3..535030f6cf3 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java @@ -45,11 +45,6 @@ public MetricReader create(DeclarativeConfigProperties config) { prometheusBuilder.setHost(host); } - Boolean withoutScopeInfo = config.getBoolean("without_scope_info_metric"); - if (withoutScopeInfo != null) { - prometheusBuilder.setOtelScopeInfoMetricEnabled(!withoutScopeInfo); - } - DeclarativeConfigProperties withResourceConstantLabels = config.getStructured("with_resource_constant_labels"); if (withResourceConstantLabels != null) { diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java index 8326d3fabc8..4660d8d1ee8 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java @@ -71,8 +71,7 @@ class Otel2PrometheusConverterTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final Otel2PrometheusConverter converter = - new Otel2PrometheusConverter( - /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ null); + new Otel2PrometheusConverter(/* allowedResourceAttributesFilter= */ null); @ParameterizedTest @MethodSource("metricMetadataArgs") @@ -202,8 +201,7 @@ void resourceAttributesAddition( throws IOException { Otel2PrometheusConverter converter = - new Otel2PrometheusConverter( - /* otelScopeInfoMetricEnabled */ true, allowedResourceAttributesFilter); + new Otel2PrometheusConverter(allowedResourceAttributesFilter); ByteArrayOutputStream out = new ByteArrayOutputStream(); MetricSnapshots snapshots = converter.convert(Collections.singletonList(metricData)); @@ -503,9 +501,7 @@ void validateCacheIsBounded() { }; Otel2PrometheusConverter otel2PrometheusConverter = - new Otel2PrometheusConverter( - /* otelScopeInfoMetricEnabled */ true, - /* allowedResourceAttributesFilter= */ countPredicate); + new Otel2PrometheusConverter(/* allowedResourceAttributesFilter= */ countPredicate); // Create 20 different metric data objects with 2 different resource attributes; Resource resource1 = Resource.builder().put("cluster", "cluster1").build(); 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 72888ea5821..1ffd4916598 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 @@ -413,7 +413,6 @@ void stringRepresentation() { "PrometheusHttpServer{" + "host=localhost," + "port=0," - + "otelScopeInfoMetricEnabled=true," + "allowedResourceAttributesFilter=null," + "memoryMode=REUSABLE_DATA," + "defaultAggregationSelector=DefaultAggregationSelector{COUNTER=default, UP_DOWN_COUNTER=default, HISTOGRAM=default, OBSERVABLE_COUNTER=default, OBSERVABLE_UP_DOWN_COUNTER=default, OBSERVABLE_GAUGE=default, GAUGE=default}" @@ -528,7 +527,6 @@ void toBuilder() { PrometheusHttpServerBuilder builder = PrometheusHttpServer.builder(); builder.setHost("localhost"); builder.setPort(1234); - builder.setOtelScopeInfoMetricEnabled(false); Predicate resourceAttributesFilter = s -> false; builder.setAllowedResourceAttributesFilter(resourceAttributesFilter); @@ -555,7 +553,6 @@ public Result authenticate(HttpExchange exchange) { .isInstanceOf(PrometheusHttpServerBuilder.class) .hasFieldOrPropertyWithValue("host", "localhost") .hasFieldOrPropertyWithValue("port", 1234) - .hasFieldOrPropertyWithValue("otelScopeInfoMetricEnabled", false) .hasFieldOrPropertyWithValue("allowedResourceAttributesFilter", resourceAttributesFilter) .hasFieldOrPropertyWithValue("executor", executor) .hasFieldOrPropertyWithValue("prometheusRegistry", prometheusRegistry) 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 4ed42415541..98e2bc964c7 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 @@ -61,9 +61,7 @@ class PrometheusMetricReaderTest { void setUp() { this.testClock.setTime(Instant.ofEpochMilli((System.currentTimeMillis() / 100) * 100)); this.createdTimestamp = convertTimestamp(testClock.now()); - this.reader = - new PrometheusMetricReader( - /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ null); + this.reader = new PrometheusMetricReader(/* allowedResourceAttributesFilter= */ null); this.meter = SdkMeterProvider.builder() .setClock(testClock) @@ -778,8 +776,7 @@ void exponentialHistogramBucketConversion() { int otelScale = random.nextInt(24) - 4; int prometheusScale = Math.min(otelScale, 8); PrometheusMetricReader reader = - new PrometheusMetricReader( - /* otelScopeInfoMetricEnabled */ true, /* allowedResourceAttributesFilter= */ null); + new PrometheusMetricReader(/* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .registerMetricReader(reader) @@ -1032,8 +1029,7 @@ void otelScopeComplete() throws IOException { @Test void otelScopeLabelsOnly() throws IOException { PrometheusMetricReader reader = - new PrometheusMetricReader( - /* otelScopeInfoMetricEnabled */ false, /* allowedResourceAttributesFilter= */ null); + new PrometheusMetricReader(/* allowedResourceAttributesFilter= */ null); Meter meter = SdkMeterProvider.builder() .setClock(testClock) @@ -1063,9 +1059,7 @@ void otelScopeLabelsOnly() throws IOException { @Test void addResourceAttributesWorks() throws IOException { PrometheusMetricReader reader = - new PrometheusMetricReader( - /* otelScopeInfoMetricEnabled */ true, - /* allowedResourceAttributesFilter= */ Predicates.is("cluster")); + new PrometheusMetricReader(/* allowedResourceAttributesFilter= */ Predicates.is("cluster")); Meter meter = SdkMeterProvider.builder() .setClock(testClock) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java index 68a23b11b5a..1e807412e55 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricReaderFactoryTest.java @@ -165,7 +165,6 @@ void create_PullPrometheusConfigured() throws IOException { PrometheusHttpServer.builder() .setHost("localhost") .setPort(port) - .setOtelScopeInfoMetricEnabled(true) // always enabled for declarative config .setAllowedResourceAttributesFilter( IncludeExcludePredicate.createPatternMatching( singletonList("foo"), singletonList("bar"))) From 88737d845b1cd8bdf6c00b1dc0de45539213e5cb Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 4 Sep 2025 14:51:24 +0200 Subject: [PATCH 14/14] remove option to keep scope info metric --- .../exporter/prometheus/PrometheusMetricReaderTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 98e2bc964c7..12bfad8d7bc 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 @@ -1090,6 +1090,14 @@ void addResourceAttributesWorks() throws IOException { assertThat(toOpenMetrics(reader.collect())).isEqualTo(expected); } + @SuppressWarnings("deprecation") // test deprecated constructor + @Test + void deprecatedConstructor() { + assertThat(new PrometheusMetricReader(false, null)) + .usingRecursiveComparison() + .isEqualTo(new PrometheusMetricReader(null)); + } + /** * 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.