Skip to content

Commit cdf4dcf

Browse files
authored
address todos (#1832)
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent c50c800 commit cdf4dcf

File tree

6 files changed

+205
-10
lines changed

6 files changed

+205
-10
lines changed

prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,33 @@
44
import java.util.Map;
55
import javax.annotation.Nullable;
66

7-
// TODO: JavaDoc is currently only in OpenTelemetryExporter.Builder. Look there for reference.
7+
/**
8+
* Properties for configuring the OpenTelemetry exporter.
9+
*
10+
* <p>These properties can be configured via {@code prometheus.properties}, system properties, or
11+
* programmatically.
12+
*
13+
* <p>All properties are prefixed with {@code io.prometheus.exporter.opentelemetry}.
14+
*
15+
* <p>Available properties:
16+
*
17+
* <ul>
18+
* <li>{@code protocol} - OTLP protocol: {@code "grpc"} or {@code "http/protobuf"}
19+
* <li>{@code endpoint} - OTLP endpoint URL
20+
* <li>{@code headers} - HTTP headers for outgoing requests
21+
* <li>{@code intervalSeconds} - Export interval in seconds
22+
* <li>{@code timeoutSeconds} - Request timeout in seconds
23+
* <li>{@code serviceName} - Service name resource attribute
24+
* <li>{@code serviceNamespace} - Service namespace resource attribute
25+
* <li>{@code serviceInstanceId} - Service instance ID resource attribute
26+
* <li>{@code serviceVersion} - Service version resource attribute
27+
* <li>{@code resourceAttributes} - Additional resource attributes
28+
* </ul>
29+
*
30+
* @see <a
31+
* href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/">OpenTelemetry
32+
* SDK Environment Variables</a>
33+
*/
834
public class ExporterOpenTelemetryProperties {
935

1036
// See
@@ -153,6 +179,14 @@ public static class Builder {
153179

154180
private Builder() {}
155181

182+
/**
183+
* The OTLP protocol to use.
184+
*
185+
* <p>Supported values: {@code "grpc"} or {@code "http/protobuf"}.
186+
*
187+
* <p>See OpenTelemetry's <a
188+
* href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_protocol">OTEL_EXPORTER_OTLP_PROTOCOL</a>.
189+
*/
156190
public Builder protocol(String protocol) {
157191
if (!protocol.equals("grpc") && !protocol.equals("http/protobuf")) {
158192
throw new IllegalArgumentException(
@@ -162,17 +196,43 @@ public Builder protocol(String protocol) {
162196
return this;
163197
}
164198

199+
/**
200+
* The OTLP endpoint to send metric data to.
201+
*
202+
* <p>The default depends on the protocol:
203+
*
204+
* <ul>
205+
* <li>{@code "grpc"}: {@code "http://localhost:4317"}
206+
* <li>{@code "http/protobuf"}: {@code "http://localhost:4318/v1/metrics"}
207+
* </ul>
208+
*
209+
* <p>See OpenTelemetry's <a
210+
* href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_endpoint">OTEL_EXPORTER_OTLP_METRICS_ENDPOINT</a>.
211+
*/
165212
public Builder endpoint(String endpoint) {
166213
this.endpoint = endpoint;
167214
return this;
168215
}
169216

170-
/** Add a request header. Call multiple times to add multiple headers. */
217+
/**
218+
* Add an HTTP header to be applied to outgoing requests. Call multiple times to add multiple
219+
* headers.
220+
*
221+
* <p>See OpenTelemetry's <a
222+
* href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_headers">OTEL_EXPORTER_OTLP_HEADERS</a>.
223+
*/
171224
public Builder header(String name, String value) {
172225
this.headers.put(name, value);
173226
return this;
174227
}
175228

229+
/**
230+
* The interval between the start of two export attempts. Default is 60 seconds.
231+
*
232+
* <p>Like OpenTelemetry's <a
233+
* href="https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#periodic-metric-reader">OTEL_METRIC_EXPORT_INTERVAL</a>
234+
* (which defaults to 60000 milliseconds), but specified in seconds rather than milliseconds.
235+
*/
176236
public Builder intervalSeconds(int intervalSeconds) {
177237
if (intervalSeconds <= 0) {
178238
throw new IllegalArgumentException(intervalSeconds + ": Expecting intervalSeconds > 0");
@@ -181,6 +241,13 @@ public Builder intervalSeconds(int intervalSeconds) {
181241
return this;
182242
}
183243

244+
/**
245+
* The timeout for outgoing requests. Default is 10.
246+
*
247+
* <p>Like OpenTelemetry's <a
248+
* href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_timeout">OTEL_EXPORTER_OTLP_METRICS_TIMEOUT</a>,
249+
* but in seconds rather than milliseconds.
250+
*/
184251
public Builder timeoutSeconds(int timeoutSeconds) {
185252
if (timeoutSeconds <= 0) {
186253
throw new IllegalArgumentException(timeoutSeconds + ": Expecting timeoutSeconds > 0");
@@ -189,26 +256,63 @@ public Builder timeoutSeconds(int timeoutSeconds) {
189256
return this;
190257
}
191258

259+
/**
260+
* The {@code service.name} resource attribute.
261+
*
262+
* <p>If not explicitly specified, {@code client_java} will try to initialize it with a
263+
* reasonable default, like the JAR file name.
264+
*
265+
* <p>See {@code service.name} in OpenTelemetry's <a
266+
* href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service">Resource
267+
* Semantic Conventions</a>.
268+
*/
192269
public Builder serviceName(String serviceName) {
193270
this.serviceName = serviceName;
194271
return this;
195272
}
196273

274+
/**
275+
* The {@code service.namespace} resource attribute.
276+
*
277+
* <p>See {@code service.namespace} in OpenTelemetry's <a
278+
* href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
279+
* Semantic Conventions</a>.
280+
*/
197281
public Builder serviceNamespace(String serviceNamespace) {
198282
this.serviceNamespace = serviceNamespace;
199283
return this;
200284
}
201285

286+
/**
287+
* The {@code service.instance.id} resource attribute.
288+
*
289+
* <p>See {@code service.instance.id} in OpenTelemetry's <a
290+
* href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
291+
* Semantic Conventions</a>.
292+
*/
202293
public Builder serviceInstanceId(String serviceInstanceId) {
203294
this.serviceInstanceId = serviceInstanceId;
204295
return this;
205296
}
206297

298+
/**
299+
* The {@code service.version} resource attribute.
300+
*
301+
* <p>See {@code service.version} in OpenTelemetry's <a
302+
* href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#service-experimental">Resource
303+
* Semantic Conventions</a>.
304+
*/
207305
public Builder serviceVersion(String serviceVersion) {
208306
this.serviceVersion = serviceVersion;
209307
return this;
210308
}
211309

310+
/**
311+
* Add a resource attribute. Call multiple times to add multiple resource attributes.
312+
*
313+
* <p>See OpenTelemetry's <a
314+
* href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration">OTEL_RESOURCE_ATTRIBUTES</a>.
315+
*/
212316
public Builder resourceAttribute(String name, String value) {
213317
this.resourceAttributes.put(name, value);
214318
return this;

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -962,11 +962,18 @@ public Builder nativeMaxNumberOfBuckets(int nativeMaxBuckets) {
962962
* <p>Default is no reset.
963963
*/
964964
public Builder nativeResetDuration(long duration, TimeUnit unit) {
965-
// TODO: reset interval isn't tested yet
966965
if (duration <= 0) {
967966
throw new IllegalArgumentException(duration + ": value > 0 expected");
968967
}
969-
nativeResetDurationSeconds = unit.toSeconds(duration);
968+
long seconds = unit.toSeconds(duration);
969+
if (seconds == 0) {
970+
throw new IllegalArgumentException(
971+
duration
972+
+ " "
973+
+ unit
974+
+ ": duration must be at least 1 second. Sub-second durations are not supported.");
975+
}
976+
nativeResetDurationSeconds = seconds;
970977
return this;
971978
}
972979

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SlidingWindow.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@
1515
* <p>It is implemented in a generic way so that 3rd party libraries can use it for implementing
1616
* sliding windows.
1717
*
18-
* <p>TODO: The current implementation is {@code synchronized}. There is likely room for
19-
* optimization.
18+
* <p><b>Thread Safety:</b> This class uses coarse-grained {@code synchronized} methods for
19+
* simplicity and correctness. All public methods ({@link #current()} and {@link #observe(double)})
20+
* are synchronized, which ensures thread-safe access to the ring buffer and rotation logic.
21+
*
22+
* <p><b>Performance Note:</b> The synchronized approach may cause contention under high-frequency
23+
* observations.
24+
*
25+
* <p>However, given that Summary metrics are less commonly used (Histogram is generally preferred),
26+
* and the observation frequency is typically lower than Counter increments, the current
27+
* implementation provides an acceptable trade-off between simplicity and performance.
2028
*/
2129
public class SlidingWindow<T> {
2230

prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,13 @@ private void doObserve(double amount) {
202202
private SummarySnapshot.SummaryDataPointSnapshot collect(Labels labels) {
203203
return buffer.run(
204204
expectedCount -> count.sum() == expectedCount,
205-
// TODO Exemplars (are hard-coded as empty in the line below)
205+
// Note: Exemplars are currently hard-coded as empty for Summary metrics.
206+
// While exemplars are sampled during observe() and observeWithExemplar() calls
207+
// via the exemplarSampler field, they are not included in the snapshot to maintain
208+
// consistency with the buffering mechanism. The buffer.run() ensures atomic
209+
// collection of count, sum, and quantiles. Adding exemplars would require
210+
// coordination between the buffer and exemplarSampler, which could impact
211+
// performance. Consider using Histogram instead if exemplars are needed.
206212
() ->
207213
new SummarySnapshot.SummaryDataPointSnapshot(
208214
count.sum(),

prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,7 @@ void testObserve() {
14231423

14241424
@Test
14251425
// See https://github.com/prometheus/client_java/issues/646
1426-
public void testNegativeAmount() {
1426+
void testNegativeAmount() {
14271427
Histogram histogram =
14281428
Histogram.builder()
14291429
.name("histogram")
@@ -1565,6 +1565,70 @@ void testObserveMultithreaded()
15651565
assertThat(executor.awaitTermination(5, TimeUnit.SECONDS)).isTrue();
15661566
}
15671567

1568+
@Test
1569+
void testNativeResetDuration() {
1570+
// Test that nativeResetDuration can be configured without error and the histogram
1571+
// functions correctly. The reset duration schedules internal reset behavior but
1572+
// is not directly observable in the snapshot.
1573+
Histogram histogram =
1574+
Histogram.builder()
1575+
.name("test_histogram_with_reset")
1576+
.nativeOnly()
1577+
.nativeResetDuration(24, TimeUnit.HOURS)
1578+
.build();
1579+
1580+
histogram.observe(1.0);
1581+
histogram.observe(2.0);
1582+
histogram.observe(3.0);
1583+
1584+
HistogramSnapshot snapshot = histogram.collect();
1585+
assertThat(snapshot.getDataPoints()).hasSize(1);
1586+
HistogramSnapshot.HistogramDataPointSnapshot dataPoint = snapshot.getDataPoints().get(0);
1587+
assertThat(dataPoint.hasNativeHistogramData()).isTrue();
1588+
assertThat(dataPoint.getCount()).isEqualTo(3);
1589+
assertThat(dataPoint.getSum()).isEqualTo(6.0);
1590+
}
1591+
1592+
@Test
1593+
void testNativeResetDurationNegativeValue() {
1594+
assertThatExceptionOfType(IllegalArgumentException.class)
1595+
.isThrownBy(
1596+
() ->
1597+
Histogram.builder()
1598+
.name("test_histogram")
1599+
.nativeOnly()
1600+
.nativeResetDuration(-1, TimeUnit.HOURS)
1601+
.build())
1602+
.withMessageContaining("value > 0 expected");
1603+
}
1604+
1605+
@Test
1606+
void testNativeResetDurationZeroValue() {
1607+
assertThatExceptionOfType(IllegalArgumentException.class)
1608+
.isThrownBy(
1609+
() ->
1610+
Histogram.builder()
1611+
.name("test_histogram")
1612+
.nativeOnly()
1613+
.nativeResetDuration(0, TimeUnit.HOURS)
1614+
.build())
1615+
.withMessageContaining("value > 0 expected");
1616+
}
1617+
1618+
@Test
1619+
void testNativeResetDurationSubSecond() {
1620+
// Sub-second durations should be rejected as they truncate to 0 seconds
1621+
assertThatExceptionOfType(IllegalArgumentException.class)
1622+
.isThrownBy(
1623+
() ->
1624+
Histogram.builder()
1625+
.name("test_histogram")
1626+
.nativeOnly()
1627+
.nativeResetDuration(500, TimeUnit.MILLISECONDS)
1628+
.build())
1629+
.withMessageContaining("duration must be at least 1 second");
1630+
}
1631+
15681632
private HistogramSnapshot.HistogramDataPointSnapshot getData(
15691633
Histogram histogram, String... labels) {
15701634
return histogram.collect().getDataPoints().stream()

prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusMetricProducer.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,14 @@ public PrometheusMetricProducer(
4141

4242
@Override
4343
public Collection<MetricData> collectAllMetrics() {
44-
// TODO: We could add a filter configuration for the OpenTelemetry exporter and call
45-
// registry.scrape(filter) if a filter is configured, like in the Servlet exporter.
44+
// Note: Currently all metrics from the registry are exported. To add metric filtering
45+
// similar to the Servlet exporter, one could:
46+
// 1. Add filter properties to ExporterOpenTelemetryProperties (allowedNames, excludedNames,
47+
// etc.)
48+
// 2. Convert these properties to a Predicate<String> using MetricNameFilter.builder()
49+
// 3. Call registry.scrape(filter) instead of registry.scrape()
50+
// OpenTelemetry also provides its own Views API for filtering and aggregation, which may be
51+
// preferred for OpenTelemetry-specific deployments.
4652
MetricSnapshots snapshots = registry.scrape();
4753
Resource resourceWithTargetInfo = resource.merge(resourceFromTargetInfo(snapshots));
4854
InstrumentationScopeInfo scopeFromInfo = instrumentationScopeFromOtelScopeInfo(snapshots);

0 commit comments

Comments
 (0)