From f96c48d6d79d6a5b86eda57e73ca22303c7fe1d8 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 May 2026 12:14:53 +0000 Subject: [PATCH 1/3] feat: add typed metric family descriptors Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/CounterSnapshot.java | 8 + .../metrics/model/snapshots/InfoSnapshot.java | 8 + .../snapshots/MetricFamilyDescriptor.java | 257 ++++++++++++++++++ .../snapshots/MetricMetadataSupport.java | 52 ++++ .../model/snapshots/MetricSnapshot.java | 8 +- .../model/snapshots/CounterSnapshotTest.java | 12 +- .../model/snapshots/GaugeSnapshotTest.java | 6 +- .../model/snapshots/InfoSnapshotTest.java | 8 +- .../snapshots/MetricFamilyDescriptorTest.java | 74 +++++ .../model/snapshots/SnapshotTestUtil.java | 7 + 10 files changed, 430 insertions(+), 10 deletions(-) create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptor.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java create mode 100644 prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptorTest.java 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..ae20a3ca3 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptor.java @@ -0,0 +1,257 @@ +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) { + this.type = type; + this.metadata = metadata; + this.labelNames = Collections.unmodifiableSet(new LinkedHashSet<>(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/snapshots/CounterSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java index 16a324323..2baec7079 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,16 @@ 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..8a87d40d9 --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptorTest.java @@ -0,0 +1,74 @@ +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 genericFactoryUsesTypedBuilderSemantics() { + MetricFamilyDescriptor counter = MetricFamilyDescriptor.of(MetricType.COUNTER, "http_requests_total").build(); + MetricFamilyDescriptor info = MetricFamilyDescriptor.of(MetricType.INFO, "build_info").build(); + + assertThat(counter.getPrometheusName()).isEqualTo("http_requests"); + assertThat(info.getPrometheusName()).isEqualTo("build"); + } + + @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); + } } From a1be08d51a344b6fc28a8c24e4a4f7c596337cce Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 May 2026 12:15:11 +0000 Subject: [PATCH 2/3] style: apply formatter fixes Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/CounterSnapshotTest.java | 6 ++++-- .../metrics/model/snapshots/MetricFamilyDescriptorTest.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) 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 2baec7079..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 @@ -100,10 +100,12 @@ void testTotalSuffixPresent() { @Test void testCounterUnitDerivedFromTypedBuilder() { - CounterSnapshot snapshot = CounterSnapshot.builder().name("test_total").unit(Unit.SECONDS).build(); + 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"); + 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/MetricFamilyDescriptorTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricFamilyDescriptorTest.java index 8a87d40d9..e3f8a8576 100644 --- 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 @@ -51,7 +51,8 @@ void gaugeDescriptorKeepsLiteralName() { @Test void genericFactoryUsesTypedBuilderSemantics() { - MetricFamilyDescriptor counter = MetricFamilyDescriptor.of(MetricType.COUNTER, "http_requests_total").build(); + MetricFamilyDescriptor counter = + MetricFamilyDescriptor.of(MetricType.COUNTER, "http_requests_total").build(); MetricFamilyDescriptor info = MetricFamilyDescriptor.of(MetricType.INFO, "build_info").build(); assertThat(counter.getPrometheusName()).isEqualTo("http_requests"); From 64933d2580124cf30f6c30eddc2d4898f42a144c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 May 2026 12:25:11 +0000 Subject: [PATCH 3/3] test: cover typed family descriptor variants Signed-off-by: Gregor Zeitlinger --- .../snapshots/MetricFamilyDescriptorTest.java | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) 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 index e3f8a8576..b909e4ceb 100644 --- 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 @@ -50,13 +50,73 @@ void gaugeDescriptorKeepsLiteralName() { } @Test - void genericFactoryUsesTypedBuilderSemantics() { + 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