diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java index 207c024a5..72e2b28ad 100644 --- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java @@ -77,7 +77,12 @@ protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeR return new MetricSnapshots(snaps); } + /** + * @deprecated Use {@code getMetricFamilyDescriptors()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public List getPrometheusNames() { List names = new ArrayList(); names.add("x_calls_total"); diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java index 8530c988f..a93321392 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java @@ -90,10 +90,15 @@ protected CounterSnapshot collect(List labels, List metricDat for (int i = 0; i < labels.size(); i++) { data.add(metricData.get(i).collect(labels.get(i))); } - return new CounterSnapshot(getMetadata(), data); + return new CounterSnapshot(metadata, data); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.COUNTER; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java index 3a818c004..de2e41174 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java @@ -48,10 +48,15 @@ public CounterSnapshot collect() { new CounterSnapshot.CounterDataPointSnapshot( value, makeLabels(labelValues), null, 0L)); }); - return new CounterSnapshot(getMetadata(), dataPoints); + return new CounterSnapshot(metadata, dataPoints); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.COUNTER; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java index 8b1f31409..7dbfd9d9b 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java @@ -92,10 +92,15 @@ protected GaugeSnapshot collect(List labels, List metricData) for (int i = 0; i < labels.size(); i++) { dataPointSnapshots.add(metricData.get(i).collect(labels.get(i))); } - return new GaugeSnapshot(getMetadata(), dataPointSnapshots); + return new GaugeSnapshot(metadata, dataPointSnapshots); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.GAUGE; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java index 88aee225f..3b26cb520 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java @@ -52,10 +52,15 @@ public GaugeSnapshot collect() { dataPoints.add( new GaugeSnapshot.GaugeDataPointSnapshot(value, makeLabels(labelValues), null, 0L)); }); - return new GaugeSnapshot(getMetadata(), dataPoints); + return new GaugeSnapshot(metadata, dataPoints); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.GAUGE; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java index 930d9e67e..4412911d3 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java @@ -647,10 +647,15 @@ protected HistogramSnapshot collect(List labels, List metricD for (int i = 0; i < labels.size(); i++) { data.add(metricData.get(i).collect(labels.get(i))); } - return new HistogramSnapshot(getMetadata(), data); + return new HistogramSnapshot(metadata, data); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.HISTOGRAM; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java index e782c9156..1aab04054 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java @@ -48,7 +48,7 @@ public void setLabelValues(String... labelValues) { throw new IllegalArgumentException( getClass().getSimpleName() + " " - + getMetadata().getName() + + metadata.getName() + " was created with " + labelNames.length + " label names, but you called setLabelValues() with " @@ -66,7 +66,7 @@ public void addLabelValues(String... labelValues) { throw new IllegalArgumentException( getClass().getSimpleName() + " " - + getMetadata().getName() + + metadata.getName() + " was created with " + labelNames.length + " label names, but you called addLabelValues() with " @@ -82,7 +82,7 @@ public void remove(String... labelValues) { throw new IllegalArgumentException( getClass().getSimpleName() + " " - + getMetadata().getName() + + metadata.getName() + " was created with " + labelNames.length + " label names, but you called remove() with " @@ -103,10 +103,15 @@ public InfoSnapshot collect() { data.add(new InfoSnapshot.InfoDataPointSnapshot(label.merge(constLabels))); } } - return new InfoSnapshot(getMetadata(), data); + return new InfoSnapshot(metadata, data); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.INFO; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java index 1b63004d8..7cbbaaed6 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.Label; import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Unit; @@ -20,7 +22,7 @@ */ public abstract class MetricWithFixedMetadata extends Metric { - private final MetricMetadata metadata; + protected final MetricMetadata metadata; protected final String[] labelNames; protected MetricWithFixedMetadata(Builder builder) { @@ -37,6 +39,22 @@ protected MetricWithFixedMetadata(Builder builder) { } @Override + @Nullable + @SuppressWarnings("deprecation") + public MetricFamilyDescriptor getMetricFamilyDescriptor() { + MetricType metricType = getMetricType(); + if (metricType == null) { + return null; + } + return MetricFamilyDescriptor.of(metricType, metadata, getPrometheusLabels()); + } + + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ + @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricMetadata getMetadata() { return metadata; } @@ -65,13 +83,27 @@ private String makeExpositionBaseName(@Nullable String expositionBaseName, @Null return expositionBaseName; } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public String getPrometheusName() { return metadata.getPrometheusName(); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Set getLabelNames() { + return getPrometheusLabels(); + } + + private Set getPrometheusLabels() { Set names = new HashSet<>(); for (String labelName : labelNames) { names.add(PrometheusNaming.prometheusName(labelName)); diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java index 740183f31..2ad314ee7 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java @@ -60,7 +60,7 @@ private StateSet(Builder builder, String[] names) { super(builder); this.names = names; for (String name : names) { - if (this.getMetadata().getPrometheusName().equals(prometheusName(name))) { + if (metadata.getPrometheusName().equals(prometheusName(name))) { throw new IllegalArgumentException( "Label name " + name @@ -82,10 +82,15 @@ protected StateSetSnapshot collect(List labels, List metricDa new StateSetSnapshot.StateSetDataPointSnapshot( names, metricDataList.get(i).values, labels.get(i))); } - return new StateSetSnapshot(getMetadata(), data); + return new StateSetSnapshot(metadata, data); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.STATESET; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java index 386e92292..f9d02d3d7 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java @@ -105,7 +105,7 @@ public D labelValues(String... labelValues) { throw new IllegalArgumentException( getClass().getSimpleName() + " " - + getMetadata().getName() + + metadata.getName() + " was created with label names, so you must call labelValues(...)" + " when using it."); } else { @@ -120,7 +120,7 @@ public D labelValues(String... labelValues) { if (l.get(i) == null) { throw new IllegalArgumentException( "null label value for metric " - + getMetadata().getName() + + metadata.getName() + " and label " + labelNames[i]); } @@ -171,7 +171,7 @@ protected MetricsProperties[] getMetricProperties( if (Objects.equals(builder.exemplarsEnabled, false)) { properties.add(MetricsProperties.builder().exemplarsEnabled(false).build()); } - String metricName = getMetadata().getName(); + String metricName = metadata.getName(); if (prometheusProperties.getMetricProperties(metricName) != null) { properties.add(prometheusProperties.getMetricProperties(metricName)); } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java index 47b6e2a9c..b200274b9 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java @@ -116,10 +116,15 @@ protected SummarySnapshot collect(List labels, List metricDat for (int i = 0; i < labels.size(); i++) { data.add(metricData.get(i).collect(labels.get(i))); } - return new SummarySnapshot(getMetadata(), data); + return new SummarySnapshot(metadata, data); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.SUMMARY; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java index fa823e68e..8229a817c 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java @@ -61,10 +61,15 @@ public SummarySnapshot collect() { new SummarySnapshot.SummaryDataPointSnapshot( count, sum, quantiles, makeLabels(labelValues), Exemplars.EMPTY, 0L)); }); - return new SummarySnapshot(getMetadata(), dataPoints); + return new SummarySnapshot(metadata, dataPoints); } + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public MetricType getMetricType() { return MetricType.SUMMARY; } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadataTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadataTest.java new file mode 100644 index 000000000..90d49f3e9 --- /dev/null +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadataTest.java @@ -0,0 +1,75 @@ +package io.prometheus.metrics.core.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.MetricType; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class MetricWithFixedMetadataTest { + + @Test + @SuppressWarnings("deprecation") + void getMetricFamilyDescriptorAdaptsDeprecatedMetricTypeOverride() { + LegacyMetric metric = + LegacyMetric.builder() + .name("legacy.metric") + .constLabels(Labels.of("const.label", "value")) + .labelNames("dynamic.label") + .build(); + + MetricFamilyDescriptor descriptor = metric.getMetricFamilyDescriptor(); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getType()).isEqualTo(MetricType.GAUGE); + assertThat(descriptor.getPrometheusName()).isEqualTo("legacy_metric"); + assertThat(descriptor.getLabelNames()) + .containsExactlyInAnyOrder("const_label", "dynamic_label"); + } + + private static class LegacyMetric extends MetricWithFixedMetadata { + + private LegacyMetric(Builder builder) { + super(builder); + } + + private static Builder builder() { + return new Builder(); + } + + @Override + public MetricSnapshot collect() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. + */ + @Override + @Deprecated + public MetricType getMetricType() { + return MetricType.GAUGE; + } + + private static class Builder extends MetricWithFixedMetadata.Builder { + + private Builder() { + super(Collections.emptyList(), PrometheusProperties.builder().build()); + } + + @Override + public LegacyMetric build() { + return new LegacyMetric(this); + } + + @Override + protected Builder self() { + return this; + } + } + } +} diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java index 553d84af5..1ae7e3179 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java @@ -19,7 +19,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -@SuppressWarnings("NonCanonicalType") +@SuppressWarnings({"NonCanonicalType", "deprecation"}) class DuplicateNamesProtobufTest { private static PrometheusRegistry getPrometheusRegistry() { diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java index 8d9643378..9251ac85e 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java @@ -61,6 +61,9 @@ public Builder setIncludeCreatedTimestamps(boolean includeCreatedTimestamps) { return this; } + /** + * @deprecated Use {@link #build()} with the default millisecond timestamps instead. + */ @Deprecated public Builder setTimestampsInMs(boolean timestampsInMs) { this.timestampsInMs = timestampsInMs; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java index d1e8c2b9a..db564d479 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java @@ -13,6 +13,7 @@ import java.io.IOException; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") class DuplicateNamesExpositionTest { private static PrometheusRegistry getPrometheusRegistry() { diff --git a/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java b/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java index c847e13eb..88e75353d 100644 --- a/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java +++ b/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java @@ -95,6 +95,8 @@ public class CacheMetricsCollector implements MultiCollector { * *

Note that the {@link #builder()} API has different default values than this deprecated * constructor. + * + * @deprecated Use {@link #builder()} instead. */ @Deprecated public CacheMetricsCollector() { @@ -313,7 +315,12 @@ public MetricSnapshots collect() { .build(); } + /** + * @deprecated Use {@code getMetricFamilyDescriptors()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public List getPrometheusNames() { if (!collectWeightedSize) { return ALL_METRIC_NAMES.stream() diff --git a/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java b/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java index d2d12aa11..cbad48b56 100644 --- a/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java +++ b/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java @@ -22,7 +22,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -@SuppressWarnings("CheckReturnValue") +@SuppressWarnings({"CheckReturnValue", "deprecation"}) class CacheMetricsCollectorTest { // This enum was added to simplify test parametrization on argument options. enum Options { diff --git a/prometheus-metrics-instrumentation-guava/src/main/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollector.java b/prometheus-metrics-instrumentation-guava/src/main/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollector.java index 42473c686..bbb169763 100644 --- a/prometheus-metrics-instrumentation-guava/src/main/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollector.java +++ b/prometheus-metrics-instrumentation-guava/src/main/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollector.java @@ -216,7 +216,12 @@ public MetricSnapshots collect() { return metricSnapshotsBuilder.build(); } + /** + * @deprecated Use {@code getMetricFamilyDescriptors()} instead. + */ @Override + @Deprecated + @SuppressWarnings("InlineMeSuggester") public List getPrometheusNames() { return ALL_METRIC_NAMES; } diff --git a/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java b/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java index 3373afaed..d56e6c15c 100644 --- a/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java +++ b/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") class CacheMetricsCollectorTest { @Test diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java index 1d1e7d232..f903ce676 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java @@ -1,7 +1,9 @@ package io.prometheus.metrics.model.registry; +import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import java.util.Collections; import java.util.Set; import java.util.function.Predicate; import javax.annotation.Nullable; @@ -60,6 +62,33 @@ default MetricSnapshot collect( } } + /** + * Returns a registration-time descriptor for this metric family. + * + *

The registry uses this descriptor for duplicate-name, type, label-schema, help, and unit + * validation at registration time. Returning {@code null} means registration-time validation is + * skipped for this collector. + * + *

The default implementation adapts the deprecated fragmented metadata methods. New collectors + * with fixed registration-time metadata should override this method directly. + */ + @Nullable + @SuppressWarnings("deprecation") + default MetricFamilyDescriptor getMetricFamilyDescriptor() { + String prometheusName = getPrometheusName(); + MetricType metricType = getMetricType(); + if (prometheusName == null || metricType == null) { + return null; + } + MetricMetadata metadata = getMetadata(); + if (metadata == null) { + metadata = new MetricMetadata(prometheusName); + } + Set labelNames = getLabelNames(); + return MetricFamilyDescriptor.of( + metricType, metadata, labelNames == null ? Collections.emptySet() : labelNames); + } + /** * This is called in two places: * @@ -77,7 +106,10 @@ default MetricSnapshot collect( * this and return the name. * *

All metrics in {@code prometheus-metrics-core} override this. + * + * @deprecated Override {@link #getMetricFamilyDescriptor()} instead. */ + @Deprecated @Nullable default String getPrometheusName() { return null; @@ -94,7 +126,9 @@ default String getPrometheusName() { * result in invalid exposition output. * * @return the metric type, or {@code null} to skip validation + * @deprecated Override {@link #getMetricFamilyDescriptor()} instead. */ + @Deprecated @Nullable default MetricType getMetricType() { return null; @@ -118,7 +152,9 @@ default MetricType getMetricType() { * * @return the set of all label names, or {@code null} (treated as empty) for a metric with no * labels + * @deprecated Override {@link #getMetricFamilyDescriptor()} instead. */ + @Deprecated @Nullable default Set getLabelNames() { return null; @@ -132,7 +168,9 @@ default Set getLabelNames() { * collector. * * @return the metric metadata, or {@code null} to skip help/unit validation + * @deprecated Override {@link #getMetricFamilyDescriptor()} instead. */ + @Deprecated @Nullable default MetricMetadata getMetadata() { return null; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java index 27ac3e10c..692f0be71 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.model.registry; +import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -53,6 +55,36 @@ default MetricSnapshots collect( return result.build(); } + /** + * Returns registration-time descriptors for the metric families collected by this collector. + * + *

The registry uses these descriptors for duplicate-name, type, label-schema, help, and unit + * validation at registration time. Returning an empty list means registration-time validation is + * skipped for this collector. + * + *

The default implementation adapts the deprecated fragmented metadata methods. New collectors + * with fixed registration-time metadata should override this method directly. + */ + @SuppressWarnings("deprecation") + default List getMetricFamilyDescriptors() { + List prometheusNames = getPrometheusNames(); + List descriptors = new ArrayList<>(prometheusNames.size()); + for (String prometheusName : prometheusNames) { + MetricType metricType = getMetricType(prometheusName); + if (metricType != null) { + MetricMetadata metadata = getMetadata(prometheusName); + if (metadata == null) { + metadata = new MetricMetadata(prometheusName); + } + Set labelNames = getLabelNames(prometheusName); + descriptors.add( + MetricFamilyDescriptor.of( + metricType, metadata, labelNames == null ? Collections.emptySet() : labelNames)); + } + } + return Collections.unmodifiableList(descriptors); + } + /** * This is called in two places: * @@ -68,7 +100,10 @@ default MetricSnapshots collect( * *

If your collector returns a constant list of metrics that have names that do not change at * runtime it is a good idea to overwrite this and return the names. + * + * @deprecated Override {@link #getMetricFamilyDescriptors()} instead. */ + @Deprecated default List getPrometheusNames() { return Collections.emptyList(); } @@ -85,7 +120,9 @@ default List getPrometheusNames() { * * @param prometheusName the Prometheus metric name * @return the metric type for the given name, or {@code null} to skip validation + * @deprecated Override {@link #getMetricFamilyDescriptors()} instead. */ + @Deprecated @Nullable default MetricType getMetricType(String prometheusName) { return null; @@ -108,7 +145,9 @@ default MetricType getMetricType(String prometheusName) { * @param prometheusName the Prometheus metric name * @return the set of all label names for the given name, or {@code null} (treated as empty) for a * metric with no labels + * @deprecated Override {@link #getMetricFamilyDescriptors()} instead. */ + @Deprecated @Nullable default Set getLabelNames(String prometheusName) { return null; @@ -123,7 +162,9 @@ default Set getLabelNames(String prometheusName) { * * @param prometheusName the Prometheus metric name * @return the metric metadata for that name, or {@code null} to skip help/unit validation + * @deprecated Override {@link #getMetricFamilyDescriptors()} instead. */ + @Deprecated @Nullable default MetricMetadata getMetadata(String prometheusName) { return null; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java index b7e305977..071c90f4b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java @@ -2,6 +2,7 @@ import static java.util.Collections.emptySet; +import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; @@ -158,6 +159,17 @@ private static Set immutableLabelNames(@Nullable Set labelNames) return Collections.unmodifiableSet(new HashSet<>(labelNames)); } + @SuppressWarnings("deprecation") + @Nullable + private static String getDeprecatedPrometheusName(Collector collector) { + return collector.getPrometheusName(); + } + + @SuppressWarnings("deprecation") + private static List getDeprecatedPrometheusNames(MultiCollector collector) { + return collector.getPrometheusNames(); + } + /** * Computes the set of exposition-level time series names that a metric with the given name and * type will produce. @@ -288,27 +300,24 @@ public void register(Collector collector) { throw new IllegalArgumentException("Collector instance is already registered"); } try { - String prometheusName = collector.getPrometheusName(); - MetricType metricType = collector.getMetricType(); - Set normalizedLabels = immutableLabelNames(collector.getLabelNames()); - MetricMetadata metadata = collector.getMetadata(); - String help = metadata != null ? metadata.getHelp() : null; - Unit unit = metadata != null ? metadata.getUnit() : null; - - // Only perform validation if collector provides sufficient metadata. - // Collectors that don't implement getPrometheusName()/getMetricType() will skip validation. - if (prometheusName != null && metricType != null) { - validateRegistration(prometheusName, metricType, normalizedLabels, help, unit); - String expositionBasePrometheusName = - metadata != null ? metadata.getExpositionBasePrometheusName() : null; + MetricFamilyDescriptor descriptor = collector.getMetricFamilyDescriptor(); + + // Only perform validation if collector provides sufficient metadata. Collectors that don't + // implement getMetricFamilyDescriptor() will skip registration-time validation. + if (descriptor != null) { + String prometheusName = descriptor.getPrometheusName(); + MetricType metricType = descriptor.getType(); + Set normalizedLabels = immutableLabelNames(descriptor.getLabelNames()); + MetricMetadata metadata = descriptor.getMetadata(); + validateRegistration( + prometheusName, metricType, normalizedLabels, metadata.getHelp(), metadata.getUnit()); collectorMetadata.put( collector, new CollectorRegistration( - prometheusName, expositionBasePrometheusName, normalizedLabels)); + prometheusName, metadata.getExpositionBasePrometheusName(), normalizedLabels)); } - // Catch RuntimeException broadly because collector methods (getPrometheusName, getMetricType, - // etc.) are user-implemented and could throw any RuntimeException. Ensures cleanup on - // failure. + // Catch RuntimeException broadly because collector methods are user-implemented and could + // throw any RuntimeException. Ensures cleanup on failure. } catch (RuntimeException e) { collectors.remove(collector); CollectorRegistration reg = collectorMetadata.remove(collector); @@ -323,27 +332,26 @@ public void register(MultiCollector collector) { if (!multiCollectors.add(collector)) { throw new IllegalArgumentException("MultiCollector instance is already registered"); } - List prometheusNamesList = collector.getPrometheusNames(); List registrations = new ArrayList<>(); try { - for (String prometheusName : prometheusNamesList) { - MetricType metricType = collector.getMetricType(prometheusName); - Set normalizedLabels = immutableLabelNames(collector.getLabelNames(prometheusName)); - MetricMetadata metadata = collector.getMetadata(prometheusName); - String help = metadata != null ? metadata.getHelp() : null; - Unit unit = metadata != null ? metadata.getUnit() : null; - - if (metricType != null) { - validateRegistration(prometheusName, metricType, normalizedLabels, help, unit); - registrations.add(new MultiCollectorRegistration(prometheusName, normalizedLabels)); - } + for (MetricFamilyDescriptor descriptor : collector.getMetricFamilyDescriptors()) { + String prometheusName = descriptor.getPrometheusName(); + Set normalizedLabels = immutableLabelNames(descriptor.getLabelNames()); + MetricMetadata metadata = descriptor.getMetadata(); + + validateRegistration( + prometheusName, + descriptor.getType(), + normalizedLabels, + metadata.getHelp(), + metadata.getUnit()); + registrations.add(new MultiCollectorRegistration(prometheusName, normalizedLabels)); } multiCollectorMetadata.put(collector, registrations); - // Catch RuntimeException broadly because collector methods (getPrometheusNames, - // getMetricType, etc.) are user-implemented and could throw any RuntimeException. - // Ensures cleanup on failure. + // Catch RuntimeException broadly because collector methods are user-implemented and could + // throw any RuntimeException. Ensures cleanup on failure. } catch (RuntimeException e) { multiCollectors.remove(collector); for (MultiCollectorRegistration registration : registrations) { @@ -445,11 +453,12 @@ public MetricSnapshots scrape( } List allSnapshots = new ArrayList<>(); for (Collector collector : collectors) { - String prometheusName = collector.getPrometheusName(); + CollectorRegistration reg = collectorMetadata.get(collector); + String prometheusName = + reg != null ? reg.prometheusName : getDeprecatedPrometheusName(collector); // prometheusName == null means the name is unknown, and we have to scrape to learn the name. // prometheusName != null means we can skip the scrape if the name is excluded. // Also test the original name (e.g. "events_total" for a counter named "events"). - CollectorRegistration reg = collectorMetadata.get(collector); String expositionName = reg != null ? reg.expositionBasePrometheusName : null; if (prometheusName == null || includedNames.test(prometheusName) @@ -464,12 +473,22 @@ public MetricSnapshots scrape( } } for (MultiCollector collector : multiCollectors) { - List prometheusNames = collector.getPrometheusNames(); + List registrations = multiCollectorMetadata.get(collector); + List prometheusNames = getDeprecatedPrometheusNames(collector); // empty prometheusNames means the names are unknown, and we have to scrape to learn the // names. // non-empty prometheusNames means we can exclude the collector if all names are excluded by // the filter. - boolean excluded = !prometheusNames.isEmpty(); + boolean excluded = + (registrations != null && !registrations.isEmpty()) || !prometheusNames.isEmpty(); + if (registrations != null) { + for (MultiCollectorRegistration registration : registrations) { + if (includedNames.test(registration.prometheusName)) { + excluded = false; + break; + } + } + } for (String prometheusName : prometheusNames) { if (includedNames.test(prometheusName)) { excluded = false; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java index 72a83a879..e5831168b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java @@ -179,6 +179,14 @@ public Builder dataPoint(CounterDataPointSnapshot dataPoint) { return this; } + @Override + protected MetricMetadata buildMetadata() { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } + return MetricMetadataSupport.counterMetadata(name, help, unit); + } + @Override public CounterSnapshot build() { return new CounterSnapshot(buildMetadata(), dataPoints); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java index ca6cf70a0..20f9038b1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java @@ -118,6 +118,14 @@ public Builder unit(@Nullable Unit unit) { throw new IllegalArgumentException("Info metric cannot have a unit."); } + @Override + protected MetricMetadata buildMetadata() { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } + return MetricMetadataSupport.infoMetadata(name, help); + } + @Override public InfoSnapshot build() { return new InfoSnapshot(buildMetadata(), dataPoints); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptor.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptor.java new file mode 100644 index 000000000..f77fcbeac --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptor.java @@ -0,0 +1,275 @@ +package io.prometheus.metrics.model.snapshots; + +import io.prometheus.metrics.model.registry.MetricType; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.Nullable; + +/** Registration-time descriptor for a metric family. */ +public final class MetricFamilyDescriptor { + + private final MetricType type; + private final MetricMetadata metadata; + private final Set labelNames; + + private MetricFamilyDescriptor( + MetricType type, MetricMetadata metadata, Collection labelNames) { + if (type == null) { + throw new IllegalArgumentException("Missing required field: type is null"); + } + if (metadata == null) { + throw new IllegalArgumentException("Missing required field: metadata is null"); + } + if (labelNames == null) { + throw new IllegalArgumentException("Missing required field: labelNames is null"); + } + this.type = type; + this.metadata = metadata; + this.labelNames = Collections.unmodifiableSet(new LinkedHashSet<>(labelNames)); + } + + public static MetricFamilyDescriptor of(MetricType type, MetricMetadata metadata) { + return of(type, metadata, Collections.emptySet()); + } + + public static MetricFamilyDescriptor of( + MetricType type, MetricMetadata metadata, Collection labelNames) { + return new MetricFamilyDescriptor(type, metadata, labelNames); + } + + public static Builder of(MetricType type, String name) { + switch (type) { + case COUNTER: + return counter(name); + case GAUGE: + return gauge(name); + case HISTOGRAM: + return histogram(name); + case SUMMARY: + return summary(name); + case INFO: + return info(name); + case STATESET: + return stateSet(name); + case UNKNOWN: + default: + return unknown(name); + } + } + + public static CounterBuilder counter(String name) { + return new CounterBuilder().name(name); + } + + public static GaugeBuilder gauge(String name) { + return new GaugeBuilder().name(name); + } + + public static HistogramBuilder histogram(String name) { + return new HistogramBuilder().name(name); + } + + public static SummaryBuilder summary(String name) { + return new SummaryBuilder().name(name); + } + + public static InfoBuilder info(String name) { + return new InfoBuilder().name(name); + } + + public static StateSetBuilder stateSet(String name) { + return new StateSetBuilder().name(name); + } + + public static UnknownBuilder unknown(String name) { + return new UnknownBuilder().name(name); + } + + public MetricType getType() { + return type; + } + + public MetricMetadata getMetadata() { + return metadata; + } + + public Set getLabelNames() { + return labelNames; + } + + public String getPrometheusName() { + return metadata.getPrometheusName(); + } + + public abstract static class Builder> { + + @Nullable protected String name; + @Nullable protected String help; + @Nullable protected Unit unit; + protected final Set labelNames = new LinkedHashSet<>(); + + public T name(String name) { + this.name = name; + return self(); + } + + public T help(@Nullable String help) { + this.help = help; + return self(); + } + + public T unit(@Nullable Unit unit) { + this.unit = unit; + return self(); + } + + public T labelName(String labelName) { + this.labelNames.add(labelName); + return self(); + } + + public T labelNames(String... labelNames) { + Collections.addAll(this.labelNames, labelNames); + return self(); + } + + public T labelNames(Collection labelNames) { + this.labelNames.addAll(labelNames); + return self(); + } + + public MetricFamilyDescriptor build() { + return new MetricFamilyDescriptor(getType(), buildMetadata(), labelNames); + } + + protected MetricMetadata buildMetadata() { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } + return MetricMetadataSupport.metricMetadata(name, help, unit); + } + + protected abstract MetricType getType(); + + protected abstract T self(); + } + + public static final class CounterBuilder extends Builder { + + @Override + protected MetricMetadata buildMetadata() { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } + return MetricMetadataSupport.counterMetadata(name, help, unit); + } + + @Override + protected MetricType getType() { + return MetricType.COUNTER; + } + + @Override + protected CounterBuilder self() { + return this; + } + } + + public static final class GaugeBuilder extends Builder { + + @Override + protected MetricType getType() { + return MetricType.GAUGE; + } + + @Override + protected GaugeBuilder self() { + return this; + } + } + + public static final class HistogramBuilder extends Builder { + + @Override + protected MetricType getType() { + return MetricType.HISTOGRAM; + } + + @Override + protected HistogramBuilder self() { + return this; + } + } + + public static final class SummaryBuilder extends Builder { + + @Override + protected MetricType getType() { + return MetricType.SUMMARY; + } + + @Override + protected SummaryBuilder self() { + return this; + } + } + + public static final class InfoBuilder extends Builder { + + @Override + public InfoBuilder unit(@Nullable Unit unit) { + throw new IllegalArgumentException("Info metric cannot have a unit."); + } + + @Override + protected MetricMetadata buildMetadata() { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } + return MetricMetadataSupport.infoMetadata(name, help); + } + + @Override + protected MetricType getType() { + return MetricType.INFO; + } + + @Override + protected InfoBuilder self() { + return this; + } + } + + public static final class StateSetBuilder extends Builder { + + @Override + public StateSetBuilder unit(@Nullable Unit unit) { + throw new IllegalArgumentException("State set metric cannot have a unit."); + } + + @Override + protected MetricType getType() { + return MetricType.STATESET; + } + + @Override + protected StateSetBuilder self() { + return this; + } + } + + public static final class UnknownBuilder extends Builder { + + @Override + protected MetricType getType() { + return MetricType.UNKNOWN; + } + + @Override + protected UnknownBuilder self() { + return this; + } + } +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java new file mode 100644 index 000000000..7fa0df6f0 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java @@ -0,0 +1,52 @@ +package io.prometheus.metrics.model.snapshots; + +import javax.annotation.Nullable; + +final class MetricMetadataSupport { + + private MetricMetadataSupport() {} + + static MetricMetadata metricMetadata(String name, @Nullable String help, @Nullable Unit unit) { + return new MetricMetadata(name, help, unit); + } + + static MetricMetadata counterMetadata(String name, @Nullable String help, @Nullable Unit unit) { + return typedMetadata(name, help, unit, "_total", ".total"); + } + + static MetricMetadata infoMetadata(String name, @Nullable String help) { + return typedMetadata(name, help, null, "_info", ".info"); + } + + private static MetricMetadata typedMetadata( + String originalName, + @Nullable String help, + @Nullable Unit unit, + String suffix, + String dotSuffix) { + String baseName = stripSuffix(originalName, suffix, dotSuffix); + return new MetricMetadata( + appendUnitIfMissing(baseName, unit), + appendUnitIfMissing(originalName, unit), + originalName, + help, + unit); + } + + private static String appendUnitIfMissing(String name, @Nullable Unit unit) { + if (unit != null && !name.endsWith("_" + unit) && !name.endsWith("." + unit)) { + return name + "_" + unit; + } + return name; + } + + private static String stripSuffix(String name, String suffix, String dotSuffix) { + if (name.endsWith(suffix)) { + return name.substring(0, name.length() - suffix.length()); + } + if (name.endsWith(dotSuffix)) { + return name.substring(0, name.length() - dotSuffix.length()); + } + return name; + } +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java index 4dac2e30e..a5b776ec2 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java @@ -55,9 +55,9 @@ private static void validateLabels( public abstract static class Builder> { - @Nullable private String name; - @Nullable private String help; - @Nullable private Unit unit; + @Nullable protected String name; + @Nullable protected String help; + @Nullable protected Unit unit; /** * The name is required. If the name is missing or invalid, {@code build()} will throw an {@link @@ -85,7 +85,7 @@ protected MetricMetadata buildMetadata() { if (name == null) { throw new IllegalArgumentException("Missing required field: name is null"); } - return new MetricMetadata(name, help, unit); + return MetricMetadataSupport.metricMetadata(name, help, unit); } protected abstract T self(); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java index 48be456a6..46ba39a2b 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java @@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") class MultiCollectorNameFilterTest { private static class Registry extends PrometheusRegistry { diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java index 166b374b8..3142fabe8 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java @@ -20,6 +20,7 @@ * scrape, and unregister continue to work for that usage pattern and that a shared registry with * both SDK-style and validated collectors behaves correctly. */ +@SuppressWarnings("deprecation") class OpenTelemetryExporterRegistryCompatibilityTest { /** diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java index 6dabad653..5e33f0a44 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java @@ -7,6 +7,7 @@ import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; +import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; @@ -16,6 +17,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") class PrometheusRegistryTest { Collector noName = () -> GaugeSnapshot.builder().name("no_name_gauge").build(); @@ -81,7 +83,7 @@ public MetricSnapshots collect() { @Override public List getPrometheusNames() { - return Arrays.asList(gaugeA.getPrometheusName(), counterB.getPrometheusName()); + return Arrays.asList("gauge_a", "counter_b"); } }; @@ -459,6 +461,81 @@ public String getPrometheusName() { assertThatCode(() -> registry.register(legacyCollector2)).doesNotThrowAnyException(); } + @Test + void register_metricFamilyDescriptor_usedForValidation() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counter = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public MetricFamilyDescriptor getMetricFamilyDescriptor() { + return MetricFamilyDescriptor.counter("requests_total").labelName("path").build(); + } + }; + + Collector gauge = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("requests").build(); + } + + @Override + public MetricFamilyDescriptor getMetricFamilyDescriptor() { + return MetricFamilyDescriptor.gauge("requests").labelName("path").build(); + } + }; + + registry.register(counter); + + assertThatThrownBy(() -> registry.register(gauge)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Conflicting metric types"); + } + + @Test + void register_multiCollector_metricFamilyDescriptorsUsedForValidation() { + PrometheusRegistry registry = new PrometheusRegistry(); + + MultiCollector multiCollector = + new MultiCollector() { + @Override + public MetricSnapshots collect() { + return new MetricSnapshots(CounterSnapshot.builder().name("requests_total").build()); + } + + @Override + public List getMetricFamilyDescriptors() { + return asList( + MetricFamilyDescriptor.counter("requests_total").labelName("path").build()); + } + }; + + Collector duplicate = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public MetricFamilyDescriptor getMetricFamilyDescriptor() { + return MetricFamilyDescriptor.counter("requests_total").labelName("path").build(); + } + }; + + registry.register(multiCollector); + + assertThatThrownBy(() -> registry.register(duplicate)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("identical label schema"); + } + @Test void register_multiCollector_withTypeValidation() { PrometheusRegistry registry = new PrometheusRegistry(); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java index 16a324323..a9d9ba000 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java @@ -75,6 +75,7 @@ void testMinimalGoodCase() { .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build()) .build(); SnapshotTestUtil.assertMetadata(snapshot, "events", null, null); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "events", "events", "events"); assertThat(snapshot.getDataPoints()).hasSize(1); CounterDataPointSnapshot data = snapshot.getDataPoints().get(0); assertThat((Iterable) data.getLabels()).isEmpty(); @@ -93,7 +94,18 @@ void testEmptyCounter() { @Test void testTotalSuffixPresent() { CounterSnapshot snapshot = CounterSnapshot.builder().name("test_total").build(); - assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("test_total"); + assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("test"); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "test", "test_total", "test_total"); + } + + @Test + void testCounterUnitDerivedFromTypedBuilder() { + CounterSnapshot snapshot = + CounterSnapshot.builder().name("test_total").unit(Unit.SECONDS).build(); + + SnapshotTestUtil.assertMetadata(snapshot, "test_seconds", null, "seconds"); + SnapshotTestUtil.assertDerivedMetadata( + snapshot, "test_seconds", "test_total_seconds", "test_total"); } @Test diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java index 7bd965913..6a68ebd88 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java @@ -87,14 +87,16 @@ void testEmptyGauge() { @Test void testTotalSuffixPresent() { - CounterSnapshot snapshot = CounterSnapshot.builder().name("test_total").build(); + GaugeSnapshot snapshot = GaugeSnapshot.builder().name("test_total").build(); assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("test_total"); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "test_total", "test_total", "test_total"); } @Test void testTotalSuffixPresentDot() { - CounterSnapshot snapshot = CounterSnapshot.builder().name("test.total").build(); + GaugeSnapshot snapshot = GaugeSnapshot.builder().name("test.total").build(); assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("test_total"); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "test.total", "test.total", "test.total"); } @Test diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java index 20353ea3a..065041bc7 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java @@ -19,7 +19,7 @@ void testCompleteGoodCase() { .labels(Labels.of("instance_id", "127.0.0.1:9100", "service_name", "gateway")) .build()) .build(); - assertThat(snapshot.getMetadata().getName()).isEqualTo("target"); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "target", "target", "target"); assertThat(snapshot.getMetadata().getHelp()).isEqualTo("Target info"); assertThat(snapshot.getMetadata().hasUnit()).isFalse(); assertThat(snapshot.getDataPoints().size()).isOne(); @@ -62,12 +62,14 @@ void testDataImmutable() { @Test void testNameMayIncludeSuffix() { InfoSnapshot snapshot = InfoSnapshot.builder().name("jvm_info").build(); - assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("jvm_info"); + assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("jvm"); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "jvm", "jvm_info", "jvm_info"); } @Test void testNameMayIncludeSuffixDot() { InfoSnapshot snapshot = InfoSnapshot.builder().name("jvm.info").build(); - assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("jvm_info"); + assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("jvm"); + SnapshotTestUtil.assertDerivedMetadata(snapshot, "jvm", "jvm.info", "jvm.info"); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptorTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptorTest.java new file mode 100644 index 000000000..b909e4ceb --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptorTest.java @@ -0,0 +1,135 @@ +package io.prometheus.metrics.model.snapshots; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.prometheus.metrics.model.registry.MetricType; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class MetricFamilyDescriptorTest { + + @Test + void counterDescriptorDerivesMetadata() { + MetricFamilyDescriptor descriptor = + MetricFamilyDescriptor.counter("events_total") + .help("help") + .unit(Unit.SECONDS) + .labelNames(Arrays.asList("method", "status")) + .build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.COUNTER); + assertThat(descriptor.getPrometheusName()).isEqualTo("events_seconds"); + assertThat(descriptor.getLabelNames()).containsExactly("method", "status"); + assertThat(descriptor.getMetadata().getName()).isEqualTo("events_seconds"); + assertThat(descriptor.getMetadata().getExpositionBaseName()).isEqualTo("events_total_seconds"); + assertThat(descriptor.getMetadata().getOriginalName()).isEqualTo("events_total"); + } + + @Test + void infoDescriptorDerivesMetadata() { + MetricFamilyDescriptor descriptor = + MetricFamilyDescriptor.info("jvm_info").help("JVM info").labelName("vendor").build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.INFO); + assertThat(descriptor.getPrometheusName()).isEqualTo("jvm"); + assertThat(descriptor.getLabelNames()).containsExactly("vendor"); + assertThat(descriptor.getMetadata().getName()).isEqualTo("jvm"); + assertThat(descriptor.getMetadata().getExpositionBaseName()).isEqualTo("jvm_info"); + assertThat(descriptor.getMetadata().getOriginalName()).isEqualTo("jvm_info"); + } + + @Test + void gaugeDescriptorKeepsLiteralName() { + MetricFamilyDescriptor descriptor = MetricFamilyDescriptor.gauge("test_total").build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.GAUGE); + assertThat(descriptor.getPrometheusName()).isEqualTo("test_total"); + assertThat(descriptor.getMetadata().getExpositionBaseName()).isEqualTo("test_total"); + assertThat(descriptor.getMetadata().getOriginalName()).isEqualTo("test_total"); + } + + @Test + void histogramDescriptorKeepsLiteralName() { + MetricFamilyDescriptor descriptor = + MetricFamilyDescriptor.histogram("request_duration_seconds") + .help("Request duration") + .labelName("method") + .build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.HISTOGRAM); + assertThat(descriptor.getPrometheusName()).isEqualTo("request_duration_seconds"); + assertThat(descriptor.getLabelNames()).containsExactly("method"); + } + + @Test + void summaryDescriptorKeepsLiteralName() { + MetricFamilyDescriptor descriptor = + MetricFamilyDescriptor.summary("request_size_bytes") + .help("Request size") + .labelName("method") + .build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.SUMMARY); + assertThat(descriptor.getPrometheusName()).isEqualTo("request_size_bytes"); + assertThat(descriptor.getLabelNames()).containsExactly("method"); + } + + @Test + void stateSetDescriptorKeepsLiteralName() { + MetricFamilyDescriptor descriptor = + MetricFamilyDescriptor.stateSet("feature_flags").help("Flags").labelName("service").build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.STATESET); + assertThat(descriptor.getPrometheusName()).isEqualTo("feature_flags"); + assertThat(descriptor.getLabelNames()).containsExactly("service"); + } + + @Test + void unknownDescriptorKeepsLiteralName() { + MetricFamilyDescriptor descriptor = + MetricFamilyDescriptor.unknown("vendor_metric").help("Vendor metric").build(); + + assertThat(descriptor.getType()).isEqualTo(MetricType.UNKNOWN); + assertThat(descriptor.getPrometheusName()).isEqualTo("vendor_metric"); + } + + @Test + void genericFactoryUsesTypedBuilderSemanticsForAllKinds() { + MetricFamilyDescriptor counter = + MetricFamilyDescriptor.of(MetricType.COUNTER, "http_requests_total").build(); + MetricFamilyDescriptor gauge = + MetricFamilyDescriptor.of(MetricType.GAUGE, "queue_depth").build(); + MetricFamilyDescriptor histogram = + MetricFamilyDescriptor.of(MetricType.HISTOGRAM, "request_duration_seconds").build(); + MetricFamilyDescriptor summary = + MetricFamilyDescriptor.of(MetricType.SUMMARY, "request_size_bytes").build(); + MetricFamilyDescriptor info = MetricFamilyDescriptor.of(MetricType.INFO, "build_info").build(); + MetricFamilyDescriptor stateSet = + MetricFamilyDescriptor.of(MetricType.STATESET, "feature_flags").build(); + MetricFamilyDescriptor unknown = + MetricFamilyDescriptor.of(MetricType.UNKNOWN, "vendor_metric").build(); + + assertThat(counter.getPrometheusName()).isEqualTo("http_requests"); + assertThat(gauge.getPrometheusName()).isEqualTo("queue_depth"); + assertThat(histogram.getPrometheusName()).isEqualTo("request_duration_seconds"); + assertThat(summary.getPrometheusName()).isEqualTo("request_size_bytes"); + assertThat(info.getPrometheusName()).isEqualTo("build"); + assertThat(stateSet.getPrometheusName()).isEqualTo("feature_flags"); + assertThat(unknown.getPrometheusName()).isEqualTo("vendor_metric"); + } + + @Test + void infoDescriptorRejectsUnit() { + assertThatThrownBy(() -> MetricFamilyDescriptor.info("jvm_info").unit(Unit.SECONDS)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Info metric cannot have a unit."); + } + + @Test + void stateSetDescriptorRejectsUnit() { + assertThatThrownBy(() -> MetricFamilyDescriptor.stateSet("feature_flags").unit(Unit.SECONDS)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("State set metric cannot have a unit."); + } +} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java index 8a8a7f93b..de75eeaeb 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java @@ -14,4 +14,11 @@ public static void assertMetadata( assertThat(snapshot.getMetadata().getUnit()).isNull(); } } + + public static void assertDerivedMetadata( + MetricSnapshot snapshot, String name, String expositionBaseName, String originalName) { + assertThat(snapshot.getMetadata().getName()).isEqualTo(name); + assertThat(snapshot.getMetadata().getExpositionBaseName()).isEqualTo(expositionBaseName); + assertThat(snapshot.getMetadata().getOriginalName()).isEqualTo(originalName); + } }