Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions docs/content/config/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,23 @@ The properties file is searched in the following locations:

<!-- editorconfig-checker-disable -->

| Name | Javadoc | Note |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Name | Javadoc | Note |
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|
| io.prometheus.metrics.exemplarsEnabled | [Counter.Builder.withExemplars()](</client_java/api/io/prometheus/metrics/core/metrics/Counter.Builder.html#withExemplars()>) | (1) (2) |
| io.prometheus.metrics.histogramNativeOnly | [Histogram.Builder.nativeOnly()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeOnly()>) | (2) |
| io.prometheus.metrics.histogramClassicOnly | [Histogram.Builder.classicOnly()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#classicOnly()>) | (2) |
| io.prometheus.metrics.histogramClassicUpperBounds | [Histogram.Builder.classicUpperBounds()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#classicUpperBounds(double...)>) | (3) |
| io.prometheus.metrics.histogramNativeInitialSchema | [Histogram.Builder.nativeInitialSchema()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeInitialSchema(int)>) | |
| io.prometheus.metrics.histogramNativeMinZeroThreshold | [Histogram.Builder.nativeMinZeroThreshold()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeMinZeroThreshold(double)>) | |
| io.prometheus.metrics.histogramNativeMaxZeroThreshold | [Histogram.Builder.nativeMaxZeroThreshold()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeMaxZeroThreshold(double)>) | |
| io.prometheus.metrics.histogramNativeMaxNumberOfBuckets | [Histogram.Builder.nativeMaxNumberOfBuckets()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeMaxNumberOfBuckets(int)>) | |
| io.prometheus.metrics.histogramNativeResetDurationSeconds | [Histogram.Builder.nativeResetDuration()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeResetDuration(long,java.util.concurrent.TimeUnit)>) | |
| io.prometheus.metrics.summaryQuantiles | [Summary.Builder.quantile(double)](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#quantile(double)>) | (4) |
| io.prometheus.metrics.summaryQuantileErrors | [Summary.Builder.quantile(double, double)](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#quantile(double,double)>) | (5) |
| io.prometheus.metrics.summaryMaxAgeSeconds | [Summary.Builder.maxAgeSeconds()](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#maxAgeSeconds(long)>) | |
| io.prometheus.metrics.summaryNumberOfAgeBuckets | [Summary.Builder.numberOfAgeBuckets()](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#numberOfAgeBuckets(int)>) | |
| io.prometheus.metrics.histogramNativeOnly | [Histogram.Builder.nativeOnly()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeOnly()>) | (2) |
| io.prometheus.metrics.histogramClassicOnly | [Histogram.Builder.classicOnly()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#classicOnly()>) | (2) |
| io.prometheus.metrics.histogramClassicUpperBounds | [Histogram.Builder.classicUpperBounds()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#classicUpperBounds(double...)>) | (3) |
| io.prometheus.metrics.histogramNativeInitialSchema | [Histogram.Builder.nativeInitialSchema()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeInitialSchema(int)>) | |
| io.prometheus.metrics.histogramNativeMinZeroThreshold | [Histogram.Builder.nativeMinZeroThreshold()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeMinZeroThreshold(double)>) | |
| io.prometheus.metrics.histogramNativeMaxZeroThreshold | [Histogram.Builder.nativeMaxZeroThreshold()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeMaxZeroThreshold(double)>) | |
| io.prometheus.metrics.histogramNativeMaxNumberOfBuckets | [Histogram.Builder.nativeMaxNumberOfBuckets()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeMaxNumberOfBuckets(int)>) | |
| io.prometheus.metrics.histogramNativeResetDurationSeconds | [Histogram.Builder.nativeResetDuration()](</client_java/api/io/prometheus/metrics/core/metrics/Histogram.Builder.html#nativeResetDuration(long,java.util.concurrent.TimeUnit)>) | |
| io.prometheus.metrics.summaryQuantiles | [Summary.Builder.quantile(double)](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#quantile(double)>) | (4) |
| io.prometheus.metrics.summaryQuantileErrors | [Summary.Builder.quantile(double, double)](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#quantile(double,double)>) | (5) |
| io.prometheus.metrics.summaryMaxAgeSeconds | [Summary.Builder.maxAgeSeconds()](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#maxAgeSeconds(long)>) | |
| io.prometheus.metrics.summaryNumberOfAgeBuckets | [Summary.Builder.numberOfAgeBuckets()](</client_java/api/io/prometheus/metrics/core/metrics/Summary.Builder.html#numberOfAgeBuckets(int)>) | |
| io.prometheus.metrics.useOtelMetrics | [MetricsProperties.useOtelMetrics()](</client_java/api/io/prometheus/metrics/config/MetricsProperties.html#useOtelMetrics()>) | (2) |
| io.prometheus.metrics.otelOptIn | [MetricsProperties.isOtelOptIn()](</client_java/api/io/prometheus/metrics/config/MetricsProperties.html#isOtelOptIn()>) | (2) |

<!-- editorconfig-checker-enable -->

Expand Down
15 changes: 15 additions & 0 deletions docs/content/instrumentation/jvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ jvm_gc_collection_seconds_count{gc="PS Scavenge"} 0
jvm_gc_collection_seconds_sum{gc="PS Scavenge"} 0.0
```

For more detailed GC metrics, enable the [useOtelMetrics](https://prometheus.github.io/client_java/config/config/#metrics-properties) configuration option. This replaces the standard metric with a
histogram implemented according to the [OpenTelemetry Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/#metric-jvmgcduration).

```text
# HELP jvm_gc_duration_seconds Duration of JVM garbage collection actions.
# TYPE jvm_gc_duration_seconds histogram
jvm_gc_duration_seconds_bucket{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation",le="0.01"} 4
jvm_gc_duration_seconds_bucket{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation",le="0.1"} 4
jvm_gc_duration_seconds_bucket{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation",le="1.0"} 4
jvm_gc_duration_seconds_bucket{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation",le="10.0"} 4
jvm_gc_duration_seconds_bucket{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation",le="+Inf"} 4
jvm_gc_duration_seconds_count{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation"} 4
jvm_gc_duration_seconds_sum{jvm_gc_action="end of minor GC",jvm_gc_name="G1 Young Generation"} 0.029
```

## JVM Memory Metrics

JVM memory metrics are provided by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class MetricsProperties {
private static final String SUMMARY_QUANTILE_ERRORS = "summaryQuantileErrors";
private static final String SUMMARY_MAX_AGE_SECONDS = "summaryMaxAgeSeconds";
private static final String SUMMARY_NUMBER_OF_AGE_BUCKETS = "summaryNumberOfAgeBuckets";
private static final String USE_OTEL_METRICS = "useOtelMetrics";
private static final String OTEL_OPT_IN = "otelOptIn";

@Nullable private final Boolean exemplarsEnabled;
@Nullable private final Boolean histogramNativeOnly;
Expand All @@ -42,6 +44,8 @@ public class MetricsProperties {
@Nullable private final List<Double> summaryQuantileErrors;
@Nullable private final Long summaryMaxAgeSeconds;
@Nullable private final Integer summaryNumberOfAgeBuckets;
@Nullable private final Boolean useOtelMetrics;
@Nullable private final Boolean otelOptIn;

public MetricsProperties(
@Nullable Boolean exemplarsEnabled,
Expand All @@ -56,7 +60,9 @@ public MetricsProperties(
@Nullable List<Double> summaryQuantiles,
@Nullable List<Double> summaryQuantileErrors,
@Nullable Long summaryMaxAgeSeconds,
@Nullable Integer summaryNumberOfAgeBuckets) {
@Nullable Integer summaryNumberOfAgeBuckets,
@Nullable Boolean useOtelMetrics,
Boolean otelOptIn) {
this(
exemplarsEnabled,
histogramNativeOnly,
Expand All @@ -71,6 +77,8 @@ public MetricsProperties(
summaryQuantileErrors,
summaryMaxAgeSeconds,
summaryNumberOfAgeBuckets,
useOtelMetrics,
otelOptIn,
"");
}

Expand All @@ -88,8 +96,11 @@ private MetricsProperties(
@Nullable List<Double> summaryQuantileErrors,
@Nullable Long summaryMaxAgeSeconds,
@Nullable Integer summaryNumberOfAgeBuckets,
@Nullable Boolean useOtelMetrics,
@Nullable Boolean otelOptIn,
String configPropertyPrefix) {
this.exemplarsEnabled = exemplarsEnabled;
this.otelOptIn = otelOptIn;
this.histogramNativeOnly = isHistogramNativeOnly(histogramClassicOnly, histogramNativeOnly);
this.histogramClassicOnly = isHistogramClassicOnly(histogramClassicOnly, histogramNativeOnly);
this.histogramClassicUpperBounds =
Expand All @@ -109,6 +120,7 @@ private MetricsProperties(
: unmodifiableList(new ArrayList<>(summaryQuantileErrors));
this.summaryMaxAgeSeconds = summaryMaxAgeSeconds;
this.summaryNumberOfAgeBuckets = summaryNumberOfAgeBuckets;
this.useOtelMetrics = useOtelMetrics;
validate(configPropertyPrefix);
}

Expand Down Expand Up @@ -334,6 +346,22 @@ public Integer getSummaryNumberOfAgeBuckets() {
return summaryNumberOfAgeBuckets;
}

/**
* Where applicable, metrics are registered in accordance with OpenTelemetry Semantic Conventions.
* Implementation should respect opt-in requirements and ensure no data duplication occurs with
* existing Prometheus metrics.
*/
@Nullable
public Boolean useOtelMetrics() {
return useOtelMetrics;
}

/** Where applicable, if using otel metrics, allow usage of opt-in labels */
@Nullable
public Boolean isOtelOptIn() {
return otelOptIn;
}

/**
* Note that this will remove entries from {@code properties}. This is because we want to know if
* there are unused properties remaining after all properties have been loaded.
Expand All @@ -354,6 +382,8 @@ static MetricsProperties load(String prefix, Map<Object, Object> properties)
Util.loadDoubleList(prefix + "." + SUMMARY_QUANTILE_ERRORS, properties),
Util.loadLong(prefix + "." + SUMMARY_MAX_AGE_SECONDS, properties),
Util.loadInteger(prefix + "." + SUMMARY_NUMBER_OF_AGE_BUCKETS, properties),
Util.loadBoolean(prefix + "." + USE_OTEL_METRICS, properties),
Util.loadBoolean(prefix + "." + OTEL_OPT_IN, properties),
prefix);
}

Expand All @@ -375,6 +405,8 @@ public static class Builder {
@Nullable private List<Double> summaryQuantileErrors;
@Nullable private Long summaryMaxAgeSeconds;
@Nullable private Integer summaryNumberOfAgeBuckets;
@Nullable private Boolean useOtelMetrics;
@Nullable private Boolean otelOptIn;

private Builder() {}

Expand All @@ -392,7 +424,9 @@ public MetricsProperties build() {
summaryQuantiles,
summaryQuantileErrors,
summaryMaxAgeSeconds,
summaryNumberOfAgeBuckets);
summaryNumberOfAgeBuckets,
useOtelMetrics,
otelOptIn);
}

/** See {@link MetricsProperties#getExemplarsEnabled()} */
Expand Down Expand Up @@ -476,5 +510,17 @@ public Builder summaryNumberOfAgeBuckets(@Nullable Integer summaryNumberOfAgeBuc
this.summaryNumberOfAgeBuckets = summaryNumberOfAgeBuckets;
return this;
}

/** See {@link MetricsProperties#useOtelMetrics()} */
public Builder useOtelMetrics(@Nullable Boolean useOtelMetrics) {
this.useOtelMetrics = useOtelMetrics;
return this;
}

/** See {@link MetricsProperties#isOtelOptIn()} */
public Builder otelOptIn(@Nullable Boolean otelOptIn) {
this.otelOptIn = otelOptIn;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -102,6 +103,20 @@ public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() {
return exporterOpenTelemetryProperties;
}

public boolean useOtelMetrics(String prometheusMetric, String otelMetric) {
Boolean useByPrometheusMetric = usesOtelMetric(prometheusMetric);
if (Boolean.FALSE.equals(useByPrometheusMetric)) return false;
Boolean useByOtelMetric = usesOtelMetric(otelMetric);
if (Boolean.FALSE.equals(useByOtelMetric)) return false;
return Boolean.TRUE.equals(getDefaultMetricProperties().useOtelMetrics());
}

private Boolean usesOtelMetric(String metric) {
return Optional.ofNullable(getMetricProperties(metric))
.map(MetricsProperties::useOtelMetrics)
.orElse(null);
}

public static class Builder {
private MetricsProperties defaultMetricsProperties = MetricsProperties.builder().build();
private Map<String, MetricsProperties> metricProperties = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -67,4 +68,44 @@ public void testBuilder() {
assertThat(result.getMetricProperties("unknown_metric")).isNull();
assertThat(result.getExporterProperties()).isSameAs(defaults.getExporterProperties());
}

@Test
void useOtelMetricsSupportsNegativeOverride() {
Map<String, MetricsProperties> metricMap = new HashMap<>();
metricMap.put("prom_metric", otelProperties(false));
PrometheusProperties props = buildProperties(true, metricMap);
assertThat(props.useOtelMetrics("prom_metric", "any_otel_metric")).isFalse();
}

@Test
void useOtelMetricsDisablesByMetricName() {
Map<String, MetricsProperties> metricMap = new HashMap<>();
metricMap.put("otel_metric", otelProperties(false));
PrometheusProperties props = buildProperties(true, metricMap);
assertThat(props.useOtelMetrics("some_prom_metric", "otel_metric")).isFalse();
}

@Test
void useOtelMetricsRespectsDefaultIfNoOverride() {
PrometheusProperties props = buildProperties(true, Collections.emptyMap());
assertThat(props.useOtelMetrics("prom_x", "otel_y")).isTrue();
}

@Test
void noOverridesReturnsFalse() {
PrometheusProperties props = PrometheusProperties.get();
assertThat(props.useOtelMetrics("prom_x", "otel_y")).isFalse();
}

private static PrometheusProperties buildProperties(
Boolean defaultUse, Map<String, MetricsProperties> metricProps) {
return PrometheusProperties.builder()
.defaultMetricsProperties(otelProperties(defaultUse))
.metricProperties(new HashMap<>(metricProps))
.build();
}

private static MetricsProperties otelProperties(Boolean useOtel) {
return MetricsProperties.builder().useOtelMetrics(useOtel).build();
}
}
Loading