From 5445c1d045a1fd536050bcfc27fb0b21d4b2eac8 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Fri, 3 Jan 2025 12:16:35 -0300 Subject: [PATCH 001/106] Add UTF-8 support in metric and label names Signed-off-by: Federico Torres --- .../client/it/common/ExporterTest.java | 2 +- .../metrics/config/NamingProperties.java | 43 ++ .../metrics/config/PrometheusProperties.java | 9 +- .../config/PrometheusPropertiesLoader.java | 5 +- .../metrics/core/metrics/HistogramTest.java | 9 +- .../metrics/core/metrics/InfoTest.java | 3 + .../common/PrometheusScrapeHandler.java | 6 +- .../exporter/pushgateway/PushGateway.java | 4 + .../ExpositionFormatsTest.java | 196 +++++++- .../metrics/expositionformats/NameType.java | 6 + .../OpenMetricsTextFormatWriter.java | 59 +-- .../PrometheusTextFormatWriter.java | 57 +-- .../expositionformats/TextFormatUtil.java | 39 +- .../caffeine/CacheMetricsCollectorTest.java | 9 +- .../dropwizard/DropwizardExportsTest.java | 3 + .../dropwizard5/DropwizardExportsTest.java | 3 + .../labels/CustomLabelMapperTest.java | 3 + .../guava/CacheMetricsCollectorTest.java | 9 +- .../metrics/instrumentation/jvm/TestUtil.java | 4 + prometheus-metrics-model/pom.xml | 8 + .../model/snapshots/EscapingScheme.java | 77 +++ .../metrics/model/snapshots/Labels.java | 2 +- .../model/snapshots/MetricMetadata.java | 2 +- .../model/snapshots/PrometheusNaming.java | 465 +++++++++++++++++- .../model/snapshots/ValidationScheme.java | 13 + .../model/snapshots/PrometheusNamingTest.java | 360 ++++++++++++++ .../src/test/resources/prometheus.properties | 1 + .../bridge/SimpleclientCollectorTest.java | 4 + 28 files changed, 1278 insertions(+), 123 deletions(-) create mode 100644 prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java create mode 100644 prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java create mode 100644 prometheus-metrics-model/src/test/resources/prometheus.properties diff --git a/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java b/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java index 7f8d84715..bf7845dfe 100644 --- a/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java +++ b/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java @@ -23,7 +23,7 @@ import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; -public abstract class ExporterTest { +public abstract class ExporterTest { private final GenericContainer sampleAppContainer; private final Volume sampleAppVolume; protected final String sampleApp; diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java new file mode 100644 index 000000000..ba91ea774 --- /dev/null +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java @@ -0,0 +1,43 @@ +package io.prometheus.metrics.config; + +import java.util.Map; + +public class NamingProperties { + + private static final String VALIDATION_SCHEME = "validationScheme"; + private final String validationScheme; + + private NamingProperties(String validation) { + this.validationScheme = validation; + } + + public String getValidationScheme() { + return validationScheme; + } + + static NamingProperties load(String prefix, Map properties) throws PrometheusPropertiesException { + String validationScheme = Util.loadString(prefix + "." + VALIDATION_SCHEME, properties); + return new NamingProperties(validationScheme); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String validationScheme; + + private Builder() {} + + public Builder validation(String validationScheme) { + this.validationScheme = validationScheme; + return this; + } + + public NamingProperties build() { + return new NamingProperties(validationScheme); + } + } + +} \ No newline at end of file diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index da31fe8cc..40b69718e 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -20,6 +20,7 @@ public class PrometheusProperties { private final ExporterHttpServerProperties exporterHttpServerProperties; private final ExporterOpenTelemetryProperties exporterOpenTelemetryProperties; private final ExporterPushgatewayProperties exporterPushgatewayProperties; + private final NamingProperties namingProperties; /** * Get the properties instance. When called for the first time, {@code get()} loads the properties @@ -44,7 +45,8 @@ public PrometheusProperties( ExporterFilterProperties exporterFilterProperties, ExporterHttpServerProperties httpServerConfig, ExporterPushgatewayProperties pushgatewayProperties, - ExporterOpenTelemetryProperties otelConfig) { + ExporterOpenTelemetryProperties otelConfig, + NamingProperties namingProperties) { this.defaultMetricsProperties = defaultMetricsProperties; this.metricProperties.putAll(metricProperties); this.exemplarProperties = exemplarProperties; @@ -53,6 +55,7 @@ public PrometheusProperties( this.exporterHttpServerProperties = httpServerConfig; this.exporterPushgatewayProperties = pushgatewayProperties; this.exporterOpenTelemetryProperties = otelConfig; + this.namingProperties = namingProperties; } /** @@ -95,4 +98,8 @@ public ExporterPushgatewayProperties getExporterPushgatewayProperties() { public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() { return exporterOpenTelemetryProperties; } + + public NamingProperties getNamingProperties() { + return namingProperties; + } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java index a847a8dba..af280df9a 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; +import java.rmi.Naming; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -42,6 +43,7 @@ public static PrometheusProperties load(Map externalProperties) ExporterPushgatewayProperties.load(properties); ExporterOpenTelemetryProperties exporterOpenTelemetryProperties = ExporterOpenTelemetryProperties.load(properties); + NamingProperties namingProperties = NamingProperties.load("io.prometheus.naming", properties); validateAllPropertiesProcessed(properties); return new PrometheusProperties( defaultMetricsProperties, @@ -51,7 +53,8 @@ public static PrometheusProperties load(Map externalProperties) exporterFilterProperties, exporterHttpServerProperties, exporterPushgatewayProperties, - exporterOpenTelemetryProperties); + exporterOpenTelemetryProperties, + namingProperties); } // This will remove entries from properties when they are processed. diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index fbd89ea15..515573bf9 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.core.metrics; import static io.prometheus.metrics.core.metrics.TestUtil.assertExemplarEquals; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; @@ -11,12 +12,7 @@ import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_29_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; -import io.prometheus.metrics.model.snapshots.ClassicHistogramBucket; -import io.prometheus.metrics.model.snapshots.Exemplar; -import io.prometheus.metrics.model.snapshots.Exemplars; -import io.prometheus.metrics.model.snapshots.HistogramSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.tracer.common.SpanContext; import io.prometheus.metrics.tracer.initializer.SpanContextSupplier; import java.io.ByteArrayOutputStream; @@ -946,6 +942,7 @@ public void testDefaults() throws IOException { // text ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, true); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, MetricSnapshots.of(snapshot)); assertThat(out).hasToString(expectedTextFormat); } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index 54f9b6032..7a0cd73cc 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.core.metrics; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -7,6 +8,7 @@ import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_29_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.Unit; @@ -121,6 +123,7 @@ public void testConstLabelsDuplicate2() { private void assertTextFormat(String expected, Info info) throws IOException { OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(outputStream, MetricSnapshots.of(info.collect())); String result = outputStream.toString(StandardCharsets.UTF_8.name()); if (!result.contains(expected)) { diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index f978a9b36..d7943c831 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -6,6 +6,7 @@ import io.prometheus.metrics.expositionformats.ExpositionFormats; import io.prometheus.metrics.model.registry.MetricNameFilter; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -19,6 +20,8 @@ import java.util.function.Predicate; import java.util.zip.GZIPOutputStream; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; + /** Prometheus scrape endpoint. */ public class PrometheusScrapeHandler { @@ -54,12 +57,13 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { try { PrometheusHttpRequest request = exchange.getRequest(); MetricSnapshots snapshots = scrape(request); + String acceptHeader = request.getHeader("Accept"); + nameEscapingScheme = EscapingScheme.fromAcceptHeader(acceptHeader); if (writeDebugResponse(snapshots, exchange)) { return; } ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream(lastResponseSize.get() + 1024); - String acceptHeader = request.getHeader("Accept"); ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeader); writer.write(responseBuffer, snapshots); lastResponseSize.set(responseBuffer.size()); diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 6c89185f1..076c337b8 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.exporter.pushgateway; import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; @@ -11,6 +12,8 @@ import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -200,6 +203,7 @@ private void doRequest(PrometheusRegistry registry, String method) throws IOExce try { if (!method.equals("DELETE")) { OutputStream outputStream = connection.getOutputStream(); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(outputStream, registry.scrape()); outputStream.flush(); outputStream.close(); diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index de0791599..9e95eb1df 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -1,33 +1,22 @@ package io.prometheus.metrics.expositionformats; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_29_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; -import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.Exemplar; -import io.prometheus.metrics.model.snapshots.Exemplars; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.HistogramSnapshot; -import io.prometheus.metrics.model.snapshots.InfoSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.Quantiles; -import io.prometheus.metrics.model.snapshots.StateSetSnapshot; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.Unit; -import io.prometheus.metrics.model.snapshots.UnknownSnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; + import org.junit.jupiter.api.Test; class ExpositionFormatsTest { @@ -205,6 +194,7 @@ public void testCounterComplete() throws IOException { + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("service_time_seconds") @@ -240,6 +230,7 @@ public void testCounterMinimal() throws IOException { String prometheusText = "# TYPE my_counter_total counter\n" + "my_counter_total 1.1\n"; String prometheusProtobuf = "name: \"my_counter_total\" type: COUNTER metric { counter { value: 1.1 } }"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("my_counter") @@ -277,6 +268,7 @@ public void testCounterWithDots() throws IOException { + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("my.request.count") @@ -344,6 +336,7 @@ public void testGaugeComplete() throws IOException { + "timestamp_ms: 1672850585820 " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() .name("disk_usage_ratio") @@ -381,6 +374,7 @@ public void testGaugeMinimal() throws IOException { "# TYPE temperature_centigrade gauge\n" + "temperature_centigrade 22.3\n"; String prometheusProtobuf = "name: \"temperature_centigrade\" type: GAUGE metric { gauge { value: 22.3 } }"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() .name("temperature_centigrade") @@ -426,6 +420,7 @@ public void testGaugeWithDots() throws IOException { + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() .name("my.temperature.celsius") @@ -445,6 +440,38 @@ public void testGaugeWithDots() throws IOException { assertPrometheusProtobuf(prometheusProtobuf, gauge); } + @Test + public void testGaugeUTF8() throws IOException { + String prometheusText = + "# HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing\n" + + "# TYPE \"gauge.name\" gauge\n" + + "{\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf\n" + + "{\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + + GaugeSnapshot gauge = GaugeSnapshot.builder() + .name("gauge.name") + .help("gauge\ndoc\nstr\"ing") + .dataPoint(GaugeDataPointSnapshot.builder() + .value(Double.POSITIVE_INFINITY) + .labels(Labels.builder() + .label("name.1", "val with\nnew line") + .label("name*2", "val with \\backslash and \"quotes\"") + .build()) + .build()) + .dataPoint(GaugeDataPointSnapshot.builder() + .value(3.14e42) + .labels(Labels.builder() + .label("name.1", "Björn") + .label("name*2", "佖佥") + .build()) + .build()) + .build(); + assertPrometheusText(prometheusText, gauge); + + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testSummaryComplete() throws IOException { String openMetricsText = @@ -693,6 +720,7 @@ public void testSummaryComplete() throws IOException { + "timestamp_ms: 1672850585820 " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("http_request_duration_seconds") @@ -764,6 +792,7 @@ public void testSummaryWithoutQuantiles() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -796,6 +825,7 @@ public void testSummaryNoCountAndSum() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -826,6 +856,7 @@ public void testSummaryJustCount() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -853,6 +884,7 @@ public void testSummaryJustSum() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -869,6 +901,7 @@ public void testSummaryJustSum() throws IOException { public void testSummaryEmptyData() throws IOException { // SummaryData can be present but empty (no count, no sum, no quantiles). // This should be treated like no data is present. + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -906,6 +939,7 @@ public void testSummaryEmptyAndNonEmpty() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -959,6 +993,7 @@ public void testSummaryWithDots() throws IOException { + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("my.request.duration.seconds") @@ -1243,6 +1278,7 @@ public void testClassicHistogramComplete() throws Exception { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("response_size_bytes") @@ -1311,6 +1347,7 @@ public void testClassicHistogramMinimal() throws Exception { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("request_latency_seconds") @@ -1357,6 +1394,7 @@ public void testClassicHistogramCountAndSum() throws Exception { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("request_latency_seconds") @@ -1630,6 +1668,7 @@ public void testClassicGaugeHistogramComplete() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot gaugeHistogram = HistogramSnapshot.builder() .gaugeHistogram(true) @@ -1699,6 +1738,7 @@ public void testClassicGaugeHistogramMinimal() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot gaugeHistogram = HistogramSnapshot.builder() .gaugeHistogram(true) @@ -1748,6 +1788,7 @@ public void testClassicGaugeHistogramCountAndSum() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot gaugeHistogram = HistogramSnapshot.builder() .gaugeHistogram(true) @@ -1815,6 +1856,7 @@ public void testClassicHistogramWithDots() throws IOException { + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("my.request.duration.seconds") @@ -2086,6 +2128,7 @@ public void testNativeHistogramComplete() throws IOException { "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot nativeHistogram = HistogramSnapshot.builder() .name("response_size_bytes") @@ -2172,6 +2215,7 @@ public void testNativeHistogramMinimal() throws IOException { + "} " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot nativeHistogram = HistogramSnapshot.builder() .name("latency_seconds") @@ -2235,6 +2279,7 @@ public void testNativeHistogramWithDots() throws IOException { + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("my.request.duration.seconds") @@ -2271,6 +2316,7 @@ public void testInfo() throws IOException { "# HELP version_info version information\n" + "# TYPE version_info gauge\n" + "version_info{version=\"1.2.3\"} 1\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; InfoSnapshot info = InfoSnapshot.builder() .name("version") @@ -2307,6 +2353,7 @@ public void testInfoWithDots() throws IOException { + "gauge { value: 1.0 } " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; InfoSnapshot info = InfoSnapshot.builder() .name("jvm.status") @@ -2354,6 +2401,7 @@ public void testStateSetComplete() throws IOException { + "state{env=\"prod\",state=\"state2\"} 1 " + scrapeTimestamp2s + "\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; StateSetSnapshot stateSet = StateSetSnapshot.builder() .name("state") @@ -2388,6 +2436,7 @@ public void testStateSetMinimal() throws IOException { + "# EOF\n"; String prometheus = "# TYPE state gauge\n" + "state{state=\"a\"} 1\n" + "state{state=\"bb\"} 0\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; StateSetSnapshot stateSet = StateSetSnapshot.builder() .name("state") @@ -2431,6 +2480,7 @@ public void testStateSetWithDots() throws IOException { + "gauge { value: 0.0 } " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; StateSetSnapshot stateSet = StateSetSnapshot.builder() .name("my.application.state") @@ -2484,6 +2534,7 @@ public void testUnknownComplete() throws IOException { + "my_special_thing_bytes{env=\"prod\"} 0.7 " + scrapeTimestamp2s + "\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; UnknownSnapshot unknown = UnknownSnapshot.builder() .name("my_special_thing_bytes") @@ -2516,6 +2567,7 @@ public void testUnknownComplete() throws IOException { public void testUnknownMinimal() throws IOException { String openMetrics = "# TYPE other unknown\n" + "other 22.3\n" + "# EOF\n"; String prometheus = "# TYPE other untyped\n" + "other 22.3\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; UnknownSnapshot unknown = UnknownSnapshot.builder() .name("other") @@ -2557,6 +2609,7 @@ public void testUnknownWithDots() throws IOException { + "untyped { value: 0.7 } " + "}"; // @formatter:on + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; UnknownSnapshot unknown = UnknownSnapshot.builder() .name(PrometheusNaming.sanitizeMetricName("some.unknown.metric", Unit.BYTES)) @@ -2587,6 +2640,7 @@ public void testHelpEscape() throws IOException { "# HELP test_total Some text and \\n some \" escaping\n" + "# TYPE test_total counter\n" + "test_total 1.0\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("test") @@ -2607,6 +2661,7 @@ public void testLabelValueEscape() throws IOException { + "# EOF\n"; String prometheus = "# TYPE test_total counter\n" + "test_total{a=\"x\",b=\"escaping\\\" example \\n \"} 1.0\n"; + PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("test") @@ -2621,9 +2676,112 @@ public void testLabelValueEscape() throws IOException { assertPrometheusText(prometheus, counter); } + @Test + public void testFindWriter() { + EscapingScheme oldDefault = nameEscapingScheme; + nameEscapingScheme = EscapingScheme.UNDERSCORE_ESCAPING; + ExpositionFormats expositionFormats = ExpositionFormats.init(); + + // delimited format + String acceptHeaderValue = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited"; + String expectedFmt = "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores"; + EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + // plain text format + acceptHeaderValue = "text/plain;version=0.0.4"; + expectedFmt = "text/plain; version=0.0.4; charset=utf-8; escaping=underscores"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + // delimited format UTF-8 + acceptHeaderValue = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited; escaping=allow-utf-8"; + expectedFmt = "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + nameEscapingScheme = EscapingScheme.VALUE_ENCODING_ESCAPING; + + // OM format, no version + acceptHeaderValue = "application/openmetrics-text"; + expectedFmt = "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + // OM format, 0.0.1 version + acceptHeaderValue = "application/openmetrics-text;version=0.0.1; escaping=underscores"; + expectedFmt = "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + // plain text format + acceptHeaderValue = "text/plain;version=0.0.4"; + expectedFmt = "text/plain; version=0.0.4; charset=utf-8; escaping=values"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + // plain text format UTF-8 + acceptHeaderValue = "text/plain;version=0.0.4; escaping=allow-utf-8"; + expectedFmt = "text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + // delimited format UTF-8 + acceptHeaderValue = "text/plain;version=0.0.4; escaping=allow-utf-8"; + expectedFmt = "text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8"; + escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + + nameEscapingScheme = oldDefault; + } + + @Test + public void testWrite() throws IOException { + ByteArrayOutputStream buff = new ByteArrayOutputStream(new AtomicInteger(2 << 9).get() + 1024); + ExpositionFormats expositionFormats = ExpositionFormats.init(); + UnknownSnapshot unknown = UnknownSnapshot.builder() + .name("foo_metric") + .dataPoint(UnknownDataPointSnapshot.builder() + .value(1.234) + .build()) + .build(); + + String acceptHeaderValue = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited"; + nameEscapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + ExpositionFormatWriter protoWriter = expositionFormats.findWriter(acceptHeaderValue); + + protoWriter.write(buff, MetricSnapshots.of(unknown)); + byte[] out = buff.toByteArray(); + assertThat(out.length).isNotEqualTo(0); + + buff.reset(); + + acceptHeaderValue = "text/plain; version=0.0.4; charset=utf-8"; + nameEscapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + ExpositionFormatWriter textWriter = expositionFormats.findWriter(acceptHeaderValue); + + textWriter.write(buff, MetricSnapshots.of(unknown)); + out = buff.toByteArray(); + assertThat(out.length).isNotEqualTo(0); + + String expected = "# TYPE foo_metric untyped\n" + + "foo_metric 1.234\n"; + + assertThat(new String(out, UTF_8)).hasToString(expected); + } + private void assertOpenMetricsText(String expected, MetricSnapshot snapshot) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, false); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, MetricSnapshots.of(snapshot)); assertThat(out).hasToString(expected); } @@ -2632,6 +2790,7 @@ private void assertOpenMetricsTextWithExemplarsOnAllTimeSeries( String expected, MetricSnapshot snapshot) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, MetricSnapshots.of(snapshot)); assertThat(out).hasToString(expected); } @@ -2640,6 +2799,7 @@ private void assertOpenMetricsTextWithoutCreated(String expected, MetricSnapshot throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, false); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, MetricSnapshots.of(snapshot)); assertThat(out).hasToString(expected); } @@ -2647,6 +2807,7 @@ private void assertOpenMetricsTextWithoutCreated(String expected, MetricSnapshot private void assertPrometheusText(String expected, MetricSnapshot snapshot) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); PrometheusTextFormatWriter writer = new PrometheusTextFormatWriter(true); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, MetricSnapshots.of(snapshot)); assertThat(out).hasToString(expected); } @@ -2655,6 +2816,7 @@ private void assertPrometheusTextWithoutCreated(String expected, MetricSnapshot throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); PrometheusTextFormatWriter writer = new PrometheusTextFormatWriter(false); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, MetricSnapshots.of(snapshot)); assertThat(out).hasToString(expected); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java new file mode 100644 index 000000000..5041e5323 --- /dev/null +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java @@ -0,0 +1,6 @@ +package io.prometheus.metrics.expositionformats; + +public enum NameType { + Metric, + Label +} diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 06850a3d8..e21efa59a 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -1,34 +1,16 @@ package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedLabelValue; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeTimestamp; +import io.prometheus.metrics.model.snapshots.*; -import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.DistributionDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.Exemplar; -import io.prometheus.metrics.model.snapshots.Exemplars; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.HistogramSnapshot; -import io.prometheus.metrics.model.snapshots.InfoSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.Quantile; -import io.prometheus.metrics.model.snapshots.StateSetSnapshot; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; -import io.prometheus.metrics.model.snapshots.UnknownSnapshot; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.List; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; + /** * Write the OpenMetrics text format as defined on https://openmetrics.io. @@ -66,7 +48,8 @@ public String getContentType() { @Override public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException { OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); - for (MetricSnapshot snapshot : metricSnapshots) { + for (MetricSnapshot s : metricSnapshots) { + MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, nameEscapingScheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { writeCounter(writer, (CounterSnapshot) snapshot); @@ -239,7 +222,7 @@ private void writeStateSet(OutputStreamWriter writer, StateSetSnapshot snapshot) } writer.write(data.getLabels().getPrometheusName(j)); writer.write("=\""); - writeEscapedLabelValue(writer, data.getLabels().getValue(j)); + writeEscapedString(writer, data.getLabels().getValue(j)); writer.write("\""); } if (!data.getLabels().isEmpty()) { @@ -247,7 +230,7 @@ private void writeStateSet(OutputStreamWriter writer, StateSetSnapshot snapshot) } writer.write(metadata.getPrometheusName()); writer.write("=\""); - writeEscapedLabelValue(writer, data.getName(i)); + writeEscapedString(writer, data.getName(i)); writer.write("\"} "); if (data.isTrue(i)) { writer.write("1"); @@ -325,12 +308,18 @@ private void writeNameAndLabels( String additionalLabelName, double additionalLabelValue) throws IOException { - writer.write(name); - if (suffix != null) { - writer.write(suffix); + boolean metricInsideBraces = false; + // If the name does not pass the legacy validity check, we must put the + // metric name inside the braces. + if (PrometheusNaming.validateLegacyMetricName(name) != null) { + metricInsideBraces = true; + writer.write('{'); } + writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { - writeLabels(writer, labels, additionalLabelName, additionalLabelValue); + writeLabels(writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces); + } else if (metricInsideBraces) { + writer.write('}'); } writer.write(' '); } @@ -343,7 +332,7 @@ private void writeScrapeTimestampAndExemplar( } if (exemplar != null) { writer.write(" # "); - writeLabels(writer, exemplar.getLabels(), null, 0); + writeLabels(writer, exemplar.getLabels(), null, 0, false); writer.write(' '); writeDouble(writer, exemplar.getValue()); if (exemplar.hasTimestamp()) { @@ -357,22 +346,22 @@ private void writeScrapeTimestampAndExemplar( private void writeMetadata(OutputStreamWriter writer, String typeName, MetricMetadata metadata) throws IOException { writer.write("# TYPE "); - writer.write(metadata.getPrometheusName()); + writeName(writer, metadata.getPrometheusName(), NameType.Metric); writer.write(' '); writer.write(typeName); writer.write('\n'); if (metadata.getUnit() != null) { writer.write("# UNIT "); - writer.write(metadata.getPrometheusName()); + writeName(writer, metadata.getPrometheusName(), NameType.Metric); writer.write(' '); - writeEscapedLabelValue(writer, metadata.getUnit().toString()); + writeEscapedString(writer, metadata.getUnit().toString()); writer.write('\n'); } if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); - writer.write(metadata.getPrometheusName()); + writeName(writer, metadata.getPrometheusName(), NameType.Metric); writer.write(' '); - writeEscapedLabelValue(writer, metadata.getHelp()); + writeEscapedString(writer, metadata.getHelp()); writer.write('\n'); } } 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 eb07abc73..adaaa5bb1 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 @@ -1,31 +1,16 @@ package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedLabelValue; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeTimestamp; +import io.prometheus.metrics.model.snapshots.*; -import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.HistogramSnapshot; -import io.prometheus.metrics.model.snapshots.InfoSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.Quantile; -import io.prometheus.metrics.model.snapshots.StateSetSnapshot; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; -import io.prometheus.metrics.model.snapshots.UnknownSnapshot; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; + /** * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your * Web browser. @@ -60,7 +45,8 @@ public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOEx // "unknown", "gauge", "counter", "stateset", "info", "histogram", "gaugehistogram", and // "summary". OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); - for (MetricSnapshot snapshot : metricSnapshots) { + for (MetricSnapshot s : metricSnapshots) { + MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, nameEscapingScheme); if (snapshot.getDataPoints().size() > 0) { if (snapshot instanceof CounterSnapshot) { writeCounter(writer, (CounterSnapshot) snapshot); @@ -80,7 +66,8 @@ public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOEx } } if (writeCreatedTimestamps) { - for (MetricSnapshot snapshot : metricSnapshots) { + for (MetricSnapshot s : metricSnapshots) { + MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, nameEscapingScheme); if (snapshot.getDataPoints().size() > 0) { if (snapshot instanceof CounterSnapshot) { writeCreated(writer, snapshot); @@ -272,7 +259,7 @@ private void writeStateSet(OutputStreamWriter writer, StateSetSnapshot snapshot) } writer.write(data.getLabels().getPrometheusName(j)); writer.write("=\""); - writeEscapedLabelValue(writer, data.getLabels().getValue(j)); + writeEscapedString(writer, data.getLabels().getValue(j)); writer.write("\""); } if (!data.getLabels().isEmpty()) { @@ -280,7 +267,7 @@ private void writeStateSet(OutputStreamWriter writer, StateSetSnapshot snapshot) } writer.write(metadata.getPrometheusName()); writer.write("=\""); - writeEscapedLabelValue(writer, data.getName(i)); + writeEscapedString(writer, data.getName(i)); writer.write("\"} "); if (data.isTrue(i)) { writer.write("1"); @@ -316,12 +303,18 @@ private void writeNameAndLabels( String additionalLabelName, double additionalLabelValue) throws IOException { - writer.write(name); - if (suffix != null) { - writer.write(suffix); + boolean metricInsideBraces = false; + // If the name does not pass the legacy validity check, we must put the + // metric name inside the braces. + if (PrometheusNaming.validateLegacyMetricName(name) != null) { + metricInsideBraces = true; + writer.write('{'); } + writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { - writeLabels(writer, labels, additionalLabelName, additionalLabelValue); + writeLabels(writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces); + } else if (metricInsideBraces) { + writer.write('}'); } writer.write(' '); } @@ -331,19 +324,13 @@ private void writeMetadata( throws IOException { if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); - writer.write(metadata.getPrometheusName()); - if (suffix != null) { - writer.write(suffix); - } + writeName(writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); writer.write(' '); writeEscapedHelp(writer, metadata.getHelp()); writer.write('\n'); } writer.write("# TYPE "); - writer.write(metadata.getPrometheusName()); - if (suffix != null) { - writer.write(suffix); - } + writeName(writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); writer.write(' '); writer.write(typeString); writer.write('\n'); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 54daaaa3e..c9b4ba5a9 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -1,6 +1,8 @@ package io.prometheus.metrics.expositionformats; import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.ValidationScheme; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; @@ -35,7 +37,7 @@ static void writeTimestamp(OutputStreamWriter writer, long timestampMs) throws I writer.write(Long.toString(ms)); } - static void writeEscapedLabelValue(Writer writer, String s) throws IOException { + static void writeEscapedString(Writer writer, String s) throws IOException { for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { @@ -58,16 +60,19 @@ static void writeLabels( OutputStreamWriter writer, Labels labels, String additionalLabelName, - double additionalLabelValue) + double additionalLabelValue, + boolean metricInsideBraces) throws IOException { - writer.write('{'); + if (!metricInsideBraces) { + writer.write('{'); + } for (int i = 0; i < labels.size(); i++) { - if (i > 0) { + if (i > 0 || metricInsideBraces) { writer.write(","); } - writer.write(labels.getPrometheusName(i)); + writeName(writer, labels.getPrometheusName(i), NameType.Label); writer.write("=\""); - writeEscapedLabelValue(writer, labels.getValue(i)); + writeEscapedString(writer, labels.getValue(i)); writer.write("\""); } if (additionalLabelName != null) { @@ -81,4 +86,26 @@ static void writeLabels( } writer.write('}'); } + + static void writeName(OutputStreamWriter writer, String name, NameType nameType) throws IOException { + switch (nameType) { + case Metric: + if (PrometheusNaming.isValidLegacyMetricName(name)) { + writer.write(name); + return; + } + break; + case Label: + if (PrometheusNaming.isValidLegacyLabelName(name) && PrometheusNaming.nameValidationScheme == ValidationScheme.LEGACY_VALIDATION) { + writer.write(name); + return; + } + break; + default: + throw new RuntimeException("Invalid name type requested: " + nameType); + } + writer.write('"'); + writeEscapedString(writer, name); + writer.write('"'); + } } 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 4ffb1d794..2fca65e1c 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 @@ -1,5 +1,6 @@ package io.prometheus.metrics.instrumentation.caffeine; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -11,11 +12,8 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; +import io.prometheus.metrics.model.snapshots.*; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -168,6 +166,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, registry.scrape()); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { diff --git a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java index ff658ad41..7e4765c14 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.instrumentation.dropwizard; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -7,6 +8,7 @@ import com.codahale.metrics.*; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -289,6 +291,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, _registry.scrape()); return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java index 8f99cb327..3be8ba502 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java @@ -1,11 +1,13 @@ package io.prometheus.metrics.instrumentation.dropwizard5; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Offset.offset; import io.dropwizard.metrics5.*; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.SummarySnapshot; @@ -288,6 +290,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, _registry.scrape()); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java index ac0b0a328..55af802d2 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.instrumentation.dropwizard5.labels; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -7,6 +8,7 @@ import io.dropwizard.metrics5.MetricRegistry; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.instrumentation.dropwizard5.DropwizardExports; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -206,6 +208,7 @@ private String convertToOpenMetricsFormat(MetricSnapshots snapshots) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, snapshots); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { 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 a03fcdb0c..8639ca0f0 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 @@ -1,5 +1,6 @@ package io.prometheus.metrics.instrumentation.guava; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -11,11 +12,8 @@ import com.google.common.cache.LoadingCache; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; +import io.prometheus.metrics.model.snapshots.*; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -163,6 +161,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, registry.scrape()); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java index 70a093f4b..0a2ce5f9d 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java @@ -1,16 +1,20 @@ package io.prometheus.metrics.instrumentation.jvm; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; + class TestUtil { static String convertToOpenMetricsFormat(MetricSnapshots snapshots) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, snapshots); return out.toString(StandardCharsets.UTF_8.name()); } diff --git a/prometheus-metrics-model/pom.xml b/prometheus-metrics-model/pom.xml index 4e803ee5a..215549b58 100644 --- a/prometheus-metrics-model/pom.xml +++ b/prometheus-metrics-model/pom.xml @@ -19,4 +19,12 @@ io.prometheus.metrics.model + + + + io.prometheus + prometheus-metrics-config + ${project.version} + + diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java new file mode 100644 index 000000000..4299c6283 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java @@ -0,0 +1,77 @@ +package io.prometheus.metrics.model.snapshots; + +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.ESCAPING_KEY; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; + +public enum EscapingScheme { + // NO_ESCAPING indicates that a name will not be escaped. + NO_ESCAPING("allow-utf-8"), + + // UNDERSCORE_ESCAPING replaces all legacy-invalid characters with underscores. + UNDERSCORE_ESCAPING("underscores"), + + // DOTS_ESCAPING is similar to UNDERSCORE_ESCAPING, except that dots are + // converted to `_dot_` and pre-existing underscores are converted to `__`. + DOTS_ESCAPING("dots"), + + // VALUE_ENCODING_ESCAPING prepends the name with `U__` and replaces all invalid + // characters with the Unicode value, surrounded by underscores. Single + // underscores are replaced with double underscores. + VALUE_ENCODING_ESCAPING("values"), + ; + + public final String getValue() { + return value; + } + + private final String value; + + EscapingScheme(String value) { + this.value = value; + } + + // fromAcceptHeader returns an EscapingScheme depending on the Accept header. Iff the + // header contains an escaping=allow-utf-8 term, it will select NO_ESCAPING. If a valid + // "escaping" term exists, that will be used. Otherwise, the global default will + // be returned. + public static EscapingScheme fromAcceptHeader(String acceptHeader) { + if (acceptHeader != null) { + for (String p : acceptHeader.split(";")) { + String[] toks = p.split("="); + if (toks.length != 2) { + continue; + } + String key = toks[0].trim(); + String value = toks[1].trim(); + if (key.equals(ESCAPING_KEY)) { + try { + return EscapingScheme.forString(value); + } catch (IllegalArgumentException e) { + // If the escaping parameter is unknown, ignore it. + return nameEscapingScheme; + } + } + } + } + return nameEscapingScheme; + } + + private static EscapingScheme forString(String value) { + switch(value) { + case "allow-utf-8": + return NO_ESCAPING; + case "underscores": + return UNDERSCORE_ESCAPING; + case "dots": + return DOTS_ESCAPING; + case "values": + return VALUE_ENCODING_ESCAPING; + default: + throw new IllegalArgumentException("Unknown escaping scheme: " + value); + } + } + + public String toHeaderFormat() { + return "; " + ESCAPING_KEY + "=" + value; + } +} \ No newline at end of file diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index 97cbfd43a..bb846ce68 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -119,7 +119,7 @@ public static Labels of(String[] names, String[] values) { static String[] makePrometheusNames(String[] names) { String[] prometheusNames = names; for (int i = 0; i < names.length; i++) { - if (names[i].contains(".")) { + if (names[i].contains(".") && PrometheusNaming.nameValidationScheme == ValidationScheme.LEGACY_VALIDATION) { if (prometheusNames == names) { prometheusNames = Arrays.copyOf(names, names.length); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 581cb9143..79ef32bbd 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -53,7 +53,7 @@ public MetricMetadata(String name, String help, Unit unit) { this.help = help; this.unit = unit; validate(); - this.prometheusName = name.contains(".") ? PrometheusNaming.prometheusName(name) : name; + this.prometheusName = name.contains(".") && PrometheusNaming.nameValidationScheme == ValidationScheme.LEGACY_VALIDATION ? PrometheusNaming.prometheusName(name) : name; } /** diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 5cb1604d1..8942a0672 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,7 +1,16 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.PrometheusProperties; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + /** * Utility for Prometheus Metric and Label naming. * @@ -11,12 +20,44 @@ */ public class PrometheusNaming { + /** + * nameValidationScheme determines the method of name validation to be used by + * all calls to validateMetricName() and isValidMetricName(). Setting UTF-8 mode + * in isolation from other components that don't support UTF-8 may result in + * bugs or other undefined behavior. This value is intended to be set by + * UTF-8-aware binaries as part of their startup via a properties file. + */ + public static ValidationScheme nameValidationScheme = initValidationScheme(); + + /** + * nameEscapingScheme defines the default way that names will be + * escaped when presented to systems that do not support UTF-8 names. If the + * Accept "escaping" term is specified, that will override this value. + */ + public static EscapingScheme nameEscapingScheme = EscapingScheme.VALUE_ENCODING_ESCAPING; + + /** + * ESCAPING_KEY is the key in an Accept header that defines how + * metric and label names that do not conform to the legacy character + * requirements should be escaped when being scraped by a legacy Prometheus + * system. If a system does not explicitly pass an escaping parameter in the + * Accept header, the default nameEscapingScheme will be used. + */ + public static final String ESCAPING_KEY = "escaping"; + + private static final String LOWERHEX = "0123456789abcdef"; + + private static final String METRIC_NAME_LABEL= "__name__"; + /** Legal characters for metric names, including dot. */ - private static final Pattern METRIC_NAME_PATTERN = + private static final Pattern LEGACY_METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.:][a-zA-Z0-9_.:]*$"); + private static final Pattern METRIC_NAME_PATTERN = + Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); + /** Legal characters for label names, including dot. */ - private static final Pattern LABEL_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); + private static final Pattern LEGACY_LABEL_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); /** Legal characters for unit names, including dot. */ private static final Pattern UNIT_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_.:]+$"); @@ -41,11 +82,21 @@ public class PrometheusNaming { ".total", ".created", ".bucket", ".info" }; + static ValidationScheme initValidationScheme() { + if (PrometheusProperties.get() != null && PrometheusProperties.get().getNamingProperties() != null) { + String validationScheme = PrometheusProperties.get().getNamingProperties().getValidationScheme(); + if (validationScheme != null && validationScheme.equals("utf-8")) { + return ValidationScheme.UTF_8_VALIDATION; + } + } + return ValidationScheme.LEGACY_VALIDATION; + } + /** * Test if a metric name is valid. Rules: * *
    - *
  • The name must match {@link #METRIC_NAME_PATTERN}. + *
  • The name must match {@link #LEGACY_METRIC_NAME_PATTERN}. *
  • The name MUST NOT end with one of the {@link #RESERVED_METRIC_NAME_SUFFIXES}. *
* @@ -65,25 +116,61 @@ public static boolean isValidMetricName(String name) { return validateMetricName(name) == null; } + public static String validateMetricName(String name) { + switch (nameValidationScheme) { + case LEGACY_VALIDATION: + return validateLegacyMetricName(name); + case UTF_8_VALIDATION: + if(name.isEmpty() || !StandardCharsets.UTF_8.newEncoder().canEncode(name)) { + return "The metric name contains unsupported characters"; + } + return null; + default: + throw new RuntimeException("Invalid name validation scheme requested: " + nameValidationScheme); + } + } + /** * Same as {@link #isValidMetricName(String)}, but produces an error message. * *

The name is valid if the error message is {@code null}. */ - public static String validateMetricName(String name) { + public static String validateLegacyMetricName(String name) { for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { if (name.endsWith(reservedSuffix)) { return "The metric name must not include the '" + reservedSuffix + "' suffix."; } } - if (!METRIC_NAME_PATTERN.matcher(name).matches()) { + if (!isValidLegacyMetricName(name)) { return "The metric name contains unsupported characters"; } return null; } + public static boolean isValidLegacyMetricName(String name) { + switch (nameValidationScheme) { + case LEGACY_VALIDATION: + return LEGACY_METRIC_NAME_PATTERN.matcher(name).matches(); + case UTF_8_VALIDATION: + return METRIC_NAME_PATTERN.matcher(name).matches(); + default: + throw new RuntimeException("Invalid name validation scheme requested: " + nameValidationScheme); + } + } + public static boolean isValidLabelName(String name) { - return LABEL_NAME_PATTERN.matcher(name).matches() + switch (nameValidationScheme) { + case LEGACY_VALIDATION: + return isValidLegacyLabelName(name); + case UTF_8_VALIDATION: + return StandardCharsets.UTF_8.newEncoder().canEncode(name); + default: + throw new RuntimeException("Invalid name validation scheme requested: " + nameValidationScheme); + } + } + + public static boolean isValidLegacyLabelName(String name) { + return LEGACY_LABEL_NAME_PATTERN.matcher(name).matches() && !(name.startsWith("__") || name.startsWith("._") || name.startsWith("..") @@ -226,7 +313,7 @@ public static String sanitizeUnitName(String unitName) { return sanitizedName; } - /** Returns a string that matches {@link #METRIC_NAME_PATTERN}. */ + /** Returns a string that matches {@link #LEGACY_METRIC_NAME_PATTERN}. */ private static String replaceIllegalCharsInMetricName(String name) { int length = name.length(); char[] sanitized = new char[length]; @@ -244,7 +331,7 @@ private static String replaceIllegalCharsInMetricName(String name) { return new String(sanitized); } - /** Returns a string that matches {@link #LABEL_NAME_PATTERN}. */ + /** Returns a string that matches {@link #LEGACY_LABEL_NAME_PATTERN}. */ private static String replaceIllegalCharsInLabelName(String name) { int length = name.length(); char[] sanitized = new char[length]; @@ -280,4 +367,366 @@ private static String replaceIllegalCharsInUnitName(String name) { } return new String(sanitized); } + + /** + * Escapes the given metric names and labels with the given + * escaping scheme. + */ + public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { + if (v == null) { + return null; + } + + if (scheme == EscapingScheme.NO_ESCAPING) { + return v; + } + + String outName; + + // If the name is null, copy as-is, don't try to escape. + if (v.getMetadata().getPrometheusName() == null || isValidLegacyMetricName(v.getMetadata().getPrometheusName())) { + outName = v.getMetadata().getPrometheusName(); + } else { + outName = escapeName(v.getMetadata().getPrometheusName(), scheme); + } + + List outDataPoints = new ArrayList<>(); + + for (DataPointSnapshot d : v.getDataPoints()) { + if (!metricNeedsEscaping(d)) { + outDataPoints.add(d); + continue; + } + + Labels.Builder outLabelsBuilder = Labels.builder(); + + for (Label l : d.getLabels()) { + if (METRIC_NAME_LABEL.equals(l.getName())) { + if (l.getValue() == null || isValidLegacyMetricName(l.getValue())) { + outLabelsBuilder.label(l.getName(), l.getValue()); + continue; + } + outLabelsBuilder.label(l.getName(), escapeName(l.getValue(), scheme)); + continue; + } + if (l.getName() == null || isValidLegacyMetricName(l.getName())) { + outLabelsBuilder.label(l.getName(), l.getValue()); + continue; + } + outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); + } + + Labels outLabels = outLabelsBuilder.build(); + DataPointSnapshot outDataPointSnapshot = null; + + if (v instanceof CounterSnapshot) { + outDataPointSnapshot = CounterSnapshot.CounterDataPointSnapshot.builder() + .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) + .exemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof GaugeSnapshot) { + outDataPointSnapshot = GaugeSnapshot.GaugeDataPointSnapshot.builder() + .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) + .exemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar()) + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof HistogramSnapshot) { + outDataPointSnapshot = HistogramSnapshot.HistogramDataPointSnapshot.builder() + .classicHistogramBuckets(((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) + .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) + .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) + .nativeZeroThreshold(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) + .nativeBucketsForPositiveValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForPositiveValues()) + .nativeBucketsForNegativeValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForNegativeValues()) + .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) + .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) + .exemplars(((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof SummarySnapshot) { + outDataPointSnapshot = SummarySnapshot.SummaryDataPointSnapshot.builder() + .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) + .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) + .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) + .exemplars(((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof InfoSnapshot) { + outDataPointSnapshot = InfoSnapshot.InfoDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof StateSetSnapshot) { + StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()); + for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { + builder.state(state.getName(), state.isTrue()); + } + outDataPointSnapshot = builder.build(); + } else if (v instanceof UnknownSnapshot) { + outDataPointSnapshot = UnknownSnapshot.UnknownDataPointSnapshot.builder() + .labels(outLabels) + .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) + .exemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } + + outDataPoints.add(outDataPointSnapshot); + } + + MetricSnapshot out; + + if (v instanceof CounterSnapshot) { + CounterSnapshot.Builder builder = CounterSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((CounterSnapshot.CounterDataPointSnapshot) d); + } + out = builder.build(); + } else if (v instanceof GaugeSnapshot) { + GaugeSnapshot.Builder builder = GaugeSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((GaugeSnapshot.GaugeDataPointSnapshot) d); + } + out = builder.build(); + } else if (v instanceof HistogramSnapshot) { + HistogramSnapshot.Builder builder = HistogramSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()) + .gaugeHistogram(((HistogramSnapshot) v).isGaugeHistogram()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((HistogramSnapshot.HistogramDataPointSnapshot) d); + } + out = builder.build(); + } else if (v instanceof SummarySnapshot) { + SummarySnapshot.Builder builder = SummarySnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((SummarySnapshot.SummaryDataPointSnapshot) d); + } + out = builder.build(); + } else if (v instanceof InfoSnapshot) { + InfoSnapshot.Builder builder = InfoSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((InfoSnapshot.InfoDataPointSnapshot) d); + } + out = builder.build(); + } else if (v instanceof StateSetSnapshot) { + StateSetSnapshot.Builder builder = StateSetSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((StateSetSnapshot.StateSetDataPointSnapshot) d); + } + out = builder.build(); + } else if (v instanceof UnknownSnapshot) { + UnknownSnapshot.Builder builder = UnknownSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((UnknownSnapshot.UnknownDataPointSnapshot) d); + } + out = builder.build(); + } else { + throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); + } + + return out; + } + + static boolean metricNeedsEscaping(DataPointSnapshot d) { + Labels labels = d.getLabels(); + for (Label l : labels) { + if (l.getName().equals(METRIC_NAME_LABEL) && !isValidLegacyMetricName(l.getValue())) { + return true; + } + if (!isValidLegacyMetricName(l.getName())) { + return true; + } + } + return false; + } + + /** + * Escapes the incoming name according to the provided escaping + * scheme. Depending on the rules of escaping, this may cause no change in the + * string that is returned (especially NO_ESCAPING, which by definition is a + * noop). This method does not do any validation of the name. + */ + static String escapeName(String name, EscapingScheme scheme) { + if (name.isEmpty()) { + return name; + } + StringBuilder escaped = new StringBuilder(); + switch (scheme) { + case NO_ESCAPING: + return name; + case UNDERSCORE_ESCAPING: + if (isValidLegacyMetricName(name)) { + return name; + } + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (isValidLegacyChar(c, i)) { + escaped.append(c); + } else { + escaped.append('_'); + } + } + return escaped.toString(); + case DOTS_ESCAPING: + // Do not early return for legacy valid names, we still escape underscores. + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c == '_') { + escaped.append("__"); + } else if (c == '.') { + escaped.append("_dot_"); + } else if (isValidLegacyChar(c, i)) { + escaped.append(c); + } else { + escaped.append('_'); + } + } + return escaped.toString(); + case VALUE_ENCODING_ESCAPING: + if (isValidLegacyMetricName(name)) { + return name; + } + escaped.append("U__"); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (isValidLegacyChar(c, i)) { + escaped.append(c); + } else if (!isValidUTF8Char(c)) { + escaped.append("_FFFD_"); + } else if (c < 0x100) { + escaped.append('_'); + for (int s = 4; s >= 0; s -= 4) { + escaped.append(LOWERHEX.charAt((c >> s) & 0xF)); + } + escaped.append('_'); + } else { + escaped.append('_'); + for (int s = 12; s >= 0; s -= 4) { + escaped.append(LOWERHEX.charAt((c >> s) & 0xF)); + } + escaped.append('_'); + } + } + return escaped.toString(); + default: + throw new IllegalArgumentException("Invalid escaping scheme " + scheme); + } + } + + /** + * Unescapes the incoming name according to the provided escaping + * scheme if possible. Some schemes are partially or totally non-roundtripable. + * If any error is encountered, returns the original input. + */ + @SuppressWarnings("IncrementInForLoopAndHeader") + static String unescapeName(String name, EscapingScheme scheme) { + if (name.isEmpty()) { + return name; + } + switch (scheme) { + case NO_ESCAPING: + return name; + case UNDERSCORE_ESCAPING: + // It is not possible to unescape from underscore replacement. + return name; + case DOTS_ESCAPING: + name = name.replaceAll("_dot_", "."); + name = name.replaceAll("__", "_"); + return name; + case VALUE_ENCODING_ESCAPING: + Matcher matcher = Pattern.compile("U__").matcher(name); + if (matcher.find()) { + String escapedName = name.substring(matcher.end()); + StringBuilder unescaped = new StringBuilder(); + TOP: + for (int i = 0; i < escapedName.length(); i++) { + // All non-underscores are treated normally. + if (escapedName.charAt(i) != '_') { + unescaped.append(escapedName.charAt(i)); + continue; + } + i++; + if (i >= escapedName.length()) { + return name; + } + // A double underscore is a single underscore. + if (escapedName.charAt(i) == '_') { + unescaped.append('_'); + continue; + } + // We think we are in a UTF-8 code, process it. + long utf8Val = 0; + for (int j = 0; i < escapedName.length(); j++) { + // This is too many characters for a UTF-8 value. + if (j > 4) { + return name; + } + // Found a closing underscore, convert to a char, check validity, and append. + if (escapedName.charAt(i) == '_') { + char utf8Char = (char) utf8Val; + if (!isValidUTF8Char(utf8Char)) { + return name; + } + unescaped.append(utf8Char); + continue TOP; + } + char r = Character.toLowerCase(escapedName.charAt(i)); + utf8Val *= 16; + if (r >= '0' && r <= '9') { + utf8Val += r - '0'; + } else if (r >= 'a' && r <= 'f') { + utf8Val += r - 'a' + 10; + } else { + return name; + } + i++; + } + // Didn't find closing underscore, invalid. + return name; + } + return unescaped.toString(); + } else { + return name; + } + default: + throw new IllegalArgumentException("Invalid escaping scheme " + scheme); + } + } + + static boolean isValidLegacyChar(char c, int i) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || (c >= '0' && c <= '9' && i > 0); + } + + private static boolean isValidUTF8Char(char b) { + return ((b < MIN_HIGH_SURROGATE || b > MAX_LOW_SURROGATE) && + (b < 0xFFFE)); + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java new file mode 100644 index 000000000..4c547bb12 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java @@ -0,0 +1,13 @@ +package io.prometheus.metrics.model.snapshots; + +// ValidationScheme is an enum for determining how metric and label names will +// be validated by this library. +public enum ValidationScheme { + // LEGACY_VALIDATION is a setting that requires that metric and label names + // conform to the original character requirements. + LEGACY_VALIDATION, + + // UTF_8_VALIDATION only requires that metric and label names be valid UTF-8 + // strings. + UTF_8_VALIDATION +} \ No newline at end of file diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index fad55e0ac..2a9ef1f6f 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -6,10 +6,13 @@ import org.junit.jupiter.api.Test; +import java.util.Optional; + class PrometheusNamingTest { @Test public void testSanitizeMetricName() { + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; assertThat(prometheusName(sanitizeMetricName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeMetricName("___ab.:c0"))).isEqualTo("___ab__c0"); assertThat(sanitizeMetricName("my_prefix/my_metric")).isEqualTo("my_prefix_my_metric"); @@ -24,6 +27,7 @@ public void testSanitizeMetricName() { @Test public void testSanitizeMetricNameWithUnit() { + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; assertThat(prometheusName(sanitizeMetricName("0abc.def", Unit.RATIO))) .isEqualTo("_abc_def_" + Unit.RATIO); assertThat(prometheusName(sanitizeMetricName("___ab.:c0", Unit.RATIO))) @@ -42,6 +46,7 @@ public void testSanitizeMetricNameWithUnit() { @Test public void testSanitizeLabelName() { + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); @@ -96,4 +101,359 @@ public void testEmptyUnitName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> sanitizeUnitName("")); } + + @Test + public void testMetricNameIsValid() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + assertThat(validateMetricName("Avalid_23name")).isNull(); + assertThat(validateMetricName("_Avalid_23name")).isNull(); + assertThat(validateMetricName("1valid_23name")).isNull(); + assertThat(validateMetricName("avalid_23name")).isNull(); + assertThat(validateMetricName("Ava:lid_23name")).isNull(); + assertThat(validateMetricName("a lid_23name")).isNull(); + assertThat(validateMetricName(":leading_colon")).isNull(); + assertThat(validateMetricName("colon:in:the:middle")).isNull(); + assertThat(validateMetricName("")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName("a\ud800z")).isEqualTo("The metric name contains unsupported characters"); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + assertThat(validateMetricName("Avalid_23name")).isNull(); + assertThat(validateMetricName("_Avalid_23name")).isNull(); + assertThat(validateMetricName("1valid_23name")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName("avalid_23name")).isNull(); + assertThat(validateMetricName("Ava:lid_23name")).isNull(); + assertThat(validateMetricName("a lid_23name")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName(":leading_colon")).isNull(); + assertThat(validateMetricName("colon:in:the:middle")).isNull(); + assertThat(validateMetricName("")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName("a\ud800z")).isEqualTo("The metric name contains unsupported characters"); + } + + @Test + public void testLabelNameIsValid() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + assertThat(isValidLabelName("Avalid_23name")).isTrue(); + assertThat(isValidLabelName("_Avalid_23name")).isTrue(); + assertThat(isValidLabelName("1valid_23name")).isTrue(); + assertThat(isValidLabelName("avalid_23name")).isTrue(); + assertThat(isValidLabelName("Ava:lid_23name")).isTrue(); + assertThat(isValidLabelName("a lid_23name")).isTrue(); + assertThat(isValidLabelName(":leading_colon")).isTrue(); + assertThat(isValidLabelName("colon:in:the:middle")).isTrue(); + assertThat(isValidLabelName("a\ud800z")).isFalse(); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + assertThat(isValidLabelName("Avalid_23name")).isTrue(); + assertThat(isValidLabelName("_Avalid_23name")).isTrue(); + assertThat(isValidLabelName("1valid_23name")).isFalse(); + assertThat(isValidLabelName("avalid_23name")).isTrue(); + assertThat(isValidLabelName("Ava:lid_23name")).isFalse(); + assertThat(isValidLabelName("a lid_23name")).isFalse(); + assertThat(isValidLabelName(":leading_colon")).isFalse(); + assertThat(isValidLabelName("colon:in:the:middle")).isFalse(); + assertThat(isValidLabelName("a\ud800z")).isFalse(); + } + + @Test + public void testEscapeName() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + + // empty string + String got = escapeName("", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo(""); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo(""); + + got = escapeName("", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo(""); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo(""); + + got = escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo(""); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo(""); + + // legacy valid name + got = escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("no:escaping_required"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("no:escaping_required"); + + got = escapeName("no:escaping_required", EscapingScheme.DOTS_ESCAPING); + // Dots escaping will escape underscores even though it's not strictly + // necessary for compatibility. + assertThat(got).isEqualTo("no:escaping__required"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("no:escaping_required"); + + got = escapeName("no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("no:escaping_required"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("no:escaping_required"); + + // name with dots + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); + + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); + + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); + + // name with dots and colon + got = escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("http_status:sum"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("http_status:sum"); + + got = escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("http_dot_status:sum"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("http.status:sum"); + + got = escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__http_2e_status:sum"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("http.status:sum"); + + // name with unicode characters > 0x100 + got = escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("__"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("__"); + + got = escapeName("花火", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("__"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + // Dots-replacement does not know the difference between two replaced + // characters and a single underscore. + assertThat(got).isEqualTo("_"); + + got = escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U___82b1__706b_"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("花火"); + + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + + @Test + public void testValueUnescapeErrors() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + String got; + + // empty string + got = unescapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo(""); + + // basic case, no error + got = unescapeName("U__no:unescapingrequired", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("no:unescapingrequired"); + + // capitals ok, no error + got = unescapeName("U__capitals_2E_ok", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("capitals.ok"); + + // underscores, no error + got = unescapeName("U__underscores__doubled__", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("underscores_doubled_"); + + // invalid single underscore + got = unescapeName("U__underscores_doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__underscores_doubled_"); + + // invalid single underscore, 2 + got = unescapeName("U__underscores__doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__underscores__doubled_"); + + // giant fake UTF-8 code + got = unescapeName("U__my__hack_2e_attempt_872348732fabdabbab_", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__my__hack_2e_attempt_872348732fabdabbab_"); + + // trailing UTF-8 + got = unescapeName("U__my__hack_2e", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__my__hack_2e"); + + // invalid UTF-8 value + got = unescapeName("U__bad__utf_2eg_", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__bad__utf_2eg_"); + + // surrogate UTF-8 value + got = unescapeName("U__bad__utf_D900_", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__bad__utf_D900_"); + + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + + @Test + public void testEscapeMetricSnapshotEmpty() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got.getMetadata().getName()).isEqualTo("empty"); + assertThat(original.getMetadata().getName()).isEqualTo("empty"); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + + @Test + public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + MetricSnapshot original = CounterSnapshot.builder() + .name("my_metric") + .help("some help text") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "my_metric") + .label("some_label", "labelvalue") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("some_label", "labelvalue") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("some_label", "labelvalue") + .build()); + + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + + @Test + public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + MetricSnapshot original = CounterSnapshot.builder() + .name("my_metric") + .help("some help text") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "my_metric") + .label("some.label", "labelvalue") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("U__some_2e_label", "labelvalue") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("some.label", "labelvalue") + .build()); + + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + + @Test + public void testEscapeMetricSnapshotCounterEscapingNeeded() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + MetricSnapshot original = CounterSnapshot.builder() + .name("my.metric") + .help("some help text") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "my.metric") + .label("some?label", "label??value") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("U__my_2e_metric"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "U__my_2e_metric") + .label("U__some_3f_label", "label??value") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("my.metric"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my.metric") + .label("some?label", "label??value") + .build()); + + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + + @Test + public void testEscapeMetricSnapshotGaugeEscapingNeeded() { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + MetricSnapshot original = GaugeSnapshot.builder() + .name("unicode.and.dots.花火") + .help("some help text") + .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "unicode.and.dots.花火") + .label("some_label", "label??value") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.DOTS_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("unicode_dot_and_dot_dots_dot___"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + GaugeSnapshot.GaugeDataPointSnapshot data = (GaugeSnapshot.GaugeDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "unicode_dot_and_dot_dots_dot___") + .label("some_label", "label??value") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("unicode.and.dots.花火"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (GaugeSnapshot.GaugeDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "unicode.and.dots.花火") + .label("some_label", "label??value") + .build()); + + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } } diff --git a/prometheus-metrics-model/src/test/resources/prometheus.properties b/prometheus-metrics-model/src/test/resources/prometheus.properties new file mode 100644 index 000000000..4ce7f8487 --- /dev/null +++ b/prometheus-metrics-model/src/test/resources/prometheus.properties @@ -0,0 +1 @@ +io.prometheus.naming.validationScheme=legacy diff --git a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java index 89cae65ba..28d68b2e1 100644 --- a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java +++ b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.simpleclient.bridge; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import io.prometheus.client.Collector; @@ -20,6 +21,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; + +import io.prometheus.metrics.model.snapshots.EscapingScheme; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -266,6 +269,7 @@ private String origOpenMetrics() throws IOException { private String newOpenMetrics() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, false); + nameEscapingScheme = EscapingScheme.NO_ESCAPING; writer.write(out, newRegistry.scrape()); return out.toString(StandardCharsets.UTF_8.name()); } From 7939abaa51ade094cbba519cdfd2c2661b3d5814 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Tue, 7 Jan 2025 18:06:44 -0300 Subject: [PATCH 002/106] Add grouping key escaping for URLs in Pushgateway exporter Signed-off-by: Federico Torres --- .../exporter/pushgateway/PushGateway.java | 8 +- .../exporter/pushgateway/PushGatewayTest.java | 124 ++++++++++++++++++ .../model/snapshots/PrometheusNaming.java | 2 +- 3 files changed, 129 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 076c337b8..0a4fce0fb 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,7 +1,7 @@ package io.prometheus.metrics.exporter.pushgateway; import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.*; import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; @@ -434,11 +434,11 @@ private URL makeUrl(ExporterPushgatewayProperties properties) if (groupingKey != null) { for (Map.Entry entry : groupingKey.entrySet()) { if (entry.getValue().isEmpty()) { - url += "/" + entry.getKey() + "@base64/="; + url += "/" + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + "@base64/="; } else if (entry.getValue().contains("/")) { - url += "/" + entry.getKey() + "@base64/" + base64url(entry.getValue()); + url += "/" + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + "@base64/" + base64url(entry.getValue()); } else { - url += "/" + entry.getKey() + "/" + URLEncoder.encode(entry.getValue(), "UTF-8"); + url += "/" + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + "/" + URLEncoder.encode(entry.getValue(), "UTF-8"); } } } diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 27617913b..268c1c49e 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.exporter.pushgateway; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameValidationScheme; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockserver.model.HttpRequest.request; @@ -11,6 +12,8 @@ import java.lang.reflect.Field; import java.net.InetAddress; import java.net.URL; + +import io.prometheus.metrics.model.snapshots.ValidationScheme; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -141,6 +144,23 @@ public void testPushWithGroupingKey() throws IOException { pg.push(); } + @Test + public void testPushWithEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testPushWithMultiGroupingKey() throws IOException { mockServerClient @@ -157,6 +177,24 @@ public void testPushWithMultiGroupingKey() throws IOException { pg.push(); } + @Test + public void testPushWithMultiEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .groupingKey("l.2", "v2") + .build(); + pg.push(); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testPushWithEmptyLabelGroupingKey() throws IOException { mockServerClient @@ -205,6 +243,23 @@ public void testPushCollectorWithGroupingKey() throws IOException { pg.push(gauge); } + @Test + public void testPushCollectorWithEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(gauge); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testPushAdd() throws IOException { mockServerClient @@ -244,6 +299,23 @@ public void testPushAddWithGroupingKey() throws IOException { pg.pushAdd(); } + @Test + public void testPushAddWithEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testPushAddCollectorWithGroupingKey() throws IOException { mockServerClient @@ -259,6 +331,23 @@ public void testPushAddCollectorWithGroupingKey() throws IOException { pg.pushAdd(gauge); } + @Test + public void testPushAddCollectorWithEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(gauge); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testDelete() throws IOException { mockServerClient @@ -283,6 +372,22 @@ public void testDeleteWithGroupingKey() throws IOException { pg.delete(); } + @Test + public void testDeleteWithEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + mockServerClient + .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.delete(); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } + @Test public void testInstanceIpGroupingKey() throws IOException { String ip = InetAddress.getLocalHost().getHostAddress(); @@ -299,4 +404,23 @@ public void testInstanceIpGroupingKey() throws IOException { .build(); pg.delete(); } + + @Test + public void testInstanceIpEscapedGroupingKey() throws IOException { + nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + String ip = InetAddress.getLocalHost().getHostAddress(); + assertThat(ip).isNotEmpty(); + mockServerClient + .when(request().withMethod("DELETE").withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .instanceIpGroupingKey() + .build(); + pg.delete(); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 8942a0672..ceb037187 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -574,7 +574,7 @@ static boolean metricNeedsEscaping(DataPointSnapshot d) { * string that is returned (especially NO_ESCAPING, which by definition is a * noop). This method does not do any validation of the name. */ - static String escapeName(String name, EscapingScheme scheme) { + public static String escapeName(String name, EscapingScheme scheme) { if (name.isEmpty()) { return name; } From a593d4ee409bc6d17fa25db0249f4ed5a4b68776 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Tue, 21 Jan 2025 13:11:25 -0300 Subject: [PATCH 003/106] Fix escaping bugs Signed-off-by: Federico Torres --- .../client/it/common/ExporterTest.java | 2 +- .../model/snapshots/PrometheusNaming.java | 83 +++++++++---------- .../model/snapshots/PrometheusNamingTest.java | 56 ++++++++++++- 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java b/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java index 123b667a7..93b5b088a 100644 --- a/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java +++ b/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java @@ -23,7 +23,7 @@ import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; -public abstract class ExporterTest { +public abstract class ExporterTest { private final GenericContainer sampleAppContainer; private final Volume sampleAppVolume; protected final String sampleApp; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index ceb037187..8511beb71 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -8,8 +8,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.lang.Character.MAX_LOW_SURROGATE; -import static java.lang.Character.MIN_HIGH_SURROGATE; +import static java.lang.Character.*; /** * Utility for Prometheus Metric and Label naming. @@ -45,8 +44,6 @@ public class PrometheusNaming { */ public static final String ESCAPING_KEY = "escaping"; - private static final String LOWERHEX = "0123456789abcdef"; - private static final String METRIC_NAME_LABEL= "__name__"; /** Legal characters for metric names, including dot. */ @@ -586,28 +583,30 @@ public static String escapeName(String name, EscapingScheme scheme) { if (isValidLegacyMetricName(name)) { return name; } - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); if (isValidLegacyChar(c, i)) { - escaped.append(c); + escaped.appendCodePoint(c); } else { escaped.append('_'); } + i += Character.charCount(c); } return escaped.toString(); case DOTS_ESCAPING: // Do not early return for legacy valid names, we still escape underscores. - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); if (c == '_') { escaped.append("__"); } else if (c == '.') { escaped.append("_dot_"); } else if (isValidLegacyChar(c, i)) { - escaped.append(c); + escaped.appendCodePoint(c); } else { - escaped.append('_'); + escaped.append("__"); } + i += Character.charCount(c); } return escaped.toString(); case VALUE_ENCODING_ESCAPING: @@ -615,25 +614,20 @@ public static String escapeName(String name, EscapingScheme scheme) { return name; } escaped.append("U__"); - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (isValidLegacyChar(c, i)) { - escaped.append(c); + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); } else if (!isValidUTF8Char(c)) { escaped.append("_FFFD_"); - } else if (c < 0x100) { - escaped.append('_'); - for (int s = 4; s >= 0; s -= 4) { - escaped.append(LOWERHEX.charAt((c >> s) & 0xF)); - } - escaped.append('_'); } else { escaped.append('_'); - for (int s = 12; s >= 0; s -= 4) { - escaped.append(LOWERHEX.charAt((c >> s) & 0xF)); - } + escaped.append(Integer.toHexString(c)); escaped.append('_'); } + i += Character.charCount(c); } return escaped.toString(); default: @@ -666,11 +660,12 @@ static String unescapeName(String name, EscapingScheme scheme) { if (matcher.find()) { String escapedName = name.substring(matcher.end()); StringBuilder unescaped = new StringBuilder(); - TOP: - for (int i = 0; i < escapedName.length(); i++) { + for (int i = 0; i < escapedName.length(); ) { // All non-underscores are treated normally. - if (escapedName.charAt(i) != '_') { - unescaped.append(escapedName.charAt(i)); + int c = escapedName.codePointAt(i); + if (c != '_') { + unescaped.appendCodePoint(c); + i += Character.charCount(c); continue; } i++; @@ -678,25 +673,29 @@ static String unescapeName(String name, EscapingScheme scheme) { return name; } // A double underscore is a single underscore. - if (escapedName.charAt(i) == '_') { + if (escapedName.codePointAt(i) == '_') { unescaped.append('_'); + i++; continue; } // We think we are in a UTF-8 code, process it. - long utf8Val = 0; + int utf8Val = 0; + boolean foundClosingUnderscore = false; for (int j = 0; i < escapedName.length(); j++) { // This is too many characters for a UTF-8 value. - if (j > 4) { + if (j >= 6) { return name; } // Found a closing underscore, convert to a char, check validity, and append. - if (escapedName.charAt(i) == '_') { - char utf8Char = (char) utf8Val; - if (!isValidUTF8Char(utf8Char)) { + if (escapedName.codePointAt(i) == '_') { + //char utf8Char = (char) utf8Val; + foundClosingUnderscore = true; + if (!isValidUTF8Char(utf8Val)) { return name; } - unescaped.append(utf8Char); - continue TOP; + unescaped.appendCodePoint(utf8Val); + i++; + break; } char r = Character.toLowerCase(escapedName.charAt(i)); utf8Val *= 16; @@ -709,8 +708,9 @@ static String unescapeName(String name, EscapingScheme scheme) { } i++; } - // Didn't find closing underscore, invalid. - return name; + if (!foundClosingUnderscore) { + return name; + } } return unescaped.toString(); } else { @@ -721,12 +721,11 @@ static String unescapeName(String name, EscapingScheme scheme) { } } - static boolean isValidLegacyChar(char c, int i) { + static boolean isValidLegacyChar(int c, int i) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || (c >= '0' && c <= '9' && i > 0); } - private static boolean isValidUTF8Char(char b) { - return ((b < MIN_HIGH_SURROGATE || b > MAX_LOW_SURROGATE) && - (b < 0xFFFE)); + private static boolean isValidUTF8Char(int c) { + return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 2a9ef1f6f..587fa801f 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -206,6 +206,22 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); + // name with dots and underscore + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); + + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); + + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); + // name with dots and colon got = escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING); assertThat(got).isEqualTo("http_status:sum"); @@ -222,6 +238,22 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("http.status:sum"); + // name with spaces and emoji + got = escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("label_with__"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("label_with__"); + + got = escapeName("label with 😱", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("label__with____"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("label_with__"); + + got = escapeName("label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__label_20_with_20__1f631_"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("label with 😱"); + // name with unicode characters > 0x100 got = escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING); assertThat(got).isEqualTo("__"); @@ -229,17 +261,33 @@ public void testEscapeName() { assertThat(got).isEqualTo("__"); got = escapeName("花火", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("__"); + assertThat(got).isEqualTo("____"); got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); // Dots-replacement does not know the difference between two replaced // characters and a single underscore. - assertThat(got).isEqualTo("_"); + assertThat(got).isEqualTo("__"); got = escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U___82b1__706b_"); got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("花火"); + // name with spaces and edge-case value + got = escapeName("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("label_with__"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("label_with__"); + + got = escapeName("label with Ā", EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("label__with____"); + got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(got).isEqualTo("label_with__"); + + got = escapeName("label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__label_20_with_20__100_"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("label with Ā"); + nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; } @@ -435,13 +483,13 @@ public void testEscapeMetricSnapshotGaugeEscapingNeeded() { .build(); MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.DOTS_ESCAPING); - assertThat(got.getMetadata().getName()).isEqualTo("unicode_dot_and_dot_dots_dot___"); + assertThat(got.getMetadata().getName()).isEqualTo("unicode_dot_and_dot_dots_dot_____"); assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); assertThat(got.getDataPoints().size()).isEqualTo(1); GaugeSnapshot.GaugeDataPointSnapshot data = (GaugeSnapshot.GaugeDataPointSnapshot) got.getDataPoints().get(0); assertThat(data.getValue()).isEqualTo(34.2); assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "unicode_dot_and_dot_dots_dot___") + .label("__name__", "unicode_dot_and_dot_dots_dot_____") .label("some_label", "label??value") .build()); assertThat(original.getMetadata().getName()).isEqualTo("unicode.and.dots.花火"); From f9ce9425acf02097955f033f8ab36326c886023f Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 10 Jul 2025 10:10:26 -0300 Subject: [PATCH 004/106] Fix tests Signed-off-by: Federico Torres --- .../expositionformats/TextFormatUtil.java | 2 +- .../ExpositionFormatsTest.java | 31 ++++++++----------- .../expositionformats/TextFormatUtilTest.java | 2 +- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 5f83627d3..23589f7aa 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -49,7 +49,7 @@ static void writeOpenMetricsTimestamp(Writer writer, long timestampMs) throws IO writer.write(Long.toString(ms)); } - static void writeEscapedLabelValue(Writer writer, String s) throws IOException { + static void writeEscapedString(Writer writer, String s) throws IOException { // optimize for the common case where no escaping is needed int start = 0; // #indexOf is a vectorized intrinsic diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 314ba7cba..1733c6cff 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -464,10 +464,12 @@ public void testGaugeWithDots() throws IOException { @Test public void testGaugeUTF8() throws IOException { String prometheusText = - "# HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing\n" + - "# TYPE \"gauge.name\" gauge\n" + - "{\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf\n" + - "{\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42\n"; + """ + # HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing + # TYPE \"gauge.name\" gauge + {\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf + {\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42 + """; PrometheusNaming.nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() @@ -2872,26 +2874,19 @@ public void testWrite() throws IOException { .build()) .build(); - String acceptHeaderValue = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited"; - nameEscapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); - ExpositionFormatWriter protoWriter = expositionFormats.findWriter(acceptHeaderValue); - - protoWriter.write(buff, MetricSnapshots.of(unknown)); - byte[] out = buff.toByteArray(); - assertThat(out.length).isNotEqualTo(0); - - buff.reset(); - - acceptHeaderValue = "text/plain; version=0.0.4; charset=utf-8"; + String acceptHeaderValue = "text/plain; version=0.0.4; charset=utf-8"; nameEscapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); ExpositionFormatWriter textWriter = expositionFormats.findWriter(acceptHeaderValue); textWriter.write(buff, MetricSnapshots.of(unknown)); - out = buff.toByteArray(); + byte[] out = buff.toByteArray(); assertThat(out.length).isNotEqualTo(0); - String expected = "# TYPE foo_metric untyped\n" + - "foo_metric 1.234\n"; + String expected = + """ + # TYPE foo_metric untyped + foo_metric 1.234 + """; assertThat(new String(out, UTF_8)).hasToString(expected); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java index 3f3558160..dbb707f51 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java @@ -19,7 +19,7 @@ void writeEscapedLabelValue() throws IOException { private static String escape(String s) throws IOException { StringWriter writer = new StringWriter(); - TextFormatUtil.writeEscapedLabelValue(writer, s); + TextFormatUtil.writeEscapedString(writer, s); return writer.toString(); } From a64dda8242603d88775835b7f30c16e12789da09 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Tue, 15 Jul 2025 10:36:28 -0300 Subject: [PATCH 005/106] Make statics in PrometheusNaming final Signed-off-by: Federico Torres --- .../benchmarks/TextFormatUtilBenchmark.java | 9 +-- .../metrics/core/metrics/HistogramTest.java | 4 +- .../metrics/core/metrics/InfoTest.java | 4 +- .../common/PrometheusScrapeHandler.java | 15 +++-- .../exporter/pushgateway/PushGateway.java | 3 +- .../PrometheusProtobufWriterImpl.java | 12 ++-- .../ExpositionFormatWriter.java | 7 ++- .../OpenMetricsTextFormatWriter.java | 5 +- .../PrometheusProtobufWriter.java | 9 +-- .../PrometheusTextFormatWriter.java | 7 +-- .../ExpositionFormatWriterTest.java | 3 +- .../ExpositionFormatsTest.java | 57 +++++-------------- .../PrometheusProtobufWriterTest.java | 5 +- .../caffeine/CacheMetricsCollectorTest.java | 4 +- .../model/snapshots/EscapingScheme.java | 6 +- .../model/snapshots/PrometheusNaming.java | 8 +-- .../model/snapshots/PrometheusNamingTest.java | 51 +++++++---------- 17 files changed, 84 insertions(+), 125 deletions(-) diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java index dcacf9a76..59c370a3a 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java @@ -3,6 +3,7 @@ import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; import io.prometheus.metrics.model.snapshots.Labels; @@ -69,14 +70,14 @@ public OutputStream openMetricsWriteToByteArray(WriterState writerState) throws // avoid growing the array ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); - OPEN_METRICS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS); + OPEN_METRICS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); return byteArrayOutputStream; } @Benchmark public OutputStream openMetricsWriteToNull() throws IOException { OutputStream nullOutputStream = NullOutputStream.INSTANCE; - OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS); + OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); return nullOutputStream; } @@ -85,14 +86,14 @@ public OutputStream prometheusWriteToByteArray(WriterState writerState) throws I // avoid growing the array ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); - PROMETHEUS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS); + PROMETHEUS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); return byteArrayOutputStream; } @Benchmark public OutputStream prometheusWriteToNull() throws IOException { OutputStream nullOutputStream = NullOutputStream.INSTANCE; - PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS); + PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); return nullOutputStream; } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index a014a1ddf..f53e82bfa 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -1,7 +1,6 @@ package io.prometheus.metrics.core.metrics; import static io.prometheus.metrics.core.metrics.TestUtil.assertExemplarEquals; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; @@ -946,8 +945,7 @@ public void testDefaults() throws IOException { // text ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, true); - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, MetricSnapshots.of(snapshot)); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); assertThat(out).hasToString(expectedTextFormat); } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index f9f58c277..b21288d5f 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.core.metrics; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -128,8 +127,7 @@ public void testConstLabelsDuplicate2() { private void assertTextFormat(String expected, Info info) throws IOException { OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(outputStream, MetricSnapshots.of(info.collect())); + writer.write(outputStream, MetricSnapshots.of(info.collect()), EscapingScheme.NO_ESCAPING); String result = outputStream.toString(StandardCharsets.UTF_8.name()); if (!result.contains(expected)) { throw new AssertionError(expected + " is not contained in the following output:\n" + result); diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index d7943c831..32efcd456 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -20,7 +20,6 @@ import java.util.function.Predicate; import java.util.zip.GZIPOutputStream; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; /** Prometheus scrape endpoint. */ public class PrometheusScrapeHandler { @@ -58,14 +57,14 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { PrometheusHttpRequest request = exchange.getRequest(); MetricSnapshots snapshots = scrape(request); String acceptHeader = request.getHeader("Accept"); - nameEscapingScheme = EscapingScheme.fromAcceptHeader(acceptHeader); - if (writeDebugResponse(snapshots, exchange)) { + EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeader); + if (writeDebugResponse(snapshots, escapingScheme, exchange)) { return; } ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream(lastResponseSize.get() + 1024); ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeader); - writer.write(responseBuffer, snapshots); + writer.write(responseBuffer, snapshots, escapingScheme); lastResponseSize.set(responseBuffer.size()); PrometheusHttpResponse response = exchange.getResponse(); response.setHeader("Content-Type", writer.getContentType()); @@ -140,7 +139,7 @@ private MetricSnapshots scrape(PrometheusHttpRequest request) { } } - private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExchange exchange) + private boolean writeDebugResponse(MetricSnapshots snapshots, EscapingScheme escapingScheme, PrometheusHttpExchange exchange) throws IOException { String debugParam = exchange.getRequest().getParameter("debug"); PrometheusHttpResponse response = exchange.getResponse(); @@ -152,14 +151,14 @@ private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExch OutputStream body = response.sendHeadersAndGetBody(responseStatus, 0); switch (debugParam) { case "openmetrics": - expositionFormats.getOpenMetricsTextFormatWriter().write(body, snapshots); + expositionFormats.getOpenMetricsTextFormatWriter().write(body, snapshots, escapingScheme); break; case "text": - expositionFormats.getPrometheusTextFormatWriter().write(body, snapshots); + expositionFormats.getPrometheusTextFormatWriter().write(body, snapshots, escapingScheme); break; case "prometheus-protobuf": String debugString = - expositionFormats.getPrometheusProtobufWriter().toDebugString(snapshots); + expositionFormats.getPrometheusProtobufWriter().toDebugString(snapshots, escapingScheme); body.write(debugString.getBytes(StandardCharsets.UTF_8)); break; default: diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 48b218ac7..91cd2ea65 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -209,8 +209,7 @@ private void doRequest(PrometheusRegistry registry, String method) throws IOExce try { if (!method.equals("DELETE")) { OutputStream outputStream = connection.getOutputStream(); - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(outputStream, registry.scrape()); + writer.write(outputStream, registry.scrape(), EscapingScheme.NO_ESCAPING); outputStream.flush(); outputStream.close(); } diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index e3e450a28..e24ba310f 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -9,6 +9,7 @@ import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; @@ -18,6 +19,7 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; @@ -38,9 +40,10 @@ public String getContentType() { } @Override - public String toDebugString(MetricSnapshots metricSnapshots) { + public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) { StringBuilder stringBuilder = new StringBuilder(); - for (MetricSnapshot snapshot : metricSnapshots) { + for (MetricSnapshot s : metricSnapshots) { + MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { stringBuilder.append(TextFormat.printer().printToString(convert(snapshot))); } @@ -49,8 +52,9 @@ public String toDebugString(MetricSnapshots metricSnapshots) { } @Override - public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException { - for (MetricSnapshot snapshot : metricSnapshots) { + public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException { + for (MetricSnapshot s : metricSnapshots) { + MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { convert(snapshot).writeDelimitedTo(out); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java index b472af0e1..56c7da238 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.expositionformats; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -9,12 +10,12 @@ public interface ExpositionFormatWriter { boolean accepts(String acceptHeader); /** Text formats use UTF-8 encoding. */ - void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException; + void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException; - default String toDebugString(MetricSnapshots metricSnapshots) { + default String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { - write(out, metricSnapshots); + write(out, metricSnapshots, escapingScheme); return out.toString("UTF-8"); } catch (IOException e) { throw new RuntimeException(e); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 7ba2c4f91..3900faefc 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -28,7 +28,6 @@ import java.util.List; import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; /** * Write the OpenMetrics text format as defined on writer.write(null, null)) + assertThatCode(() -> writer.write(null, null, EscapingScheme.NO_ESCAPING)) .isInstanceOf(UnsupportedOperationException.class); } @Test void toDebugString() { - assertThatCode(() -> writer.toDebugString(null)) + assertThatCode(() -> writer.toDebugString(null, EscapingScheme.NO_ESCAPING)) .isInstanceOf(UnsupportedOperationException.class); } } 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 85abcc1fd..2d80602bd 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 @@ -1,6 +1,5 @@ package io.prometheus.metrics.instrumentation.caffeine; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -313,8 +312,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, registry.scrape()); + writer.write(out, registry.scrape(), EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java index 4299c6283..a5752956b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java @@ -1,7 +1,7 @@ package io.prometheus.metrics.model.snapshots; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.ESCAPING_KEY; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.DEFAULT_ESCAPING_SCHEME; public enum EscapingScheme { // NO_ESCAPING indicates that a name will not be escaped. @@ -48,12 +48,12 @@ public static EscapingScheme fromAcceptHeader(String acceptHeader) { return EscapingScheme.forString(value); } catch (IllegalArgumentException e) { // If the escaping parameter is unknown, ignore it. - return nameEscapingScheme; + return DEFAULT_ESCAPING_SCHEME; } } } } - return nameEscapingScheme; + return DEFAULT_ESCAPING_SCHEME; } private static EscapingScheme forString(String value) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 8511beb71..e6ee51fc9 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -26,14 +26,12 @@ public class PrometheusNaming { * bugs or other undefined behavior. This value is intended to be set by * UTF-8-aware binaries as part of their startup via a properties file. */ - public static ValidationScheme nameValidationScheme = initValidationScheme(); + public static final ValidationScheme nameValidationScheme = initValidationScheme(); /** - * nameEscapingScheme defines the default way that names will be - * escaped when presented to systems that do not support UTF-8 names. If the - * Accept "escaping" term is specified, that will override this value. + * Default escaping scheme for names when not specified. */ - public static EscapingScheme nameEscapingScheme = EscapingScheme.VALUE_ENCODING_ESCAPING; + public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = EscapingScheme.VALUE_ENCODING_ESCAPING; /** * ESCAPING_KEY is the key in an Accept header that defines how diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 587fa801f..70d4ba1fb 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -12,7 +12,7 @@ class PrometheusNamingTest { @Test public void testSanitizeMetricName() { - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + // nameValidationScheme is now final and initialized from properties assertThat(prometheusName(sanitizeMetricName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeMetricName("___ab.:c0"))).isEqualTo("___ab__c0"); assertThat(sanitizeMetricName("my_prefix/my_metric")).isEqualTo("my_prefix_my_metric"); @@ -27,7 +27,7 @@ public void testSanitizeMetricName() { @Test public void testSanitizeMetricNameWithUnit() { - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + assertThat(prometheusName(sanitizeMetricName("0abc.def", Unit.RATIO))) .isEqualTo("_abc_def_" + Unit.RATIO); assertThat(prometheusName(sanitizeMetricName("___ab.:c0", Unit.RATIO))) @@ -46,7 +46,7 @@ public void testSanitizeMetricNameWithUnit() { @Test public void testSanitizeLabelName() { - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); @@ -104,18 +104,7 @@ public void testEmptyUnitName() { @Test public void testMetricNameIsValid() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - assertThat(validateMetricName("Avalid_23name")).isNull(); - assertThat(validateMetricName("_Avalid_23name")).isNull(); - assertThat(validateMetricName("1valid_23name")).isNull(); - assertThat(validateMetricName("avalid_23name")).isNull(); - assertThat(validateMetricName("Ava:lid_23name")).isNull(); - assertThat(validateMetricName("a lid_23name")).isNull(); - assertThat(validateMetricName(":leading_colon")).isNull(); - assertThat(validateMetricName("colon:in:the:middle")).isNull(); - assertThat(validateMetricName("")).isEqualTo("The metric name contains unsupported characters"); - assertThat(validateMetricName("a\ud800z")).isEqualTo("The metric name contains unsupported characters"); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + // nameValidationScheme is now final and initialized from properties (defaults to LEGACY_VALIDATION) assertThat(validateMetricName("Avalid_23name")).isNull(); assertThat(validateMetricName("_Avalid_23name")).isNull(); assertThat(validateMetricName("1valid_23name")).isEqualTo("The metric name contains unsupported characters"); @@ -130,7 +119,7 @@ public void testMetricNameIsValid() { @Test public void testLabelNameIsValid() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties assertThat(isValidLabelName("Avalid_23name")).isTrue(); assertThat(isValidLabelName("_Avalid_23name")).isTrue(); assertThat(isValidLabelName("1valid_23name")).isTrue(); @@ -140,7 +129,7 @@ public void testLabelNameIsValid() { assertThat(isValidLabelName(":leading_colon")).isTrue(); assertThat(isValidLabelName("colon:in:the:middle")).isTrue(); assertThat(isValidLabelName("a\ud800z")).isFalse(); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + assertThat(isValidLabelName("Avalid_23name")).isTrue(); assertThat(isValidLabelName("_Avalid_23name")).isTrue(); assertThat(isValidLabelName("1valid_23name")).isFalse(); @@ -154,7 +143,7 @@ public void testLabelNameIsValid() { @Test public void testEscapeName() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties // empty string String got = escapeName("", EscapingScheme.UNDERSCORE_ESCAPING); @@ -288,12 +277,12 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("label with Ā"); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } @Test public void testValueUnescapeErrors() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties String got; // empty string @@ -336,22 +325,22 @@ public void testValueUnescapeErrors() { got = unescapeName("U__bad__utf_D900_", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U__bad__utf_D900_"); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } @Test public void testEscapeMetricSnapshotEmpty() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got.getMetadata().getName()).isEqualTo("empty"); assertThat(original.getMetadata().getName()).isEqualTo("empty"); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } @Test public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties MetricSnapshot original = CounterSnapshot.builder() .name("my_metric") .help("some help text") @@ -385,12 +374,12 @@ public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { .label("some_label", "labelvalue") .build()); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } @Test public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties MetricSnapshot original = CounterSnapshot.builder() .name("my_metric") .help("some help text") @@ -424,12 +413,12 @@ public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { .label("some.label", "labelvalue") .build()); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } @Test public void testEscapeMetricSnapshotCounterEscapingNeeded() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties MetricSnapshot original = CounterSnapshot.builder() .name("my.metric") .help("some help text") @@ -463,12 +452,12 @@ public void testEscapeMetricSnapshotCounterEscapingNeeded() { .label("some?label", "label??value") .build()); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } @Test public void testEscapeMetricSnapshotGaugeEscapingNeeded() { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; + // nameValidationScheme is now final and initialized from properties MetricSnapshot original = GaugeSnapshot.builder() .name("unicode.and.dots.花火") .help("some help text") @@ -502,6 +491,6 @@ public void testEscapeMetricSnapshotGaugeEscapingNeeded() { .label("some_label", "label??value") .build()); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + } } From afc315685050d1635f44a4dbea6d6ab7af34e784 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Sat, 19 Jul 2025 19:49:25 -0300 Subject: [PATCH 006/106] Fix write errors in tests Signed-off-by: Federico Torres --- .../instrumentation/dropwizard/DropwizardExportsTest.java | 4 +--- .../instrumentation/dropwizard5/DropwizardExportsTest.java | 4 +--- .../dropwizard5/labels/CustomLabelMapperTest.java | 4 +--- .../instrumentation/guava/CacheMetricsCollectorTest.java | 4 +--- .../io/prometheus/metrics/instrumentation/jvm/TestUtil.java | 5 +---- .../simpleclient/bridge/SimpleclientCollectorTest.java | 4 +--- 6 files changed, 6 insertions(+), 19 deletions(-) diff --git a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java index 3425525d3..8b4ac999c 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.instrumentation.dropwizard; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -342,8 +341,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, _registry.scrape()); + writer.write(out, _registry.scrape(), EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java index 392f94dc9..4ade85707 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.instrumentation.dropwizard5; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.data.Offset.offset; @@ -351,8 +350,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, _registry.scrape()); + writer.write(out, _registry.scrape(), EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new RuntimeException(e); diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java index b1a3adf6c..0623ea531 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.instrumentation.dropwizard5.labels; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -220,8 +219,7 @@ private String convertToOpenMetricsFormat(MetricSnapshots snapshots) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, snapshots); + writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new RuntimeException(e); 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 b1b0b0d4b..95a8a5c2a 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 @@ -1,6 +1,5 @@ package io.prometheus.metrics.instrumentation.guava; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -163,8 +162,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, registry.scrape()); + writer.write(out, registry.scrape(), EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java index 0a2ce5f9d..56683f75a 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java @@ -7,15 +7,12 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; - class TestUtil { static String convertToOpenMetricsFormat(MetricSnapshots snapshots) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, snapshots); + writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8.name()); } } diff --git a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java index 28d68b2e1..e21c7b5ba 100644 --- a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java +++ b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.simpleclient.bridge; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameEscapingScheme; import static org.assertj.core.api.Assertions.assertThat; import io.prometheus.client.Collector; @@ -269,8 +268,7 @@ private String origOpenMetrics() throws IOException { private String newOpenMetrics() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, false); - nameEscapingScheme = EscapingScheme.NO_ESCAPING; - writer.write(out, newRegistry.scrape()); + writer.write(out, newRegistry.scrape(), EscapingScheme.NO_ESCAPING); return out.toString(StandardCharsets.UTF_8.name()); } } From 314838d0b966f59dc3f1275d2aa7357964e68c04 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Fri, 25 Jul 2025 16:57:22 -0300 Subject: [PATCH 007/106] Add getter for nameValidationScheme Signed-off-by: Federico Torres --- .../exporter/pushgateway/PushGatewayTest.java | 213 +++++---- .../expositionformats/TextFormatUtil.java | 2 +- .../ExpositionFormatsTest.java | 95 ++-- .../metrics/model/snapshots/Labels.java | 2 +- .../model/snapshots/MetricMetadata.java | 2 +- .../model/snapshots/PrometheusNaming.java | 21 +- .../model/snapshots/PrometheusNamingTest.java | 412 ++++++++++-------- 7 files changed, 397 insertions(+), 350 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 268c1c49e..663430e1f 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.exporter.pushgateway; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.nameValidationScheme; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; @@ -13,10 +15,12 @@ import java.net.InetAddress; import java.net.URL; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.ValidationScheme; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; @@ -146,19 +150,22 @@ public void testPushWithGroupingKey() throws IOException { @Test public void testPushWithEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .build(); - pg.push(); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(); + } } @Test @@ -179,20 +186,23 @@ public void testPushWithMultiGroupingKey() throws IOException { @Test public void testPushWithMultiEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .groupingKey("l.2", "v2") - .build(); - pg.push(); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .groupingKey("l.2", "v2") + .build(); + pg.push(); + } } @Test @@ -245,19 +255,22 @@ public void testPushCollectorWithGroupingKey() throws IOException { @Test public void testPushCollectorWithEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .build(); - pg.push(gauge); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(gauge); + } } @Test @@ -301,19 +314,22 @@ public void testPushAddWithGroupingKey() throws IOException { @Test public void testPushAddWithEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - mockServerClient - .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .groupingKey("l.1", "v1") - .job("j") - .build(); - pg.pushAdd(); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(); + } } @Test @@ -333,19 +349,22 @@ public void testPushAddCollectorWithGroupingKey() throws IOException { @Test public void testPushAddCollectorWithEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - mockServerClient - .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .groupingKey("l.1", "v1") - .job("j") - .build(); - pg.pushAdd(gauge); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(gauge); + } } @Test @@ -374,18 +393,21 @@ public void testDeleteWithGroupingKey() throws IOException { @Test public void testDeleteWithEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - mockServerClient - .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("j") - .groupingKey("l.1", "v1") - .build(); - pg.delete(); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + mockServerClient + .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.delete(); + } } @Test @@ -407,20 +429,23 @@ public void testInstanceIpGroupingKey() throws IOException { @Test public void testInstanceIpEscapedGroupingKey() throws IOException { - nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - String ip = InetAddress.getLocalHost().getHostAddress(); - assertThat(ip).isNotEmpty(); - mockServerClient - .when(request().withMethod("DELETE").withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("j") - .groupingKey("l.1", "v1") - .instanceIpGroupingKey() - .build(); - pg.delete(); - nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + String ip = InetAddress.getLocalHost().getHostAddress(); + assertThat(ip).isNotEmpty(); + mockServerClient + .when(request().withMethod("DELETE").withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .instanceIpGroupingKey() + .build(); + pg.delete(); + } } } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 23589f7aa..980783ed2 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -136,7 +136,7 @@ static void writeName(Writer writer, String name, NameType nameType) throws IOEx } break; case Label: - if (PrometheusNaming.isValidLegacyLabelName(name) && PrometheusNaming.nameValidationScheme == ValidationScheme.LEGACY_VALIDATION) { + if (PrometheusNaming.isValidLegacyLabelName(name) && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { writer.write(name); return; } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 816f49c1d..29f498c0d 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -2,6 +2,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; @@ -12,10 +14,12 @@ import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; class ExpositionFormatsTest { @@ -192,7 +196,6 @@ public void testCounterComplete() throws IOException { + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("service_time_seconds") @@ -237,7 +240,6 @@ public void testCounterMinimal() throws IOException { """; String prometheusProtobuf = "name: \"my_counter_total\" type: COUNTER metric { counter { value: 1.1 } }"; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("my_counter") @@ -277,7 +279,6 @@ public void testCounterWithDots() throws IOException { + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("my.request.count") @@ -345,7 +346,6 @@ public void testGaugeComplete() throws IOException { + "timestamp_ms: 1672850585820 " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() .name("disk_usage_ratio") @@ -390,7 +390,6 @@ public void testGaugeMinimal() throws IOException { """; String prometheusProtobuf = "name: \"temperature_centigrade\" type: GAUGE metric { gauge { value: 22.3 } }"; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() .name("temperature_centigrade") @@ -440,7 +439,6 @@ public void testGaugeWithDots() throws IOException { + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; GaugeSnapshot gauge = GaugeSnapshot.builder() .name("my.temperature.celsius") @@ -462,36 +460,37 @@ public void testGaugeWithDots() throws IOException { @Test public void testGaugeUTF8() throws IOException { - String prometheusText = - """ - # HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing - # TYPE \"gauge.name\" gauge - {\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf - {\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42 - """; - PrometheusNaming.nameValidationScheme = ValidationScheme.UTF_8_VALIDATION; - - GaugeSnapshot gauge = GaugeSnapshot.builder() - .name("gauge.name") - .help("gauge\ndoc\nstr\"ing") - .dataPoint(GaugeDataPointSnapshot.builder() - .value(Double.POSITIVE_INFINITY) - .labels(Labels.builder() - .label("name.1", "val with\nnew line") - .label("name*2", "val with \\backslash and \"quotes\"") + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + String prometheusText = + """ + # HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing + # TYPE \"gauge.name\" gauge + {\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf + {\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42 + """; + GaugeSnapshot gauge = GaugeSnapshot.builder() + .name("gauge.name") + .help("gauge\ndoc\nstr\"ing") + .dataPoint(GaugeDataPointSnapshot.builder() + .value(Double.POSITIVE_INFINITY) + .labels(Labels.builder() + .label("name.1", "val with\nnew line") + .label("name*2", "val with \\backslash and \"quotes\"") + .build()) .build()) - .build()) - .dataPoint(GaugeDataPointSnapshot.builder() - .value(3.14e42) - .labels(Labels.builder() - .label("name.1", "Björn") - .label("name*2", "佖佥") + .dataPoint(GaugeDataPointSnapshot.builder() + .value(3.14e42) + .labels(Labels.builder() + .label("name.1", "Björn") + .label("name*2", "佖佥") + .build()) .build()) - .build()) - .build(); - assertPrometheusText(prometheusText, gauge); - - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; + .build(); + assertPrometheusText(prometheusText, gauge); + } } @Test @@ -742,7 +741,6 @@ public void testSummaryComplete() throws IOException { + "timestamp_ms: 1672850585820 " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("http_request_duration_seconds") @@ -818,7 +816,6 @@ public void testSummaryWithoutQuantiles() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -856,7 +853,6 @@ public void testSummaryNoCountAndSum() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -895,7 +891,6 @@ public void testSummaryJustCount() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -931,7 +926,6 @@ public void testSummaryJustSum() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -948,7 +942,6 @@ public void testSummaryJustSum() throws IOException { public void testSummaryEmptyData() throws IOException { // SummaryData can be present but empty (no count, no sum, no quantiles). // This should be treated like no data is present. - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -990,7 +983,6 @@ public void testSummaryEmptyAndNonEmpty() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("latency_seconds") @@ -1048,7 +1040,6 @@ public void testSummaryWithDots() throws IOException { + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; SummarySnapshot summary = SummarySnapshot.builder() .name("my.request.duration.seconds") @@ -1333,7 +1324,6 @@ public void testClassicHistogramComplete() throws Exception { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("response_size_bytes") @@ -1406,7 +1396,6 @@ public void testClassicHistogramMinimal() throws Exception { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("request_latency_seconds") @@ -1457,7 +1446,6 @@ public void testClassicHistogramCountAndSum() throws Exception { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("request_latency_seconds") @@ -1731,7 +1719,6 @@ public void testClassicGaugeHistogramComplete() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot gaugeHistogram = HistogramSnapshot.builder() .gaugeHistogram(true) @@ -1805,7 +1792,6 @@ public void testClassicGaugeHistogramMinimal() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot gaugeHistogram = HistogramSnapshot.builder() .gaugeHistogram(true) @@ -1859,7 +1845,6 @@ public void testClassicGaugeHistogramCountAndSum() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot gaugeHistogram = HistogramSnapshot.builder() .gaugeHistogram(true) @@ -1929,7 +1914,6 @@ public void testClassicHistogramWithDots() throws IOException { + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("my.request.duration.seconds") @@ -2201,7 +2185,6 @@ public void testNativeHistogramComplete() throws IOException { "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot nativeHistogram = HistogramSnapshot.builder() .name("response_size_bytes") @@ -2292,7 +2275,6 @@ public void testNativeHistogramMinimal() throws IOException { + "} " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot nativeHistogram = HistogramSnapshot.builder() .name("latency_seconds") @@ -2358,7 +2340,6 @@ public void testNativeHistogramWithDots() throws IOException { + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; HistogramSnapshot histogram = HistogramSnapshot.builder() .name("my.request.duration.seconds") @@ -2399,7 +2380,6 @@ public void testInfo() throws IOException { # TYPE version_info gauge version_info{version="1.2.3"} 1 """; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; InfoSnapshot info = InfoSnapshot.builder() .name("version") @@ -2440,7 +2420,6 @@ public void testInfoWithDots() throws IOException { + "gauge { value: 1.0 } " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; InfoSnapshot info = InfoSnapshot.builder() .name("jvm.status") @@ -2488,7 +2467,6 @@ public void testStateSetComplete() throws IOException { + "state{env=\"prod\",state=\"state2\"} 1 " + scrapeTimestamp2s + "\n"; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; StateSetSnapshot stateSet = StateSetSnapshot.builder() .name("state") @@ -2529,7 +2507,6 @@ public void testStateSetMinimal() throws IOException { state{state="a"} 1 state{state="bb"} 0 """; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; StateSetSnapshot stateSet = StateSetSnapshot.builder() .name("state") @@ -2577,7 +2554,6 @@ public void testStateSetWithDots() throws IOException { + "gauge { value: 0.0 } " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; StateSetSnapshot stateSet = StateSetSnapshot.builder() .name("my.application.state") @@ -2631,7 +2607,6 @@ public void testUnknownComplete() throws IOException { + "my_special_thing_bytes{env=\"prod\"} 0.7 " + scrapeTimestamp2s + "\n"; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; UnknownSnapshot unknown = UnknownSnapshot.builder() .name("my_special_thing_bytes") @@ -2673,7 +2648,6 @@ public void testUnknownMinimal() throws IOException { # TYPE other untyped other 22.3 """; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; UnknownSnapshot unknown = UnknownSnapshot.builder() .name("other") @@ -2719,7 +2693,6 @@ public void testUnknownWithDots() throws IOException { + "untyped { value: 0.7 } " + "}"; // @formatter:on - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; UnknownSnapshot unknown = UnknownSnapshot.builder() .name(PrometheusNaming.sanitizeMetricName("some.unknown.metric", Unit.BYTES)) @@ -2754,7 +2727,6 @@ public void testHelpEscape() throws IOException { # TYPE test_total counter test_total 1.0 """; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("test") @@ -2780,7 +2752,6 @@ public void testLabelValueEscape() throws IOException { # TYPE test_total counter test_total{a="x",b="escaping\\" example \\n "} 1.0 """; - PrometheusNaming.nameValidationScheme = ValidationScheme.LEGACY_VALIDATION; CounterSnapshot counter = CounterSnapshot.builder() .name("test") diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index bb846ce68..eb1b7369c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -119,7 +119,7 @@ public static Labels of(String[] names, String[] values) { static String[] makePrometheusNames(String[] names) { String[] prometheusNames = names; for (int i = 0; i < names.length; i++) { - if (names[i].contains(".") && PrometheusNaming.nameValidationScheme == ValidationScheme.LEGACY_VALIDATION) { + if (names[i].contains(".") && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { if (prometheusNames == names) { prometheusNames = Arrays.copyOf(names, names.length); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 79ef32bbd..a6704ef3e 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -53,7 +53,7 @@ public MetricMetadata(String name, String help, Unit unit) { this.help = help; this.unit = unit; validate(); - this.prometheusName = name.contains(".") && PrometheusNaming.nameValidationScheme == ValidationScheme.LEGACY_VALIDATION ? PrometheusNaming.prometheusName(name) : name; + this.prometheusName = name.contains(".") && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION ? PrometheusNaming.prometheusName(name) : name; } /** diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index e6ee51fc9..1b6c2243a 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -87,6 +87,15 @@ static ValidationScheme initValidationScheme() { return ValidationScheme.LEGACY_VALIDATION; } + /** + * Get the current validation scheme. This method exists primarily to enable + * testing different validation behaviors while keeping the validation scheme + * field final and immutable. + */ + public static ValidationScheme getValidationScheme() { + return nameValidationScheme; + } + /** * Test if a metric name is valid. Rules: * @@ -112,7 +121,7 @@ public static boolean isValidMetricName(String name) { } public static String validateMetricName(String name) { - switch (nameValidationScheme) { + switch (getValidationScheme()) { case LEGACY_VALIDATION: return validateLegacyMetricName(name); case UTF_8_VALIDATION: @@ -121,7 +130,7 @@ public static String validateMetricName(String name) { } return null; default: - throw new RuntimeException("Invalid name validation scheme requested: " + nameValidationScheme); + throw new RuntimeException("Invalid name validation scheme requested: " + getValidationScheme()); } } @@ -143,24 +152,24 @@ public static String validateLegacyMetricName(String name) { } public static boolean isValidLegacyMetricName(String name) { - switch (nameValidationScheme) { + switch (getValidationScheme()) { case LEGACY_VALIDATION: return LEGACY_METRIC_NAME_PATTERN.matcher(name).matches(); case UTF_8_VALIDATION: return METRIC_NAME_PATTERN.matcher(name).matches(); default: - throw new RuntimeException("Invalid name validation scheme requested: " + nameValidationScheme); + throw new RuntimeException("Invalid name validation scheme requested: " + getValidationScheme()); } } public static boolean isValidLabelName(String name) { - switch (nameValidationScheme) { + switch (getValidationScheme()) { case LEGACY_VALIDATION: return isValidLegacyLabelName(name); case UTF_8_VALIDATION: return StandardCharsets.UTF_8.newEncoder().canEncode(name); default: - throw new RuntimeException("Invalid name validation scheme requested: " + nameValidationScheme); + throw new RuntimeException("Invalid name validation scheme requested: " + getValidationScheme()); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 70d4ba1fb..68a9afd5c 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -3,16 +3,19 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.CALLS_REAL_METHODS; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import java.net.InetAddress; import java.util.Optional; class PrometheusNamingTest { @Test public void testSanitizeMetricName() { - // nameValidationScheme is now final and initialized from properties assertThat(prometheusName(sanitizeMetricName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeMetricName("___ab.:c0"))).isEqualTo("___ab__c0"); assertThat(sanitizeMetricName("my_prefix/my_metric")).isEqualTo("my_prefix_my_metric"); @@ -104,7 +107,6 @@ public void testEmptyUnitName() { @Test public void testMetricNameIsValid() { - // nameValidationScheme is now final and initialized from properties (defaults to LEGACY_VALIDATION) assertThat(validateMetricName("Avalid_23name")).isNull(); assertThat(validateMetricName("_Avalid_23name")).isNull(); assertThat(validateMetricName("1valid_23name")).isEqualTo("The metric name contains unsupported characters"); @@ -119,16 +121,22 @@ public void testMetricNameIsValid() { @Test public void testLabelNameIsValid() { - // nameValidationScheme is now final and initialized from properties - assertThat(isValidLabelName("Avalid_23name")).isTrue(); - assertThat(isValidLabelName("_Avalid_23name")).isTrue(); - assertThat(isValidLabelName("1valid_23name")).isTrue(); - assertThat(isValidLabelName("avalid_23name")).isTrue(); - assertThat(isValidLabelName("Ava:lid_23name")).isTrue(); - assertThat(isValidLabelName("a lid_23name")).isTrue(); - assertThat(isValidLabelName(":leading_colon")).isTrue(); - assertThat(isValidLabelName("colon:in:the:middle")).isTrue(); - assertThat(isValidLabelName("a\ud800z")).isFalse(); + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + // Mock the validation scheme to use UTF-8 validation for this test + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + // These assertions now use UTF-8 validation behavior + assertThat(isValidLabelName("Avalid_23name")).isTrue(); + assertThat(isValidLabelName("_Avalid_23name")).isTrue(); + assertThat(isValidLabelName("1valid_23name")).isTrue(); + assertThat(isValidLabelName("avalid_23name")).isTrue(); + assertThat(isValidLabelName("Ava:lid_23name")).isTrue(); + assertThat(isValidLabelName("a lid_23name")).isTrue(); + assertThat(isValidLabelName(":leading_colon")).isTrue(); + assertThat(isValidLabelName("colon:in:the:middle")).isTrue(); + assertThat(isValidLabelName("a\ud800z")).isFalse(); + } assertThat(isValidLabelName("Avalid_23name")).isTrue(); assertThat(isValidLabelName("_Avalid_23name")).isTrue(); @@ -143,8 +151,6 @@ public void testLabelNameIsValid() { @Test public void testEscapeName() { - // nameValidationScheme is now final and initialized from properties - // empty string String got = escapeName("", EscapingScheme.UNDERSCORE_ESCAPING); assertThat(got).isEqualTo(""); @@ -179,53 +185,83 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("no:escaping_required"); - // name with dots - got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); + // name with dots - needs UTF-8 validation for escaping to occur + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); + } got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load"); got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); - got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); - - // name with dots and underscore - got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); + } + + // name with dots and underscore - needs UTF-8 validation for escaping to occur + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); + } got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"); got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); - got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); - - // name with dots and colon - got = escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("http_status:sum"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("http_status:sum"); + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); + } + + // name with dots and colon - needs UTF-8 validation for escaping to occur + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + got = escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("http_status:sum"); + got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(got).isEqualTo("http_status:sum"); + } got = escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("http_dot_status:sum"); got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("http.status:sum"); - got = escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__http_2e_status:sum"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("http.status:sum"); + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + got = escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("U__http_2e_status:sum"); + got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got).isEqualTo("http.status:sum"); + } // name with spaces and emoji got = escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING); @@ -282,7 +318,6 @@ public void testEscapeName() { @Test public void testValueUnescapeErrors() { - // nameValidationScheme is now final and initialized from properties String got; // empty string @@ -330,7 +365,6 @@ public void testValueUnescapeErrors() { @Test public void testEscapeMetricSnapshotEmpty() { - // nameValidationScheme is now final and initialized from properties MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got.getMetadata().getName()).isEqualTo("empty"); @@ -340,157 +374,165 @@ public void testEscapeMetricSnapshotEmpty() { @Test public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { - // nameValidationScheme is now final and initialized from properties - MetricSnapshot original = CounterSnapshot.builder() - .name("my_metric") - .help("some help text") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "my_metric") - .label("some_label", "labelvalue") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - - assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("some_label", "labelvalue") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("some_label", "labelvalue") - .build()); - - + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + MetricSnapshot original = CounterSnapshot.builder() + .name("my_metric") + .help("some help text") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "my_metric") + .label("some_label", "labelvalue") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("some_label", "labelvalue") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("some_label", "labelvalue") + .build()); + } } @Test public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { - // nameValidationScheme is now final and initialized from properties - MetricSnapshot original = CounterSnapshot.builder() - .name("my_metric") - .help("some help text") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "my_metric") - .label("some.label", "labelvalue") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - - assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("U__some_2e_label", "labelvalue") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("some.label", "labelvalue") - .build()); - - + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + MetricSnapshot original = CounterSnapshot.builder() + .name("my_metric") + .help("some help text") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "my_metric") + .label("some.label", "labelvalue") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("U__some_2e_label", "labelvalue") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my_metric") + .label("some.label", "labelvalue") + .build()); + } } @Test public void testEscapeMetricSnapshotCounterEscapingNeeded() { - // nameValidationScheme is now final and initialized from properties - MetricSnapshot original = CounterSnapshot.builder() - .name("my.metric") - .help("some help text") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "my.metric") - .label("some?label", "label??value") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - - assertThat(got.getMetadata().getName()).isEqualTo("U__my_2e_metric"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "U__my_2e_metric") - .label("U__some_3f_label", "label??value") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("my.metric"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my.metric") - .label("some?label", "label??value") - .build()); - - + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + MetricSnapshot original = CounterSnapshot.builder() + .name("my.metric") + .help("some help text") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "my.metric") + .label("some?label", "label??value") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("U__my_2e_metric"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "U__my_2e_metric") + .label("U__some_3f_label", "label??value") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("my.metric"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "my.metric") + .label("some?label", "label??value") + .build()); + } } @Test public void testEscapeMetricSnapshotGaugeEscapingNeeded() { - // nameValidationScheme is now final and initialized from properties - MetricSnapshot original = GaugeSnapshot.builder() - .name("unicode.and.dots.花火") - .help("some help text") - .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "unicode.and.dots.花火") - .label("some_label", "label??value") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.DOTS_ESCAPING); - - assertThat(got.getMetadata().getName()).isEqualTo("unicode_dot_and_dot_dots_dot_____"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - GaugeSnapshot.GaugeDataPointSnapshot data = (GaugeSnapshot.GaugeDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "unicode_dot_and_dot_dots_dot_____") - .label("some_label", "label??value") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("unicode.and.dots.花火"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (GaugeSnapshot.GaugeDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "unicode.and.dots.花火") - .label("some_label", "label??value") - .build()); - - + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + mock.when(PrometheusNaming::getValidationScheme) + .thenReturn(ValidationScheme.UTF_8_VALIDATION); + + MetricSnapshot original = GaugeSnapshot.builder() + .name("unicode.and.dots.花火") + .help("some help text") + .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder() + .value(34.2) + .labels(Labels.builder() + .label("__name__", "unicode.and.dots.花火") + .label("some_label", "label??value") + .build()) + .build() + ) + .build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.DOTS_ESCAPING); + + assertThat(got.getMetadata().getName()).isEqualTo("unicode_dot_and_dot_dots_dot_____"); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints().size()).isEqualTo(1); + GaugeSnapshot.GaugeDataPointSnapshot data = (GaugeSnapshot.GaugeDataPointSnapshot) got.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "unicode_dot_and_dot_dots_dot_____") + .label("some_label", "label??value") + .build()); + assertThat(original.getMetadata().getName()).isEqualTo("unicode.and.dots.花火"); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints().size()).isEqualTo(1); + data = (GaugeSnapshot.GaugeDataPointSnapshot) original.getDataPoints().get(0); + assertThat(data.getValue()).isEqualTo(34.2); + assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() + .label("__name__", "unicode.and.dots.花火") + .label("some_label", "label??value") + .build()); + } } } From ce9aeb2f5debbf1bf6c318c7e0c1d564b617ccdc Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Wed, 6 Aug 2025 09:40:53 -0300 Subject: [PATCH 008/106] Change NameType accessibility Signed-off-by: Federico Torres --- .../java/io/prometheus/metrics/expositionformats/NameType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java index 5041e5323..1a9a813aa 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.expositionformats; -public enum NameType { +enum NameType { Metric, Label } From 65bfb6b6349eb1b65ff574cb7d51a9f531cd0931 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Wed, 6 Aug 2025 10:41:01 -0300 Subject: [PATCH 009/106] Remove prefix argument from load method in NamingProperties Signed-off-by: Federico Torres --- .../java/io/prometheus/metrics/config/NamingProperties.java | 5 +++-- .../metrics/config/PrometheusPropertiesLoader.java | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java index ba91ea774..b8750cc13 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java @@ -4,6 +4,7 @@ public class NamingProperties { + private static final String PREFIX = "io.prometheus.naming"; private static final String VALIDATION_SCHEME = "validationScheme"; private final String validationScheme; @@ -15,8 +16,8 @@ public String getValidationScheme() { return validationScheme; } - static NamingProperties load(String prefix, Map properties) throws PrometheusPropertiesException { - String validationScheme = Util.loadString(prefix + "." + VALIDATION_SCHEME, properties); + static NamingProperties load(Map properties) throws PrometheusPropertiesException { + String validationScheme = Util.loadString(PREFIX + "." + VALIDATION_SCHEME, properties); return new NamingProperties(validationScheme); } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java index af280df9a..cd4ba3e33 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java @@ -4,7 +4,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; -import java.rmi.Naming; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -43,7 +42,7 @@ public static PrometheusProperties load(Map externalProperties) ExporterPushgatewayProperties.load(properties); ExporterOpenTelemetryProperties exporterOpenTelemetryProperties = ExporterOpenTelemetryProperties.load(properties); - NamingProperties namingProperties = NamingProperties.load("io.prometheus.naming", properties); + NamingProperties namingProperties = NamingProperties.load(properties); validateAllPropertiesProcessed(properties); return new PrometheusProperties( defaultMetricsProperties, From e918ca3b765870efda22a1456d53985d4bb539cb Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Wed, 6 Aug 2025 10:48:07 -0300 Subject: [PATCH 010/106] Remove redundant null check in initValidationScheme Signed-off-by: Federico Torres --- .../metrics/model/snapshots/PrometheusNaming.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 1b6c2243a..f9f232e99 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -78,12 +78,11 @@ public class PrometheusNaming { }; static ValidationScheme initValidationScheme() { - if (PrometheusProperties.get() != null && PrometheusProperties.get().getNamingProperties() != null) { - String validationScheme = PrometheusProperties.get().getNamingProperties().getValidationScheme(); - if (validationScheme != null && validationScheme.equals("utf-8")) { - return ValidationScheme.UTF_8_VALIDATION; - } + String validationScheme = PrometheusProperties.get().getNamingProperties().getValidationScheme(); + if (validationScheme != null && validationScheme.equals("utf-8")) { + return ValidationScheme.UTF_8_VALIDATION; } + return ValidationScheme.LEGACY_VALIDATION; } From e2aacd5ce85fe427b0a611863d489e47650276d9 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Wed, 6 Aug 2025 11:41:28 -0300 Subject: [PATCH 011/106] Refactor escaped snapshots creation Signed-off-by: Federico Torres --- .../model/snapshots/PrometheusNaming.java | 177 +++++++++--------- 1 file changed, 90 insertions(+), 87 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index f9f232e99..1f56cf361 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -420,75 +420,93 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche } Labels outLabels = outLabelsBuilder.build(); - DataPointSnapshot outDataPointSnapshot = null; - - if (v instanceof CounterSnapshot) { - outDataPointSnapshot = CounterSnapshot.CounterDataPointSnapshot.builder() - .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) - .exemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar()) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof GaugeSnapshot) { - outDataPointSnapshot = GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) - .exemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar()) - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof HistogramSnapshot) { - outDataPointSnapshot = HistogramSnapshot.HistogramDataPointSnapshot.builder() - .classicHistogramBuckets(((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) - .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) - .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) - .nativeZeroThreshold(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) - .nativeBucketsForPositiveValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForPositiveValues()) - .nativeBucketsForNegativeValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForNegativeValues()) - .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) - .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) - .exemplars(((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars()) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof SummarySnapshot) { - outDataPointSnapshot = SummarySnapshot.SummaryDataPointSnapshot.builder() - .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) - .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) - .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) - .exemplars(((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars()) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof InfoSnapshot) { - outDataPointSnapshot = InfoSnapshot.InfoDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()); - for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { - builder.state(state.getName(), state.isTrue()); - } - outDataPointSnapshot = builder.build(); - } else if (v instanceof UnknownSnapshot) { - outDataPointSnapshot = UnknownSnapshot.UnknownDataPointSnapshot.builder() - .labels(outLabels) - .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) - .exemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } - + DataPointSnapshot outDataPointSnapshot = createEscapedDataPointSnapshot(v, d, outLabels); outDataPoints.add(outDataPointSnapshot); } - MetricSnapshot out; + return createEscapedMetricSnapshot(v, outName, outDataPoints); + } + + static boolean metricNeedsEscaping(DataPointSnapshot d) { + Labels labels = d.getLabels(); + for (Label l : labels) { + if (l.getName().equals(METRIC_NAME_LABEL) && !isValidLegacyMetricName(l.getValue())) { + return true; + } + if (!isValidLegacyMetricName(l.getName())) { + return true; + } + } + return false; + } + + private static DataPointSnapshot createEscapedDataPointSnapshot(MetricSnapshot v, DataPointSnapshot d, Labels outLabels) { + if (v instanceof CounterSnapshot) { + return CounterSnapshot.CounterDataPointSnapshot.builder() + .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) + .exemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof GaugeSnapshot) { + return GaugeSnapshot.GaugeDataPointSnapshot.builder() + .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) + .exemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar()) + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof HistogramSnapshot) { + return HistogramSnapshot.HistogramDataPointSnapshot.builder() + .classicHistogramBuckets(((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) + .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) + .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) + .nativeZeroThreshold(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) + .nativeBucketsForPositiveValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForPositiveValues()) + .nativeBucketsForNegativeValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForNegativeValues()) + .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) + .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) + .exemplars(((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof SummarySnapshot) { + return SummarySnapshot.SummaryDataPointSnapshot.builder() + .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) + .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) + .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) + .exemplars(((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof InfoSnapshot) { + return InfoSnapshot.InfoDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof StateSetSnapshot) { + StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()); + for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { + builder.state(state.getName(), state.isTrue()); + } + return builder.build(); + } else if (v instanceof UnknownSnapshot) { + return UnknownSnapshot.UnknownDataPointSnapshot.builder() + .labels(outLabels) + .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) + .exemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else { + throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); + } + } + private static MetricSnapshot createEscapedMetricSnapshot(MetricSnapshot v, String outName, List outDataPoints) { if (v instanceof CounterSnapshot) { CounterSnapshot.Builder builder = CounterSnapshot.builder() .name(outName) @@ -497,7 +515,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((CounterSnapshot.CounterDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else if (v instanceof GaugeSnapshot) { GaugeSnapshot.Builder builder = GaugeSnapshot.builder() .name(outName) @@ -506,7 +524,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((GaugeSnapshot.GaugeDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else if (v instanceof HistogramSnapshot) { HistogramSnapshot.Builder builder = HistogramSnapshot.builder() .name(outName) @@ -516,7 +534,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((HistogramSnapshot.HistogramDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else if (v instanceof SummarySnapshot) { SummarySnapshot.Builder builder = SummarySnapshot.builder() .name(outName) @@ -525,7 +543,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((SummarySnapshot.SummaryDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else if (v instanceof InfoSnapshot) { InfoSnapshot.Builder builder = InfoSnapshot.builder() .name(outName) @@ -533,7 +551,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((InfoSnapshot.InfoDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else if (v instanceof StateSetSnapshot) { StateSetSnapshot.Builder builder = StateSetSnapshot.builder() .name(outName) @@ -541,7 +559,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((StateSetSnapshot.StateSetDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else if (v instanceof UnknownSnapshot) { UnknownSnapshot.Builder builder = UnknownSnapshot.builder() .name(outName) @@ -550,25 +568,10 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((UnknownSnapshot.UnknownDataPointSnapshot) d); } - out = builder.build(); + return builder.build(); } else { throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); } - - return out; - } - - static boolean metricNeedsEscaping(DataPointSnapshot d) { - Labels labels = d.getLabels(); - for (Label l : labels) { - if (l.getName().equals(METRIC_NAME_LABEL) && !isValidLegacyMetricName(l.getValue())) { - return true; - } - if (!isValidLegacyMetricName(l.getName())) { - return true; - } - } - return false; } /** From 7204288cbc37654260690140211379a63d778b08 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 7 Aug 2025 10:43:52 -0300 Subject: [PATCH 012/106] Extract common logic in escaping tests Signed-off-by: Federico Torres --- .../model/snapshots/PrometheusNamingTest.java | 235 +++++++----------- 1 file changed, 84 insertions(+), 151 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 68a9afd5c..3afbde588 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -9,9 +9,6 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import java.net.InetAddress; -import java.util.Optional; - class PrometheusNamingTest { @Test @@ -153,19 +150,19 @@ public void testLabelNameIsValid() { public void testEscapeName() { // empty string String got = escapeName("", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); got = escapeName("", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); got = escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); // legacy valid name got = escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING); @@ -322,7 +319,7 @@ public void testValueUnescapeErrors() { // empty string got = unescapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo(""); + assertThat(got).isEmpty(); // basic case, no error got = unescapeName("U__no:unescapingrequired", EscapingScheme.VALUE_ENCODING_ESCAPING); @@ -374,165 +371,101 @@ public void testEscapeMetricSnapshotEmpty() { @Test public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - MetricSnapshot original = CounterSnapshot.builder() - .name("my_metric") - .help("some help text") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "my_metric") - .label("some_label", "labelvalue") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - - assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("some_label", "labelvalue") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("some_label", "labelvalue") - .build()); - } + testEscapeMetricSnapshot( + "my_metric", "some_label", "labelvalue", "some help text", + "my_metric", "some_label", "labelvalue", "some help text", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); } @Test public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - MetricSnapshot original = CounterSnapshot.builder() - .name("my_metric") - .help("some help text") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "my_metric") - .label("some.label", "labelvalue") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - - assertThat(got.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("U__some_2e_label", "labelvalue") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("my_metric"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my_metric") - .label("some.label", "labelvalue") - .build()); - } + testEscapeMetricSnapshot( + "my_metric", "some.label", "labelvalue", "some help text", + "my_metric", "U__some_2e_label", "labelvalue", "some help text", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); } @Test public void testEscapeMetricSnapshotCounterEscapingNeeded() { + testEscapeMetricSnapshot( + "my.metric", "some?label", "label??value", "some help text", + "U__my_2e_metric", "U__some_3f_label", "label??value", "some help text", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + public void testEscapeMetricSnapshotGaugeEscapingNeeded() { + testEscapeMetricSnapshot( + "unicode.and.dots.花火", "some_label", "label??value", "some help text", + "unicode_dot_and_dot_dots_dot_____", "some_label", "label??value", "some help text", + EscapingScheme.DOTS_ESCAPING, + GaugeSnapshot.class); + } + + private void testEscapeMetricSnapshot( + String name, String labelName, String labelValue, String help, + String expectedName, String expectedLabelName, String expectedLabelValue, String expectedHelp, + EscapingScheme escapingScheme, + Class snapshotType) { + try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - MetricSnapshot original = CounterSnapshot.builder() - .name("my.metric") - .help("some help text") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "my.metric") - .label("some?label", "label??value") - .build()) - .build() - ) - .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); - assertThat(got.getMetadata().getName()).isEqualTo("U__my_2e_metric"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, help, 34.2, snapshotType); + MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); + + assertThat(got.getMetadata().getName()).isEqualTo(expectedName); + assertThat(got.getMetadata().getHelp()).isEqualTo(expectedHelp); assertThat(got.getDataPoints().size()).isEqualTo(1); - CounterSnapshot.CounterDataPointSnapshot data = (CounterSnapshot.CounterDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "U__my_2e_metric") - .label("U__some_3f_label", "label??value") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("my.metric"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + + DataPointSnapshot escapedData = got.getDataPoints().get(0); + assertThat((Iterable) escapedData.getLabels()).isEqualTo(Labels.builder() + .label("__name__", expectedName) + .label(expectedLabelName, expectedLabelValue) + .build()); + + assertThat(original.getMetadata().getName()).isEqualTo(name); + assertThat(original.getMetadata().getHelp()).isEqualTo(help); assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (CounterSnapshot.CounterDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "my.metric") - .label("some?label", "label??value") - .build()); + + DataPointSnapshot originalData = original.getDataPoints().get(0); + assertThat((Iterable) originalData.getLabels()).isEqualTo(Labels.builder() + .label("__name__", name) + .label(labelName, labelValue) + .build()); } } - @Test - public void testEscapeMetricSnapshotGaugeEscapingNeeded() { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - MetricSnapshot original = GaugeSnapshot.builder() - .name("unicode.and.dots.花火") - .help("some help text") - .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(34.2) - .labels(Labels.builder() - .label("__name__", "unicode.and.dots.花火") - .label("some_label", "label??value") - .build()) - .build() - ) + private MetricSnapshot createTestSnapshot(String name, String labelName, String labelValue, String help, double value, Class snapshotType) { + Labels labels = Labels.builder() + .label("__name__", name) + .label(labelName, labelValue) .build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.DOTS_ESCAPING); - assertThat(got.getMetadata().getName()).isEqualTo("unicode_dot_and_dot_dots_dot_____"); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints().size()).isEqualTo(1); - GaugeSnapshot.GaugeDataPointSnapshot data = (GaugeSnapshot.GaugeDataPointSnapshot) got.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "unicode_dot_and_dot_dots_dot_____") - .label("some_label", "label??value") - .build()); - assertThat(original.getMetadata().getName()).isEqualTo("unicode.and.dots.花火"); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints().size()).isEqualTo(1); - data = (GaugeSnapshot.GaugeDataPointSnapshot) original.getDataPoints().get(0); - assertThat(data.getValue()).isEqualTo(34.2); - assertThat((Iterable) data.getLabels()).isEqualTo(Labels.builder() - .label("__name__", "unicode.and.dots.花火") - .label("some_label", "label??value") - .build()); + if (snapshotType.equals(CounterSnapshot.class)) { + return CounterSnapshot.builder() + .name(name) + .help(help) + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() + .value(value) + .labels(labels) + .build()) + .build(); + } else if (snapshotType.equals(GaugeSnapshot.class)) { + return GaugeSnapshot.builder() + .name(name) + .help(help) + .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder() + .value(value) + .labels(labels) + .build()) + .build(); } + + throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); } } From 891d28d25e7d8892d3e111f14610461f88e2dfea Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 7 Aug 2025 11:33:11 -0300 Subject: [PATCH 013/106] Parameterize testFindWriter Signed-off-by: Federico Torres --- .../ExpositionFormatsTest.java | 53 +++++-------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 29f498c0d..3b4bc6012 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -19,6 +19,8 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.MockedStatic; class ExpositionFormatsTest { @@ -2766,51 +2768,20 @@ public void testLabelValueEscape() throws IOException { assertPrometheusText(prometheus, counter); } - @Test - public void testFindWriter() { + @ParameterizedTest + @CsvSource({ + "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values'", + "'text/plain;version=0.0.4', 'text/plain; version=0.0.4; charset=utf-8; escaping=values'", + "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited; escaping=allow-utf-8', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8'", + "'application/openmetrics-text', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values'", + "'application/openmetrics-text;version=0.0.1; escaping=underscores', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'", + "'text/plain;version=0.0.4; escaping=allow-utf-8', 'text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8'" + }) + public void testFindWriter(String acceptHeaderValue, String expectedFmt) { ExpositionFormats expositionFormats = ExpositionFormats.init(); - - // delimited format - should use default escaping scheme - String acceptHeaderValue = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited"; - String expectedFmt = "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values"; EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeaderValue); assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); - - // plain text format - should use default escaping scheme - acceptHeaderValue = "text/plain;version=0.0.4"; - expectedFmt = "text/plain; version=0.0.4; charset=utf-8; escaping=values"; - escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); - writer = expositionFormats.findWriter(acceptHeaderValue); - assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); - - // delimited format UTF-8 - explicit escaping parameter - acceptHeaderValue = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited; escaping=allow-utf-8"; - expectedFmt = "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8"; - escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); - writer = expositionFormats.findWriter(acceptHeaderValue); - assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); - - // OM format, no version - should use default escaping scheme - acceptHeaderValue = "application/openmetrics-text"; - expectedFmt = "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values"; - escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); - writer = expositionFormats.findWriter(acceptHeaderValue); - assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); - - // OM format, 0.0.1 version - explicit escaping parameter - acceptHeaderValue = "application/openmetrics-text;version=0.0.1; escaping=underscores"; - expectedFmt = "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores"; - escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); - writer = expositionFormats.findWriter(acceptHeaderValue); - assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); - - // plain text format UTF-8 - explicit escaping parameter - acceptHeaderValue = "text/plain;version=0.0.4; escaping=allow-utf-8"; - expectedFmt = "text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8"; - escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); - writer = expositionFormats.findWriter(acceptHeaderValue); - assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); } @Test From 8143f69204ce79e04c86c281a3ace39efe93fa97 Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 7 Aug 2025 12:20:13 -0300 Subject: [PATCH 014/106] Add escaping scheme to ExporterPushgatewayProperties Signed-off-by: Federico Torres --- .../config/ExporterPushgatewayProperties.java | 28 +++++++++++++++++-- .../exporter/pushgateway/PushGateway.java | 24 ++++++++++++++-- .../exporter/pushgateway/PushGatewayTest.java | 18 ++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 8aafba3a4..b1d2e5a12 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -7,15 +7,18 @@ public class ExporterPushgatewayProperties { private static final String ADDRESS = "address"; private static final String JOB = "job"; private static final String SCHEME = "scheme"; + private static final String ESCAPING_SCHEME = "escapingScheme"; private static final String PREFIX = "io.prometheus.exporter.pushgateway"; private final String scheme; private final String address; private final String job; + private final String escapingScheme; - private ExporterPushgatewayProperties(String address, String job, String scheme) { + private ExporterPushgatewayProperties(String address, String job, String scheme, String escapingScheme) { this.address = address; this.job = job; this.scheme = scheme; + this.escapingScheme = escapingScheme; } /** Address of the Pushgateway in the form {@code host:port}. Default is {@code localhost:9091} */ @@ -39,6 +42,14 @@ public String getScheme() { return scheme; } + /** + * Escaping scheme to be used when pushing metric data to the pushgateway. + * Valid values: "no-escaping", "values", "underscores", "dots". Default is "no-escaping". + */ + public String getEscapingScheme() { + return escapingScheme; + } + /** * Note that this will remove entries from {@code properties}. This is because we want to know if * there are unused properties remaining after all properties have been loaded. @@ -48,6 +59,8 @@ static ExporterPushgatewayProperties load(Map properties) String address = Util.loadString(PREFIX + "." + ADDRESS, properties); String job = Util.loadString(PREFIX + "." + JOB, properties); String scheme = Util.loadString(PREFIX + "." + SCHEME, properties); + String escapingScheme = Util.loadString(PREFIX + "." + ESCAPING_SCHEME, properties); + if (scheme != null) { if (!scheme.equals("http") && !scheme.equals("https")) { throw new PrometheusPropertiesException( @@ -56,6 +69,17 @@ static ExporterPushgatewayProperties load(Map properties) PREFIX, SCHEME, scheme)); } } - return new ExporterPushgatewayProperties(address, job, scheme); + + if (escapingScheme != null) { + if (!escapingScheme.equals("no-escaping") && !escapingScheme.equals("values") + && !escapingScheme.equals("underscores") && !escapingScheme.equals("dots")) { + throw new PrometheusPropertiesException( + String.format( + "%s.%s: Illegal value. Expecting 'no-escaping', 'values', 'underscores', or 'dots'. Found: %s", + PREFIX, ESCAPING_SCHEME, escapingScheme)); + } + } + + return new ExporterPushgatewayProperties(address, job, scheme, escapingScheme); } } diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 91cd2ea65..632a17307 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -87,6 +87,7 @@ public class PushGateway { private final Map requestHeaders; private final PrometheusRegistry registry; private final HttpConnectionFactory connectionFactory; + private final EscapingScheme escapingScheme; private PushGateway( PrometheusRegistry registry, @@ -94,12 +95,14 @@ private PushGateway( URL url, HttpConnectionFactory connectionFactory, Map requestHeaders, - boolean prometheusTimestampsInMs) { + boolean prometheusTimestampsInMs, + EscapingScheme escapingScheme) { this.registry = registry; this.url = url; this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders)); this.connectionFactory = connectionFactory; this.prometheusTimestampsInMs = prometheusTimestampsInMs; + this.escapingScheme = escapingScheme; writer = getWriter(format); if (!writer.isAvailable()) { throw new RuntimeException(writer.getClass() + " is not available"); @@ -209,7 +212,7 @@ private void doRequest(PrometheusRegistry registry, String method) throws IOExce try { if (!method.equals("DELETE")) { OutputStream outputStream = connection.getOutputStream(); - writer.write(outputStream, registry.scrape(), EscapingScheme.NO_ESCAPING); + writer.write(outputStream, registry.scrape(), this.escapingScheme); outputStream.flush(); outputStream.close(); } @@ -435,6 +438,20 @@ private String getJob(ExporterPushgatewayProperties properties) { } } + private EscapingScheme getEscapingScheme(ExporterPushgatewayProperties properties) { + if (properties != null && properties.getEscapingScheme() != null) { + String scheme = properties.getEscapingScheme(); + switch (scheme) { + case "no-escaping": return EscapingScheme.NO_ESCAPING; + case "values": return EscapingScheme.VALUE_ENCODING_ESCAPING; + case "underscores": return EscapingScheme.UNDERSCORE_ESCAPING; + case "dots": return EscapingScheme.DOTS_ESCAPING; + default: return EscapingScheme.NO_ESCAPING; + } + } + return EscapingScheme.NO_ESCAPING; + } + private Format getFormat() { // currently not configurable via properties if (this.format != null) { @@ -483,7 +500,8 @@ public PushGateway build() { makeUrl(properties), connectionFactory, requestHeaders, - getPrometheusTimestampsInMs()); + getPrometheusTimestampsInMs(), + getEscapingScheme(properties)); } catch (MalformedURLException e) { throw new PrometheusPropertiesException( address + ": Invalid address. Expecting :"); diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 663430e1f..99d0d9721 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -8,12 +8,16 @@ import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.config.PrometheusPropertiesLoader; import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.URL; +import java.util.Properties; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.ValidationScheme; @@ -448,4 +452,18 @@ public void testInstanceIpEscapedGroupingKey() throws IOException { pg.delete(); } } + + @Test + public void testEscapingSchemeDefaultValue() throws IllegalAccessException, NoSuchFieldException { + PushGateway pg = PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("test") + .build(); + + Field escapingSchemeField = pg.getClass().getDeclaredField("escapingScheme"); + escapingSchemeField.setAccessible(true); + EscapingScheme scheme = (EscapingScheme) escapingSchemeField.get(pg); + + assertThat(scheme).isEqualTo(EscapingScheme.NO_ESCAPING); + } } From 2f5bd9c68b2363d37fbe2ee0d61e89bed017fb8b Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 7 Aug 2025 16:19:04 -0300 Subject: [PATCH 015/106] Formatting Signed-off-by: Federico Torres --- .../benchmarks/TextFormatUtilBenchmark.java | 6 +- .../config/ExporterPushgatewayProperties.java | 19 +- .../metrics/config/NamingProperties.java | 6 +- .../common/PrometheusScrapeHandler.java | 8 +- .../exporter/pushgateway/PushGateway.java | 33 ++- .../exporter/pushgateway/PushGatewayTest.java | 174 +++++++------ .../PrometheusProtobufWriterImpl.java | 4 +- .../ExpositionFormatWriter.java | 3 +- .../OpenMetricsTextFormatWriter.java | 9 +- .../PrometheusProtobufWriter.java | 4 +- .../PrometheusTextFormatWriter.java | 15 +- .../expositionformats/TextFormatUtil.java | 9 +- .../ExpositionFormatWriterTest.java | 3 +- .../ExpositionFormatsTest.java | 61 +++-- .../caffeine/CacheMetricsCollectorTest.java | 1 - .../guava/CacheMetricsCollectorTest.java | 1 - .../model/snapshots/EscapingScheme.java | 6 +- .../metrics/model/snapshots/Labels.java | 3 +- .../model/snapshots/MetricMetadata.java | 6 +- .../model/snapshots/PrometheusNaming.java | 242 +++++++++--------- .../model/snapshots/ValidationScheme.java | 2 +- .../model/snapshots/PrometheusNamingTest.java | 169 +++++++----- .../bridge/SimpleclientCollectorTest.java | 3 +- 23 files changed, 442 insertions(+), 345 deletions(-) diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java index 59c370a3a..04352a829 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java @@ -70,7 +70,8 @@ public OutputStream openMetricsWriteToByteArray(WriterState writerState) throws // avoid growing the array ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); - OPEN_METRICS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); + OPEN_METRICS_TEXT_FORMAT_WRITER.write( + byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); return byteArrayOutputStream; } @@ -86,7 +87,8 @@ public OutputStream prometheusWriteToByteArray(WriterState writerState) throws I // avoid growing the array ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); - PROMETHEUS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); + PROMETHEUS_TEXT_FORMAT_WRITER.write( + byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); return byteArrayOutputStream; } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index b1d2e5a12..15ab88686 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -14,7 +14,8 @@ public class ExporterPushgatewayProperties { private final String job; private final String escapingScheme; - private ExporterPushgatewayProperties(String address, String job, String scheme, String escapingScheme) { + private ExporterPushgatewayProperties( + String address, String job, String scheme, String escapingScheme) { this.address = address; this.job = job; this.scheme = scheme; @@ -43,8 +44,8 @@ public String getScheme() { } /** - * Escaping scheme to be used when pushing metric data to the pushgateway. - * Valid values: "no-escaping", "values", "underscores", "dots". Default is "no-escaping". + * Escaping scheme to be used when pushing metric data to the pushgateway. Valid values: + * "no-escaping", "values", "underscores", "dots". Default is "no-escaping". */ public String getEscapingScheme() { return escapingScheme; @@ -60,7 +61,7 @@ static ExporterPushgatewayProperties load(Map properties) String job = Util.loadString(PREFIX + "." + JOB, properties); String scheme = Util.loadString(PREFIX + "." + SCHEME, properties); String escapingScheme = Util.loadString(PREFIX + "." + ESCAPING_SCHEME, properties); - + if (scheme != null) { if (!scheme.equals("http") && !scheme.equals("https")) { throw new PrometheusPropertiesException( @@ -69,17 +70,19 @@ static ExporterPushgatewayProperties load(Map properties) PREFIX, SCHEME, scheme)); } } - + if (escapingScheme != null) { - if (!escapingScheme.equals("no-escaping") && !escapingScheme.equals("values") - && !escapingScheme.equals("underscores") && !escapingScheme.equals("dots")) { + if (!escapingScheme.equals("no-escaping") + && !escapingScheme.equals("values") + && !escapingScheme.equals("underscores") + && !escapingScheme.equals("dots")) { throw new PrometheusPropertiesException( String.format( "%s.%s: Illegal value. Expecting 'no-escaping', 'values', 'underscores', or 'dots'. Found: %s", PREFIX, ESCAPING_SCHEME, escapingScheme)); } } - + return new ExporterPushgatewayProperties(address, job, scheme, escapingScheme); } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java index b8750cc13..7fe5ebc00 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java @@ -16,7 +16,8 @@ public String getValidationScheme() { return validationScheme; } - static NamingProperties load(Map properties) throws PrometheusPropertiesException { + static NamingProperties load(Map properties) + throws PrometheusPropertiesException { String validationScheme = Util.loadString(PREFIX + "." + VALIDATION_SCHEME, properties); return new NamingProperties(validationScheme); } @@ -40,5 +41,4 @@ public NamingProperties build() { return new NamingProperties(validationScheme); } } - -} \ No newline at end of file +} diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index 32efcd456..b89782329 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -20,7 +20,6 @@ import java.util.function.Predicate; import java.util.zip.GZIPOutputStream; - /** Prometheus scrape endpoint. */ public class PrometheusScrapeHandler { @@ -139,7 +138,8 @@ private MetricSnapshots scrape(PrometheusHttpRequest request) { } } - private boolean writeDebugResponse(MetricSnapshots snapshots, EscapingScheme escapingScheme, PrometheusHttpExchange exchange) + private boolean writeDebugResponse( + MetricSnapshots snapshots, EscapingScheme escapingScheme, PrometheusHttpExchange exchange) throws IOException { String debugParam = exchange.getRequest().getParameter("debug"); PrometheusHttpResponse response = exchange.getResponse(); @@ -158,7 +158,9 @@ private boolean writeDebugResponse(MetricSnapshots snapshots, EscapingScheme esc break; case "prometheus-protobuf": String debugString = - expositionFormats.getPrometheusProtobufWriter().toDebugString(snapshots, escapingScheme); + expositionFormats + .getPrometheusProtobufWriter() + .toDebugString(snapshots, escapingScheme); body.write(debugString.getBytes(StandardCharsets.UTF_8)); break; default: diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 632a17307..dc4863b41 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -13,7 +13,6 @@ import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -442,11 +441,16 @@ private EscapingScheme getEscapingScheme(ExporterPushgatewayProperties propertie if (properties != null && properties.getEscapingScheme() != null) { String scheme = properties.getEscapingScheme(); switch (scheme) { - case "no-escaping": return EscapingScheme.NO_ESCAPING; - case "values": return EscapingScheme.VALUE_ENCODING_ESCAPING; - case "underscores": return EscapingScheme.UNDERSCORE_ESCAPING; - case "dots": return EscapingScheme.DOTS_ESCAPING; - default: return EscapingScheme.NO_ESCAPING; + case "no-escaping": + return EscapingScheme.NO_ESCAPING; + case "values": + return EscapingScheme.VALUE_ENCODING_ESCAPING; + case "underscores": + return EscapingScheme.UNDERSCORE_ESCAPING; + case "dots": + return EscapingScheme.DOTS_ESCAPING; + default: + return EscapingScheme.NO_ESCAPING; } } return EscapingScheme.NO_ESCAPING; @@ -472,11 +476,22 @@ private URL makeUrl(ExporterPushgatewayProperties properties) if (groupingKey != null) { for (Map.Entry entry : groupingKey.entrySet()) { if (entry.getValue().isEmpty()) { - url += "/" + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + "@base64/="; + url += + "/" + + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + + "@base64/="; } else if (entry.getValue().contains("/")) { - url += "/" + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + "@base64/" + base64url(entry.getValue()); + url += + "/" + + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + + "@base64/" + + base64url(entry.getValue()); } else { - url += "/" + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + "/" + URLEncoder.encode(entry.getValue(), "UTF-8"); + url += + "/" + + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) + + "/" + + URLEncoder.encode(entry.getValue(), "UTF-8"); } } } diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 99d0d9721..4e0277c1c 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.exporter.pushgateway; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.CALLS_REAL_METHODS; @@ -8,19 +7,15 @@ import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; -import io.prometheus.metrics.config.PrometheusProperties; -import io.prometheus.metrics.config.PrometheusPropertiesLoader; import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.ValidationScheme; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.URL; -import java.util.Properties; - -import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.ValidationScheme; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -154,20 +149,21 @@ public void testPushWithGroupingKey() throws IOException { @Test public void testPushWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); pg.push(); } } @@ -190,21 +186,22 @@ public void testPushWithMultiGroupingKey() throws IOException { @Test public void testPushWithMultiEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) - .respond(response().withStatusCode(202)); + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .groupingKey("l.2", "v2") - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .groupingKey("l.2", "v2") + .build(); pg.push(); } } @@ -259,20 +256,21 @@ public void testPushCollectorWithGroupingKey() throws IOException { @Test public void testPushCollectorWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); pg.push(gauge); } } @@ -318,20 +316,21 @@ public void testPushAddWithGroupingKey() throws IOException { @Test public void testPushAddWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); mockServerClient - .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .groupingKey("l.1", "v1") - .job("j") - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); pg.pushAdd(); } } @@ -353,20 +352,21 @@ public void testPushAddCollectorWithGroupingKey() throws IOException { @Test public void testPushAddCollectorWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); mockServerClient - .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .groupingKey("l.1", "v1") - .job("j") - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); pg.pushAdd(gauge); } } @@ -397,19 +397,20 @@ public void testDeleteWithGroupingKey() throws IOException { @Test public void testDeleteWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); mockServerClient - .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); + .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("j") - .groupingKey("l.1", "v1") - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .build(); pg.delete(); } } @@ -433,37 +434,42 @@ public void testInstanceIpGroupingKey() throws IOException { @Test public void testInstanceIpEscapedGroupingKey() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); String ip = InetAddress.getLocalHost().getHostAddress(); assertThat(ip).isNotEmpty(); mockServerClient - .when(request().withMethod("DELETE").withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); + .when( + request() + .withMethod("DELETE") + .withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("j") - .groupingKey("l.1", "v1") - .instanceIpGroupingKey() - .build(); + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .instanceIpGroupingKey() + .build(); pg.delete(); } } - @Test + @Test public void testEscapingSchemeDefaultValue() throws IllegalAccessException, NoSuchFieldException { - PushGateway pg = PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("test") - .build(); - + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("test") + .build(); + Field escapingSchemeField = pg.getClass().getDeclaredField("escapingScheme"); escapingSchemeField.setAccessible(true); EscapingScheme scheme = (EscapingScheme) escapingSchemeField.get(pg); - + assertThat(scheme).isEqualTo(EscapingScheme.NO_ESCAPING); } } diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index e24ba310f..114fdb502 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -52,7 +52,9 @@ public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme esca } @Override - public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException { + public void write( + OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + throws IOException { for (MetricSnapshot s : metricSnapshots) { MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java index 56c7da238..f0df429ab 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java @@ -10,7 +10,8 @@ public interface ExpositionFormatWriter { boolean accepts(String acceptHeader); /** Text formats use UTF-8 encoding. */ - void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException; + void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + throws IOException; default String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) { ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 3900faefc..6c5c32b1d 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -1,7 +1,8 @@ package io.prometheus.metrics.expositionformats; -import io.prometheus.metrics.model.snapshots.*; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; +import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; @@ -27,8 +28,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; - /** * Write the OpenMetrics text format as defined on https://openmetrics.io. @@ -101,7 +100,9 @@ public String getContentType() { } @Override - public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException { + public void write( + OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + throws IOException { Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); for (MetricSnapshot s : metricSnapshots) { MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java index 16e21cc95..77f439d23 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java @@ -61,7 +61,9 @@ public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme esca } @Override - public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException { + public void write( + OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + throws IOException { checkAvailable(); DELEGATE.write(out, metricSnapshots, escapingScheme); } 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 a18e99cb6..648d766b8 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 @@ -1,7 +1,8 @@ package io.prometheus.metrics.expositionformats; -import io.prometheus.metrics.model.snapshots.*; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; +import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; @@ -23,8 +24,6 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; - /** * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your * Web browser. @@ -100,7 +99,9 @@ public String getContentType() { } @Override - public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException { + public void write( + OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + throws IOException { // See https://prometheus.io/docs/instrumenting/exposition_formats/ // "unknown", "gauge", "counter", "stateset", "info", "histogram", "gaugehistogram", and // "summary". @@ -377,13 +378,15 @@ private void writeMetadata( Writer writer, String suffix, String typeString, MetricMetadata metadata) throws IOException { if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); - writeName(writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); + writeName( + writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); writer.write(' '); writeEscapedHelp(writer, metadata.getHelp()); writer.write('\n'); } writer.write("# TYPE "); - writeName(writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); + writeName( + writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); writer.write(' '); writer.write(typeString); writer.write('\n'); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 980783ed2..36eafc886 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -101,7 +101,11 @@ static void writeEscapedString(Writer writer, String s) throws IOException { } static void writeLabels( - Writer writer, Labels labels, String additionalLabelName, double additionalLabelValue, boolean metricInsideBraces) + Writer writer, + Labels labels, + String additionalLabelName, + double additionalLabelValue, + boolean metricInsideBraces) throws IOException { if (!metricInsideBraces) { writer.write('{'); @@ -136,7 +140,8 @@ static void writeName(Writer writer, String name, NameType nameType) throws IOEx } break; case Label: - if (PrometheusNaming.isValidLegacyLabelName(name) && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { + if (PrometheusNaming.isValidLegacyLabelName(name) + && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { writer.write(name); return; } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java index 943ef9ea4..60bf91ae0 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java @@ -12,7 +12,8 @@ class ExpositionFormatWriterTest { @Test void toDebugString() { - assertThat(writer.toDebugString(new MetricSnapshots(), EscapingScheme.NO_ESCAPING)).isEqualTo("# EOF\n"); + assertThat(writer.toDebugString(new MetricSnapshots(), EscapingScheme.NO_ESCAPING)) + .isEqualTo("# EOF\n"); } @Test diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 3b4bc6012..4beeb9802 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -14,10 +14,7 @@ import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicInteger; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -462,35 +459,38 @@ public void testGaugeWithDots() throws IOException { @Test public void testGaugeUTF8() throws IOException { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + .thenReturn(ValidationScheme.UTF_8_VALIDATION); String prometheusText = - """ + """ # HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing # TYPE \"gauge.name\" gauge {\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf {\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42 """; - GaugeSnapshot gauge = GaugeSnapshot.builder() - .name("gauge.name") - .help("gauge\ndoc\nstr\"ing") - .dataPoint(GaugeDataPointSnapshot.builder() - .value(Double.POSITIVE_INFINITY) - .labels(Labels.builder() - .label("name.1", "val with\nnew line") - .label("name*2", "val with \\backslash and \"quotes\"") - .build()) - .build()) - .dataPoint(GaugeDataPointSnapshot.builder() - .value(3.14e42) - .labels(Labels.builder() - .label("name.1", "Björn") - .label("name*2", "佖佥") - .build()) - .build()) - .build(); + GaugeSnapshot gauge = + GaugeSnapshot.builder() + .name("gauge.name") + .help("gauge\ndoc\nstr\"ing") + .dataPoint( + GaugeDataPointSnapshot.builder() + .value(Double.POSITIVE_INFINITY) + .labels( + Labels.builder() + .label("name.1", "val with\nnew line") + .label("name*2", "val with \\backslash and \"quotes\"") + .build()) + .build()) + .dataPoint( + GaugeDataPointSnapshot.builder() + .value(3.14e42) + .labels( + Labels.builder().label("name.1", "Björn").label("name*2", "佖佥").build()) + .build()) + .build(); assertPrometheusText(prometheusText, gauge); } } @@ -2788,12 +2788,11 @@ public void testFindWriter(String acceptHeaderValue, String expectedFmt) { public void testWrite() throws IOException { ByteArrayOutputStream buff = new ByteArrayOutputStream(new AtomicInteger(2 << 9).get() + 1024); ExpositionFormats expositionFormats = ExpositionFormats.init(); - UnknownSnapshot unknown = UnknownSnapshot.builder() - .name("foo_metric") - .dataPoint(UnknownDataPointSnapshot.builder() - .value(1.234) - .build()) - .build(); + UnknownSnapshot unknown = + UnknownSnapshot.builder() + .name("foo_metric") + .dataPoint(UnknownDataPointSnapshot.builder().value(1.234).build()) + .build(); String acceptHeaderValue = "text/plain; version=0.0.4; charset=utf-8"; EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); @@ -2804,7 +2803,7 @@ public void testWrite() throws IOException { assertThat(out.length).isNotEqualTo(0); String expected = - """ + """ # TYPE foo_metric untyped foo_metric 1.234 """; 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 2d80602bd..f6b6ae482 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 @@ -12,7 +12,6 @@ import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.*; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; 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 95a8a5c2a..edd423d6c 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 @@ -12,7 +12,6 @@ import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.*; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java index a5752956b..cac199ba7 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java @@ -1,7 +1,7 @@ package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.ESCAPING_KEY; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.DEFAULT_ESCAPING_SCHEME; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.ESCAPING_KEY; public enum EscapingScheme { // NO_ESCAPING indicates that a name will not be escaped. @@ -57,7 +57,7 @@ public static EscapingScheme fromAcceptHeader(String acceptHeader) { } private static EscapingScheme forString(String value) { - switch(value) { + switch (value) { case "allow-utf-8": return NO_ESCAPING; case "underscores": @@ -74,4 +74,4 @@ private static EscapingScheme forString(String value) { public String toHeaderFormat() { return "; " + ESCAPING_KEY + "=" + value; } -} \ No newline at end of file +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index eb1b7369c..708220543 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -119,7 +119,8 @@ public static Labels of(String[] names, String[] values) { static String[] makePrometheusNames(String[] names) { String[] prometheusNames = names; for (int i = 0; i < names.length; i++) { - if (names[i].contains(".") && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { + if (names[i].contains(".") + && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { if (prometheusNames == names) { prometheusNames = Arrays.copyOf(names, names.length); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index a6704ef3e..d9c87089b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -53,7 +53,11 @@ public MetricMetadata(String name, String help, Unit unit) { this.help = help; this.unit = unit; validate(); - this.prometheusName = name.contains(".") && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION ? PrometheusNaming.prometheusName(name) : name; + this.prometheusName = + name.contains(".") + && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION + ? PrometheusNaming.prometheusName(name) + : name; } /** diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 1f56cf361..2a754d6e9 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,15 +1,14 @@ package io.prometheus.metrics.model.snapshots; -import io.prometheus.metrics.config.PrometheusProperties; +import static java.lang.Character.*; +import io.prometheus.metrics.config.PrometheusProperties; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.lang.Character.*; - /** * Utility for Prometheus Metric and Label naming. * @@ -20,39 +19,36 @@ public class PrometheusNaming { /** - * nameValidationScheme determines the method of name validation to be used by - * all calls to validateMetricName() and isValidMetricName(). Setting UTF-8 mode - * in isolation from other components that don't support UTF-8 may result in - * bugs or other undefined behavior. This value is intended to be set by - * UTF-8-aware binaries as part of their startup via a properties file. + * nameValidationScheme determines the method of name validation to be used by all calls to + * validateMetricName() and isValidMetricName(). Setting UTF-8 mode in isolation from other + * components that don't support UTF-8 may result in bugs or other undefined behavior. This value + * is intended to be set by UTF-8-aware binaries as part of their startup via a properties file. */ public static final ValidationScheme nameValidationScheme = initValidationScheme(); - /** - * Default escaping scheme for names when not specified. - */ - public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = EscapingScheme.VALUE_ENCODING_ESCAPING; + /** Default escaping scheme for names when not specified. */ + public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = + EscapingScheme.VALUE_ENCODING_ESCAPING; /** - * ESCAPING_KEY is the key in an Accept header that defines how - * metric and label names that do not conform to the legacy character - * requirements should be escaped when being scraped by a legacy Prometheus - * system. If a system does not explicitly pass an escaping parameter in the - * Accept header, the default nameEscapingScheme will be used. + * ESCAPING_KEY is the key in an Accept header that defines how metric and label names that do not + * conform to the legacy character requirements should be escaped when being scraped by a legacy + * Prometheus system. If a system does not explicitly pass an escaping parameter in the Accept + * header, the default nameEscapingScheme will be used. */ public static final String ESCAPING_KEY = "escaping"; - private static final String METRIC_NAME_LABEL= "__name__"; + private static final String METRIC_NAME_LABEL = "__name__"; /** Legal characters for metric names, including dot. */ private static final Pattern LEGACY_METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.:][a-zA-Z0-9_.:]*$"); - private static final Pattern METRIC_NAME_PATTERN = - Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); + private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); /** Legal characters for label names, including dot. */ - private static final Pattern LEGACY_LABEL_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); + private static final Pattern LEGACY_LABEL_NAME_PATTERN = + Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); /** Legal characters for unit names, including dot. */ private static final Pattern UNIT_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_.:]+$"); @@ -78,7 +74,8 @@ public class PrometheusNaming { }; static ValidationScheme initValidationScheme() { - String validationScheme = PrometheusProperties.get().getNamingProperties().getValidationScheme(); + String validationScheme = + PrometheusProperties.get().getNamingProperties().getValidationScheme(); if (validationScheme != null && validationScheme.equals("utf-8")) { return ValidationScheme.UTF_8_VALIDATION; } @@ -87,9 +84,8 @@ static ValidationScheme initValidationScheme() { } /** - * Get the current validation scheme. This method exists primarily to enable - * testing different validation behaviors while keeping the validation scheme - * field final and immutable. + * Get the current validation scheme. This method exists primarily to enable testing different + * validation behaviors while keeping the validation scheme field final and immutable. */ public static ValidationScheme getValidationScheme() { return nameValidationScheme; @@ -124,12 +120,13 @@ public static String validateMetricName(String name) { case LEGACY_VALIDATION: return validateLegacyMetricName(name); case UTF_8_VALIDATION: - if(name.isEmpty() || !StandardCharsets.UTF_8.newEncoder().canEncode(name)) { + if (name.isEmpty() || !StandardCharsets.UTF_8.newEncoder().canEncode(name)) { return "The metric name contains unsupported characters"; } return null; default: - throw new RuntimeException("Invalid name validation scheme requested: " + getValidationScheme()); + throw new RuntimeException( + "Invalid name validation scheme requested: " + getValidationScheme()); } } @@ -157,7 +154,8 @@ public static boolean isValidLegacyMetricName(String name) { case UTF_8_VALIDATION: return METRIC_NAME_PATTERN.matcher(name).matches(); default: - throw new RuntimeException("Invalid name validation scheme requested: " + getValidationScheme()); + throw new RuntimeException( + "Invalid name validation scheme requested: " + getValidationScheme()); } } @@ -168,7 +166,8 @@ public static boolean isValidLabelName(String name) { case UTF_8_VALIDATION: return StandardCharsets.UTF_8.newEncoder().canEncode(name); default: - throw new RuntimeException("Invalid name validation scheme requested: " + getValidationScheme()); + throw new RuntimeException( + "Invalid name validation scheme requested: " + getValidationScheme()); } } @@ -371,10 +370,7 @@ private static String replaceIllegalCharsInUnitName(String name) { return new String(sanitized); } - /** - * Escapes the given metric names and labels with the given - * escaping scheme. - */ + /** Escapes the given metric names and labels with the given escaping scheme. */ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { if (v == null) { return null; @@ -387,7 +383,8 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche String outName; // If the name is null, copy as-is, don't try to escape. - if (v.getMetadata().getPrometheusName() == null || isValidLegacyMetricName(v.getMetadata().getPrometheusName())) { + if (v.getMetadata().getPrometheusName() == null + || isValidLegacyMetricName(v.getMetadata().getPrometheusName())) { outName = v.getMetadata().getPrometheusName(); } else { outName = escapeName(v.getMetadata().getPrometheusName(), scheme); @@ -440,131 +437,143 @@ static boolean metricNeedsEscaping(DataPointSnapshot d) { return false; } - private static DataPointSnapshot createEscapedDataPointSnapshot(MetricSnapshot v, DataPointSnapshot d, Labels outLabels) { + private static DataPointSnapshot createEscapedDataPointSnapshot( + MetricSnapshot v, DataPointSnapshot d, Labels outLabels) { if (v instanceof CounterSnapshot) { return CounterSnapshot.CounterDataPointSnapshot.builder() - .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) - .exemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar()) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); + .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) + .exemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); } else if (v instanceof GaugeSnapshot) { return GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) - .exemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar()) - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); + .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) + .exemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar()) + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); } else if (v instanceof HistogramSnapshot) { return HistogramSnapshot.HistogramDataPointSnapshot.builder() - .classicHistogramBuckets(((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) - .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) - .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) - .nativeZeroThreshold(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) - .nativeBucketsForPositiveValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForPositiveValues()) - .nativeBucketsForNegativeValues(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeBucketsForNegativeValues()) - .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) - .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) - .exemplars(((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars()) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); + .classicHistogramBuckets( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) + .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) + .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) + .nativeZeroThreshold( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) + .nativeBucketsForPositiveValues( + ((HistogramSnapshot.HistogramDataPointSnapshot) d) + .getNativeBucketsForPositiveValues()) + .nativeBucketsForNegativeValues( + ((HistogramSnapshot.HistogramDataPointSnapshot) d) + .getNativeBucketsForNegativeValues()) + .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) + .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) + .exemplars(((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); } else if (v instanceof SummarySnapshot) { return SummarySnapshot.SummaryDataPointSnapshot.builder() - .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) - .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) - .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) - .exemplars(((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars()) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); + .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) + .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) + .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) + .exemplars(((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars()) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); } else if (v instanceof InfoSnapshot) { return InfoSnapshot.InfoDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()); + StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()); for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { builder.state(state.getName(), state.isTrue()); } return builder.build(); } else if (v instanceof UnknownSnapshot) { return UnknownSnapshot.UnknownDataPointSnapshot.builder() - .labels(outLabels) - .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) - .exemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); + .labels(outLabels) + .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) + .exemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); } else { throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); } } - private static MetricSnapshot createEscapedMetricSnapshot(MetricSnapshot v, String outName, List outDataPoints) { + private static MetricSnapshot createEscapedMetricSnapshot( + MetricSnapshot v, String outName, List outDataPoints) { if (v instanceof CounterSnapshot) { - CounterSnapshot.Builder builder = CounterSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); + CounterSnapshot.Builder builder = + CounterSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((CounterSnapshot.CounterDataPointSnapshot) d); } return builder.build(); } else if (v instanceof GaugeSnapshot) { - GaugeSnapshot.Builder builder = GaugeSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); + GaugeSnapshot.Builder builder = + GaugeSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((GaugeSnapshot.GaugeDataPointSnapshot) d); } return builder.build(); } else if (v instanceof HistogramSnapshot) { - HistogramSnapshot.Builder builder = HistogramSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()) - .gaugeHistogram(((HistogramSnapshot) v).isGaugeHistogram()); + HistogramSnapshot.Builder builder = + HistogramSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()) + .gaugeHistogram(((HistogramSnapshot) v).isGaugeHistogram()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((HistogramSnapshot.HistogramDataPointSnapshot) d); } return builder.build(); } else if (v instanceof SummarySnapshot) { - SummarySnapshot.Builder builder = SummarySnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); + SummarySnapshot.Builder builder = + SummarySnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((SummarySnapshot.SummaryDataPointSnapshot) d); } return builder.build(); } else if (v instanceof InfoSnapshot) { - InfoSnapshot.Builder builder = InfoSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()); + InfoSnapshot.Builder builder = + InfoSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((InfoSnapshot.InfoDataPointSnapshot) d); } return builder.build(); } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.Builder builder = StateSetSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()); + StateSetSnapshot.Builder builder = + StateSetSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((StateSetSnapshot.StateSetDataPointSnapshot) d); } return builder.build(); } else if (v instanceof UnknownSnapshot) { - UnknownSnapshot.Builder builder = UnknownSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); + UnknownSnapshot.Builder builder = + UnknownSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); for (DataPointSnapshot d : outDataPoints) { builder.dataPoint((UnknownSnapshot.UnknownDataPointSnapshot) d); } @@ -575,10 +584,9 @@ private static MetricSnapshot createEscapedMetricSnapshot(MetricSnapshot v, Stri } /** - * Escapes the incoming name according to the provided escaping - * scheme. Depending on the rules of escaping, this may cause no change in the - * string that is returned (especially NO_ESCAPING, which by definition is a - * noop). This method does not do any validation of the name. + * Escapes the incoming name according to the provided escaping scheme. Depending on the rules of + * escaping, this may cause no change in the string that is returned (especially NO_ESCAPING, + * which by definition is a noop). This method does not do any validation of the name. */ public static String escapeName(String name, EscapingScheme scheme) { if (name.isEmpty()) { @@ -645,9 +653,9 @@ public static String escapeName(String name, EscapingScheme scheme) { } /** - * Unescapes the incoming name according to the provided escaping - * scheme if possible. Some schemes are partially or totally non-roundtripable. - * If any error is encountered, returns the original input. + * Unescapes the incoming name according to the provided escaping scheme if possible. Some schemes + * are partially or totally non-roundtripable. If any error is encountered, returns the original + * input. */ @SuppressWarnings("IncrementInForLoopAndHeader") static String unescapeName(String name, EscapingScheme scheme) { @@ -697,7 +705,7 @@ static String unescapeName(String name, EscapingScheme scheme) { } // Found a closing underscore, convert to a char, check validity, and append. if (escapedName.codePointAt(i) == '_') { - //char utf8Char = (char) utf8Val; + // char utf8Char = (char) utf8Val; foundClosingUnderscore = true; if (!isValidUTF8Char(utf8Val)) { return name; @@ -731,7 +739,11 @@ static String unescapeName(String name, EscapingScheme scheme) { } static boolean isValidLegacyChar(int c, int i) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || (c >= '0' && c <= '9' && i > 0); + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || (c >= '0' && c <= '9' && i > 0); } private static boolean isValidUTF8Char(int c) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java index 4c547bb12..2f365733d 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java @@ -10,4 +10,4 @@ public enum ValidationScheme { // UTF_8_VALIDATION only requires that metric and label names be valid UTF-8 // strings. UTF_8_VALIDATION -} \ No newline at end of file +} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 3afbde588..77241bba4 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -3,8 +3,8 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; @@ -27,7 +27,7 @@ public void testSanitizeMetricName() { @Test public void testSanitizeMetricNameWithUnit() { - + assertThat(prometheusName(sanitizeMetricName("0abc.def", Unit.RATIO))) .isEqualTo("_abc_def_" + Unit.RATIO); assertThat(prometheusName(sanitizeMetricName("___ab.:c0", Unit.RATIO))) @@ -46,7 +46,7 @@ public void testSanitizeMetricNameWithUnit() { @Test public void testSanitizeLabelName() { - + assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); @@ -106,23 +106,27 @@ public void testEmptyUnitName() { public void testMetricNameIsValid() { assertThat(validateMetricName("Avalid_23name")).isNull(); assertThat(validateMetricName("_Avalid_23name")).isNull(); - assertThat(validateMetricName("1valid_23name")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName("1valid_23name")) + .isEqualTo("The metric name contains unsupported characters"); assertThat(validateMetricName("avalid_23name")).isNull(); assertThat(validateMetricName("Ava:lid_23name")).isNull(); - assertThat(validateMetricName("a lid_23name")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName("a lid_23name")) + .isEqualTo("The metric name contains unsupported characters"); assertThat(validateMetricName(":leading_colon")).isNull(); assertThat(validateMetricName("colon:in:the:middle")).isNull(); assertThat(validateMetricName("")).isEqualTo("The metric name contains unsupported characters"); - assertThat(validateMetricName("a\ud800z")).isEqualTo("The metric name contains unsupported characters"); + assertThat(validateMetricName("a\ud800z")) + .isEqualTo("The metric name contains unsupported characters"); } @Test public void testLabelNameIsValid() { - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { // Mock the validation scheme to use UTF-8 validation for this test mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + // These assertions now use UTF-8 validation behavior assertThat(isValidLabelName("Avalid_23name")).isTrue(); assertThat(isValidLabelName("_Avalid_23name")).isTrue(); @@ -134,7 +138,7 @@ public void testLabelNameIsValid() { assertThat(isValidLabelName("colon:in:the:middle")).isTrue(); assertThat(isValidLabelName("a\ud800z")).isFalse(); } - + assertThat(isValidLabelName("Avalid_23name")).isTrue(); assertThat(isValidLabelName("_Avalid_23name")).isTrue(); assertThat(isValidLabelName("1valid_23name")).isFalse(); @@ -183,10 +187,11 @@ public void testEscapeName() { assertThat(got).isEqualTo("no:escaping_required"); // name with dots - needs UTF-8 validation for escaping to occur - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING); assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); @@ -198,10 +203,11 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); @@ -209,10 +215,11 @@ public void testEscapeName() { } // name with dots and underscore - needs UTF-8 validation for escaping to occur - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING); assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); @@ -224,10 +231,11 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); @@ -235,10 +243,11 @@ public void testEscapeName() { } // name with dots and colon - needs UTF-8 validation for escaping to occur - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + got = escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING); assertThat(got).isEqualTo("http_status:sum"); got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); @@ -250,10 +259,11 @@ public void testEscapeName() { got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); assertThat(got).isEqualTo("http.status:sum"); - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - + got = escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U__http_2e_status:sum"); got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); @@ -309,8 +319,6 @@ public void testEscapeName() { assertThat(got).isEqualTo("U__label_20_with_20__100_"); got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("label with Ā"); - - } @Test @@ -342,7 +350,9 @@ public void testValueUnescapeErrors() { assertThat(got).isEqualTo("U__underscores__doubled_"); // giant fake UTF-8 code - got = unescapeName("U__my__hack_2e_attempt_872348732fabdabbab_", EscapingScheme.VALUE_ENCODING_ESCAPING); + got = + unescapeName( + "U__my__hack_2e_attempt_872348732fabdabbab_", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U__my__hack_2e_attempt_872348732fabdabbab_"); // trailing UTF-8 @@ -356,8 +366,6 @@ public void testValueUnescapeErrors() { // surrogate UTF-8 value got = unescapeName("U__bad__utf_D900_", EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got).isEqualTo("U__bad__utf_D900_"); - - } @Test @@ -366,14 +374,19 @@ public void testEscapeMetricSnapshotEmpty() { MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(got.getMetadata().getName()).isEqualTo("empty"); assertThat(original.getMetadata().getName()).isEqualTo("empty"); - } @Test public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { testEscapeMetricSnapshot( - "my_metric", "some_label", "labelvalue", "some help text", - "my_metric", "some_label", "labelvalue", "some help text", + "my_metric", + "some_label", + "labelvalue", + "some help text", + "my_metric", + "some_label", + "labelvalue", + "some help text", EscapingScheme.VALUE_ENCODING_ESCAPING, CounterSnapshot.class); } @@ -381,8 +394,14 @@ public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { @Test public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { testEscapeMetricSnapshot( - "my_metric", "some.label", "labelvalue", "some help text", - "my_metric", "U__some_2e_label", "labelvalue", "some help text", + "my_metric", + "some.label", + "labelvalue", + "some help text", + "my_metric", + "U__some_2e_label", + "labelvalue", + "some help text", EscapingScheme.VALUE_ENCODING_ESCAPING, CounterSnapshot.class); } @@ -390,8 +409,14 @@ public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { @Test public void testEscapeMetricSnapshotCounterEscapingNeeded() { testEscapeMetricSnapshot( - "my.metric", "some?label", "label??value", "some help text", - "U__my_2e_metric", "U__some_3f_label", "label??value", "some help text", + "my.metric", + "some?label", + "label??value", + "some help text", + "U__my_2e_metric", + "U__some_3f_label", + "label??value", + "some help text", EscapingScheme.VALUE_ENCODING_ESCAPING, CounterSnapshot.class); } @@ -399,73 +424,89 @@ public void testEscapeMetricSnapshotCounterEscapingNeeded() { @Test public void testEscapeMetricSnapshotGaugeEscapingNeeded() { testEscapeMetricSnapshot( - "unicode.and.dots.花火", "some_label", "label??value", "some help text", - "unicode_dot_and_dot_dots_dot_____", "some_label", "label??value", "some help text", + "unicode.and.dots.花火", + "some_label", + "label??value", + "some help text", + "unicode_dot_and_dot_dots_dot_____", + "some_label", + "label??value", + "some help text", EscapingScheme.DOTS_ESCAPING, GaugeSnapshot.class); } private void testEscapeMetricSnapshot( - String name, String labelName, String labelValue, String help, - String expectedName, String expectedLabelName, String expectedLabelValue, String expectedHelp, + String name, + String labelName, + String labelValue, + String help, + String expectedName, + String expectedLabelName, + String expectedLabelValue, + String expectedHelp, EscapingScheme escapingScheme, Class snapshotType) { - - try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { + + try (MockedStatic mock = + mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, help, 34.2, snapshotType); + MetricSnapshot original = + createTestSnapshot(name, labelName, labelValue, help, 34.2, snapshotType); MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); assertThat(got.getMetadata().getName()).isEqualTo(expectedName); assertThat(got.getMetadata().getHelp()).isEqualTo(expectedHelp); assertThat(got.getDataPoints().size()).isEqualTo(1); - + DataPointSnapshot escapedData = got.getDataPoints().get(0); - assertThat((Iterable) escapedData.getLabels()).isEqualTo(Labels.builder() - .label("__name__", expectedName) - .label(expectedLabelName, expectedLabelValue) - .build()); + assertThat((Iterable) escapedData.getLabels()) + .isEqualTo( + Labels.builder() + .label("__name__", expectedName) + .label(expectedLabelName, expectedLabelValue) + .build()); assertThat(original.getMetadata().getName()).isEqualTo(name); assertThat(original.getMetadata().getHelp()).isEqualTo(help); assertThat(original.getDataPoints().size()).isEqualTo(1); - + DataPointSnapshot originalData = original.getDataPoints().get(0); - assertThat((Iterable) originalData.getLabels()).isEqualTo(Labels.builder() - .label("__name__", name) - .label(labelName, labelValue) - .build()); + assertThat((Iterable) originalData.getLabels()) + .isEqualTo(Labels.builder().label("__name__", name).label(labelName, labelValue).build()); } } - private MetricSnapshot createTestSnapshot(String name, String labelName, String labelValue, String help, double value, Class snapshotType) { - Labels labels = Labels.builder() - .label("__name__", name) - .label(labelName, labelValue) - .build(); + private MetricSnapshot createTestSnapshot( + String name, + String labelName, + String labelValue, + String help, + double value, + Class snapshotType) { + Labels labels = Labels.builder().label("__name__", name).label(labelName, labelValue).build(); if (snapshotType.equals(CounterSnapshot.class)) { return CounterSnapshot.builder() .name(name) .help(help) - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder() - .value(value) - .labels(labels) - .build()) + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .value(value) + .labels(labels) + .build()) .build(); } else if (snapshotType.equals(GaugeSnapshot.class)) { return GaugeSnapshot.builder() .name(name) .help(help) - .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(value) - .labels(labels) - .build()) + .dataPoint( + GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value).labels(labels).build()) .build(); } - + throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); } } diff --git a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java index e21c7b5ba..76cb3f67c 100644 --- a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java +++ b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java @@ -12,6 +12,7 @@ import io.prometheus.client.exporter.common.TextFormat; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; @@ -20,8 +21,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; - -import io.prometheus.metrics.model.snapshots.EscapingScheme; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; From 861b8a28ad43ad9b4d301e03bc3aa93516612bfc Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 7 Aug 2025 16:21:33 -0300 Subject: [PATCH 016/106] Update comment Signed-off-by: Federico Torres --- .../io/prometheus/metrics/model/snapshots/PrometheusNaming.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 2a754d6e9..57f165e3d 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -34,7 +34,7 @@ public class PrometheusNaming { * ESCAPING_KEY is the key in an Accept header that defines how metric and label names that do not * conform to the legacy character requirements should be escaped when being scraped by a legacy * Prometheus system. If a system does not explicitly pass an escaping parameter in the Accept - * header, the default nameEscapingScheme will be used. + * header, the default escaping scheme will be used. */ public static final String ESCAPING_KEY = "escaping"; From b98e0544d13f33ba494a8bbf97eeb1613ea7252e Mon Sep 17 00:00:00 2001 From: Federico Torres Date: Thu, 7 Aug 2025 16:46:31 -0300 Subject: [PATCH 017/106] Remove variable reassignment in escaping tests Signed-off-by: Federico Torres --- .../model/snapshots/PrometheusNamingTest.java | 326 +++++++++++------- 1 file changed, 197 insertions(+), 129 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 77241bba4..4c083dc34 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -153,38 +153,52 @@ public void testLabelNameIsValid() { @Test public void testEscapeName() { // empty string - String got = escapeName("", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEmpty(); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEmpty(); - - got = escapeName("", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEmpty(); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEmpty(); - - got = escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEmpty(); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEmpty(); + assertThat(escapeName("", EscapingScheme.UNDERSCORE_ESCAPING)).isEmpty(); + assertThat( + unescapeName( + escapeName("", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEmpty(); + + assertThat(escapeName("", EscapingScheme.DOTS_ESCAPING)).isEmpty(); + assertThat( + unescapeName( + escapeName("", EscapingScheme.DOTS_ESCAPING), EscapingScheme.DOTS_ESCAPING)) + .isEmpty(); + + assertThat(escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING)).isEmpty(); + assertThat( + unescapeName( + escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEmpty(); // legacy valid name - got = escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("no:escaping_required"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("no:escaping_required"); + assertThat(escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("no:escaping_required"); + assertThat( + unescapeName( + escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("no:escaping_required"); - got = escapeName("no:escaping_required", EscapingScheme.DOTS_ESCAPING); // Dots escaping will escape underscores even though it's not strictly // necessary for compatibility. - assertThat(got).isEqualTo("no:escaping__required"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("no:escaping_required"); - - got = escapeName("no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("no:escaping_required"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("no:escaping_required"); + assertThat(escapeName("no:escaping_required", EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("no:escaping__required"); + assertThat( + unescapeName( + escapeName("no:escaping_required", EscapingScheme.DOTS_ESCAPING), + EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("no:escaping_required"); + + assertThat(escapeName("no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("no:escaping_required"); + assertThat( + unescapeName( + escapeName("no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("no:escaping_required"); // name with dots - needs UTF-8 validation for escaping to occur try (MockedStatic mock = @@ -192,26 +206,35 @@ public void testEscapeName() { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load"); + assertThat(escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("mysystem_prod_west_cpu_load"); + assertThat( + unescapeName( + escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("mysystem_prod_west_cpu_load"); } - got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); + assertThat(escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load"); + assertThat( + unescapeName( + escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING), + EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("mysystem.prod.west.cpu.load"); try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - got = escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("mysystem.prod.west.cpu.load"); + assertThat(escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); + assertThat( + unescapeName( + escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("mysystem.prod.west.cpu.load"); } // name with dots and underscore - needs UTF-8 validation for escaping to occur @@ -220,26 +243,40 @@ public void testEscapeName() { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("mysystem_prod_west_cpu_load_total"); + assertThat( + escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("mysystem_prod_west_cpu_load_total"); + assertThat( + unescapeName( + escapeName( + "mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("mysystem_prod_west_cpu_load_total"); } - got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); + assertThat(escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"); + assertThat( + unescapeName( + escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING), + EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("mysystem.prod.west.cpu.load_total"); try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - got = escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("mysystem.prod.west.cpu.load_total"); + assertThat( + escapeName( + "mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); + assertThat( + unescapeName( + escapeName( + "mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("mysystem.prod.west.cpu.load_total"); } // name with dots and colon - needs UTF-8 validation for escaping to occur @@ -248,124 +285,155 @@ public void testEscapeName() { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - got = escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("http_status:sum"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("http_status:sum"); + assertThat(escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("http_status:sum"); + assertThat( + unescapeName( + escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("http_status:sum"); } - got = escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("http_dot_status:sum"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("http.status:sum"); + assertThat(escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("http_dot_status:sum"); + assertThat( + unescapeName( + escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING), + EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("http.status:sum"); try (MockedStatic mock = mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { mock.when(PrometheusNaming::getValidationScheme) .thenReturn(ValidationScheme.UTF_8_VALIDATION); - got = escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__http_2e_status:sum"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("http.status:sum"); + assertThat(escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__http_2e_status:sum"); + assertThat( + unescapeName( + escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("http.status:sum"); } // name with spaces and emoji - got = escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("label_with__"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("label_with__"); - - got = escapeName("label with 😱", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("label__with____"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("label_with__"); - - got = escapeName("label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__label_20_with_20__1f631_"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("label with 😱"); + assertThat(escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("label_with__"); + assertThat( + unescapeName( + escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("label_with__"); + + assertThat(escapeName("label with 😱", EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("label__with____"); + assertThat( + unescapeName( + escapeName("label with 😱", EscapingScheme.DOTS_ESCAPING), + EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("label_with__"); + + assertThat(escapeName("label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__label_20_with_20__1f631_"); + assertThat( + unescapeName( + escapeName("label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("label with 😱"); // name with unicode characters > 0x100 - got = escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("__"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("__"); - - got = escapeName("花火", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("____"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); + assertThat(escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING)).isEqualTo("__"); + assertThat( + unescapeName( + escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("__"); + + assertThat(escapeName("花火", EscapingScheme.DOTS_ESCAPING)).isEqualTo("____"); // Dots-replacement does not know the difference between two replaced // characters and a single underscore. - assertThat(got).isEqualTo("__"); - - got = escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U___82b1__706b_"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("花火"); + assertThat( + unescapeName( + escapeName("花火", EscapingScheme.DOTS_ESCAPING), EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("__"); + + assertThat(escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U___82b1__706b_"); + assertThat( + unescapeName( + escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("花火"); // name with spaces and edge-case value - got = escapeName("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("label_with__"); - got = unescapeName(got, EscapingScheme.UNDERSCORE_ESCAPING); - assertThat(got).isEqualTo("label_with__"); - - got = escapeName("label with Ā", EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("label__with____"); - got = unescapeName(got, EscapingScheme.DOTS_ESCAPING); - assertThat(got).isEqualTo("label_with__"); - - got = escapeName("label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__label_20_with_20__100_"); - got = unescapeName(got, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("label with Ā"); + assertThat(escapeName("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("label_with__"); + assertThat( + unescapeName( + escapeName("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING), + EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("label_with__"); + + assertThat(escapeName("label with Ā", EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("label__with____"); + assertThat( + unescapeName( + escapeName("label with Ā", EscapingScheme.DOTS_ESCAPING), + EscapingScheme.DOTS_ESCAPING)) + .isEqualTo("label_with__"); + + assertThat(escapeName("label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__label_20_with_20__100_"); + assertThat( + unescapeName( + escapeName("label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING), + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("label with Ā"); } @Test public void testValueUnescapeErrors() { - String got; - // empty string - got = unescapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEmpty(); + assertThat(unescapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING)).isEmpty(); // basic case, no error - got = unescapeName("U__no:unescapingrequired", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("no:unescapingrequired"); + assertThat(unescapeName("U__no:unescapingrequired", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("no:unescapingrequired"); // capitals ok, no error - got = unescapeName("U__capitals_2E_ok", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("capitals.ok"); + assertThat(unescapeName("U__capitals_2E_ok", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("capitals.ok"); // underscores, no error - got = unescapeName("U__underscores__doubled__", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("underscores_doubled_"); + assertThat(unescapeName("U__underscores__doubled__", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("underscores_doubled_"); // invalid single underscore - got = unescapeName("U__underscores_doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__underscores_doubled_"); + assertThat(unescapeName("U__underscores_doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__underscores_doubled_"); // invalid single underscore, 2 - got = unescapeName("U__underscores__doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__underscores__doubled_"); + assertThat(unescapeName("U__underscores__doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__underscores__doubled_"); // giant fake UTF-8 code - got = - unescapeName( - "U__my__hack_2e_attempt_872348732fabdabbab_", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__my__hack_2e_attempt_872348732fabdabbab_"); + assertThat( + unescapeName( + "U__my__hack_2e_attempt_872348732fabdabbab_", + EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__my__hack_2e_attempt_872348732fabdabbab_"); // trailing UTF-8 - got = unescapeName("U__my__hack_2e", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__my__hack_2e"); + assertThat(unescapeName("U__my__hack_2e", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__my__hack_2e"); // invalid UTF-8 value - got = unescapeName("U__bad__utf_2eg_", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__bad__utf_2eg_"); + assertThat(unescapeName("U__bad__utf_2eg_", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__bad__utf_2eg_"); // surrogate UTF-8 value - got = unescapeName("U__bad__utf_D900_", EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got).isEqualTo("U__bad__utf_D900_"); + assertThat(unescapeName("U__bad__utf_D900_", EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo("U__bad__utf_D900_"); } @Test From 263d805c3bee1021162e593a6402ce194e4698db Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 09:40:21 +0200 Subject: [PATCH 018/106] use string builder for performance Signed-off-by: Gregor Zeitlinger --- .../exporter/pushgateway/PushGateway.java | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index dc4863b41..e43b14f08 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,8 +1,5 @@ package io.prometheus.metrics.exporter.pushgateway; -import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.*; - import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesException; @@ -13,6 +10,7 @@ import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -32,6 +30,9 @@ import java.util.Map; import java.util.TreeMap; +import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; + /** * Export metrics via the Prometheus * Pushgateway @@ -466,36 +467,34 @@ private Format getFormat() { private URL makeUrl(ExporterPushgatewayProperties properties) throws UnsupportedEncodingException, MalformedURLException { - String url = getScheme(properties) + "://" + getAddress(properties) + "/metrics/"; + StringBuilder url = + new StringBuilder(getScheme(properties) + "://" + getAddress(properties) + "/metrics/"); String job = getJob(properties); if (job.contains("/")) { - url += "job@base64/" + base64url(job); + url.append("job@base64/").append(base64url(job)); } else { - url += "job/" + URLEncoder.encode(job, "UTF-8"); + url.append("job/").append(URLEncoder.encode(job, "UTF-8")); } if (groupingKey != null) { for (Map.Entry entry : groupingKey.entrySet()) { if (entry.getValue().isEmpty()) { - url += - "/" - + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) - + "@base64/="; + url.append("/") + .append(escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING)) + .append("@base64/="); } else if (entry.getValue().contains("/")) { - url += - "/" - + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) - + "@base64/" - + base64url(entry.getValue()); + url.append("/") + .append(escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING)) + .append("@base64/") + .append(base64url(entry.getValue())); } else { - url += - "/" - + escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING) - + "/" - + URLEncoder.encode(entry.getValue(), "UTF-8"); + url.append("/") + .append(escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING)) + .append("/") + .append(URLEncoder.encode(entry.getValue(), "UTF-8")); } } } - return URI.create(url).normalize().toURL(); + return URI.create(url.toString()).normalize().toURL(); } private String base64url(String v) { From 27362ee6fc3782c2ca0c4e6fa45e78f77df5e82c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 09:43:02 +0200 Subject: [PATCH 019/106] naming Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/PrometheusNaming.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 57f165e3d..893048256 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,14 +1,17 @@ package io.prometheus.metrics.model.snapshots; -import static java.lang.Character.*; - import io.prometheus.metrics.config.PrometheusProperties; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + /** * Utility for Prometheus Metric and Label naming. * @@ -637,7 +640,7 @@ public static String escapeName(String name, EscapingScheme scheme) { escaped.append("__"); } else if (isValidLegacyChar(c, i)) { escaped.appendCodePoint(c); - } else if (!isValidUTF8Char(c)) { + } else if (!isValidUtf8Char(c)) { escaped.append("_FFFD_"); } else { escaped.append('_'); @@ -707,7 +710,7 @@ static String unescapeName(String name, EscapingScheme scheme) { if (escapedName.codePointAt(i) == '_') { // char utf8Char = (char) utf8Val; foundClosingUnderscore = true; - if (!isValidUTF8Char(utf8Val)) { + if (!isValidUtf8Char(utf8Val)) { return name; } unescaped.appendCodePoint(utf8Val); @@ -746,7 +749,7 @@ static boolean isValidLegacyChar(int c, int i) { || (c >= '0' && c <= '9' && i > 0); } - private static boolean isValidUTF8Char(int c) { + private static boolean isValidUtf8Char(int c) { return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); } } From c5de1749d999ce9ae88b4780a89e5341f276f619 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 10:44:13 +0200 Subject: [PATCH 020/106] fix merge Signed-off-by: Gregor Zeitlinger --- .../metrics/config/PrometheusProperties.java | 9 +++++++- .../common/PrometheusScrapeHandler.java | 21 ++++++++++++------- .../exporter/pushgateway/PushGateway.java | 7 +++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index a94c85d99..49b03fb16 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -116,6 +116,7 @@ public static class Builder { private ExporterHttpServerProperties exporterHttpServerProperties; private ExporterPushgatewayProperties pushgatewayProperties; private ExporterOpenTelemetryProperties otelConfig; + private NamingProperties namingProperties; private Builder() {} @@ -167,6 +168,11 @@ public Builder exporterOpenTelemetryProperties( return this; } + public Builder namingProperties(NamingProperties namingProperties) { + this.namingProperties = namingProperties; + return this; + } + public PrometheusProperties build() { return new PrometheusProperties( defaultMetricsProperties, @@ -176,7 +182,8 @@ public PrometheusProperties build() { exporterFilterProperties, exporterHttpServerProperties, pushgatewayProperties, - otelConfig); + otelConfig, + namingProperties); } } } diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index 6aa70a162..7510f6978 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -6,6 +6,7 @@ import io.prometheus.metrics.expositionformats.ExpositionFormats; import io.prometheus.metrics.model.registry.MetricNameFilter; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -54,10 +55,11 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { try { PrometheusHttpRequest request = exchange.getRequest(); MetricSnapshots snapshots = scrape(request); - if (writeDebugResponse(snapshots, exchange)) { + String acceptHeader = request.getHeader("Accept"); + EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeader); + if (writeDebugResponse(snapshots, escapingScheme, exchange)) { return; } - String acceptHeader = request.getHeader("Accept"); ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeader); PrometheusHttpResponse response = exchange.getResponse(); response.setHeader("Content-Type", writer.getContentType()); @@ -66,12 +68,12 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { response.setHeader("Content-Encoding", "gzip"); try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.sendHeadersAndGetBody(200, 0))) { - writer.write(gzipOutputStream, snapshots); + writer.write(gzipOutputStream, snapshots, escapingScheme); } } else { ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream(lastResponseSize.get() + 1024); - writer.write(responseBuffer, snapshots); + writer.write(responseBuffer, snapshots, escapingScheme); lastResponseSize.set(responseBuffer.size()); int contentLength = responseBuffer.size(); if (contentLength > 0) { @@ -136,7 +138,8 @@ private MetricSnapshots scrape(PrometheusHttpRequest request) { } } - private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExchange exchange) + private boolean writeDebugResponse( + MetricSnapshots snapshots, EscapingScheme escapingScheme, PrometheusHttpExchange exchange) throws IOException { String debugParam = exchange.getRequest().getParameter("debug"); PrometheusHttpResponse response = exchange.getResponse(); @@ -148,14 +151,16 @@ private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExch OutputStream body = response.sendHeadersAndGetBody(responseStatus, 0); switch (debugParam) { case "openmetrics": - expositionFormats.getOpenMetricsTextFormatWriter().write(body, snapshots); + expositionFormats.getOpenMetricsTextFormatWriter().write(body, snapshots, escapingScheme); break; case "text": - expositionFormats.getPrometheusTextFormatWriter().write(body, snapshots); + expositionFormats.getPrometheusTextFormatWriter().write(body, snapshots, escapingScheme); break; case "prometheus-protobuf": String debugString = - expositionFormats.getPrometheusProtobufWriter().toDebugString(snapshots); + expositionFormats + .getPrometheusProtobufWriter() + .toDebugString(snapshots, escapingScheme); body.write(debugString.getBytes(StandardCharsets.UTF_8)); break; default: diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index e43b14f08..596657340 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,5 +1,8 @@ package io.prometheus.metrics.exporter.pushgateway; +import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; + import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesException; @@ -10,7 +13,6 @@ import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -30,9 +32,6 @@ import java.util.Map; import java.util.TreeMap; -import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; - /** * Export metrics via the Prometheus * Pushgateway From 3418c7e8932a40690aea195ab8e54d1aa7ab4b02 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 10:45:14 +0200 Subject: [PATCH 021/106] format Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/PrometheusNaming.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 893048256..c303d3fa3 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,17 +1,16 @@ package io.prometheus.metrics.model.snapshots; -import io.prometheus.metrics.config.PrometheusProperties; +import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; +import io.prometheus.metrics.config.PrometheusProperties; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.lang.Character.MAX_CODE_POINT; -import static java.lang.Character.MAX_LOW_SURROGATE; -import static java.lang.Character.MIN_HIGH_SURROGATE; - /** * Utility for Prometheus Metric and Label naming. * From 717dbfb9a2bc500fb3ab95b46982d3d1b2cced21 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 11:40:18 +0200 Subject: [PATCH 022/106] use parameterized tests Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNaming.java | 24 ++-- .../model/snapshots/PrometheusNamingTest.java | 113 ++++++++++-------- 2 files changed, 80 insertions(+), 57 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index c303d3fa3..3c3723eca 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,16 +1,18 @@ package io.prometheus.metrics.model.snapshots; -import static java.lang.Character.MAX_CODE_POINT; -import static java.lang.Character.MAX_LOW_SURROGATE; -import static java.lang.Character.MIN_HIGH_SURROGATE; - import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.config.PrometheusPropertiesLoader; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + /** * Utility for Prometheus Metric and Label naming. * @@ -26,7 +28,8 @@ public class PrometheusNaming { * components that don't support UTF-8 may result in bugs or other undefined behavior. This value * is intended to be set by UTF-8-aware binaries as part of their startup via a properties file. */ - public static final ValidationScheme nameValidationScheme = initValidationScheme(); + public static ValidationScheme nameValidationScheme = + initValidationScheme(PrometheusProperties.get()); /** Default escaping scheme for names when not specified. */ public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = @@ -75,16 +78,19 @@ public class PrometheusNaming { ".total", ".created", ".bucket", ".info" }; - static ValidationScheme initValidationScheme() { - String validationScheme = - PrometheusProperties.get().getNamingProperties().getValidationScheme(); - if (validationScheme != null && validationScheme.equals("utf-8")) { + static ValidationScheme initValidationScheme(PrometheusProperties properties) { + if ("utf-8".equals(properties.getNamingProperties().getValidationScheme())) { return ValidationScheme.UTF_8_VALIDATION; } return ValidationScheme.LEGACY_VALIDATION; } + // VisibleForTesting + public static void resetForTest() { + nameValidationScheme = initValidationScheme(PrometheusPropertiesLoader.load()); + } + /** * Get the current validation scheme. This method exists primarily to enable testing different * validation behaviors while keeping the validation scheme field final and immutable. diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 4c083dc34..583651810 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,16 +1,37 @@ package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.mockito.MockedStatic; + +import java.util.stream.Stream; + +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeMetricSnapshot; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeUnitName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.unescapeName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateUnitName; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.mockStatic; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; - class PrometheusNamingTest { + @AfterEach + void tearDown() { + PrometheusNaming.resetForTest(); + } + @Test public void testSanitizeMetricName() { assertThat(prometheusName(sanitizeMetricName("0abc.def"))).isEqualTo("_abc_def"); @@ -27,7 +48,6 @@ public void testSanitizeMetricName() { @Test public void testSanitizeMetricNameWithUnit() { - assertThat(prometheusName(sanitizeMetricName("0abc.def", Unit.RATIO))) .isEqualTo("_abc_def_" + Unit.RATIO); assertThat(prometheusName(sanitizeMetricName("___ab.:c0", Unit.RATIO))) @@ -46,7 +66,6 @@ public void testSanitizeMetricNameWithUnit() { @Test public void testSanitizeLabelName() { - assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); @@ -102,52 +121,50 @@ public void testEmptyUnitName() { .isThrownBy(() -> sanitizeUnitName("")); } - @Test - public void testMetricNameIsValid() { - assertThat(validateMetricName("Avalid_23name")).isNull(); - assertThat(validateMetricName("_Avalid_23name")).isNull(); - assertThat(validateMetricName("1valid_23name")) - .isEqualTo("The metric name contains unsupported characters"); - assertThat(validateMetricName("avalid_23name")).isNull(); - assertThat(validateMetricName("Ava:lid_23name")).isNull(); - assertThat(validateMetricName("a lid_23name")) - .isEqualTo("The metric name contains unsupported characters"); - assertThat(validateMetricName(":leading_colon")).isNull(); - assertThat(validateMetricName("colon:in:the:middle")).isNull(); - assertThat(validateMetricName("")).isEqualTo("The metric name contains unsupported characters"); - assertThat(validateMetricName("a\ud800z")) - .isEqualTo("The metric name contains unsupported characters"); + @SuppressWarnings("unused") + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") + @ParameterizedTest + @MethodSource("testLabelNameIsValid") + public void testLabelNameIsValidUtf8( + String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { + PrometheusNaming.resetForTest(); + assertMetricName(labelName, utf8Valid); + // for some reason, an empty label name is considered valid in UTF-8 validation + assertLabelName(labelName, utf8Valid || labelName.isEmpty()); } - @Test - public void testLabelNameIsValid() { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - // Mock the validation scheme to use UTF-8 validation for this test - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); + @SuppressWarnings("unused") + @ParameterizedTest + @MethodSource("testLabelNameIsValid") + public void testLabelNameIsValidLegacy( + String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { + assertMetricName(labelName, legacyCharsetValid); + assertLabelName(labelName, legacyValid); + } - // These assertions now use UTF-8 validation behavior - assertThat(isValidLabelName("Avalid_23name")).isTrue(); - assertThat(isValidLabelName("_Avalid_23name")).isTrue(); - assertThat(isValidLabelName("1valid_23name")).isTrue(); - assertThat(isValidLabelName("avalid_23name")).isTrue(); - assertThat(isValidLabelName("Ava:lid_23name")).isTrue(); - assertThat(isValidLabelName("a lid_23name")).isTrue(); - assertThat(isValidLabelName(":leading_colon")).isTrue(); - assertThat(isValidLabelName("colon:in:the:middle")).isTrue(); - assertThat(isValidLabelName("a\ud800z")).isFalse(); - } + private static void assertLabelName(String labelName, boolean legacyValid) { + assertThat(isValidLabelName(labelName)).describedAs("isValidLabelName(%s)", labelName) + .isEqualTo(legacyValid); + } + + private static void assertMetricName(String labelName, boolean valid) { + assertThat(validateMetricName(labelName)).describedAs("validateMetricName(%s)", labelName) + .isEqualTo(valid ? null : "The metric name contains unsupported characters"); + } - assertThat(isValidLabelName("Avalid_23name")).isTrue(); - assertThat(isValidLabelName("_Avalid_23name")).isTrue(); - assertThat(isValidLabelName("1valid_23name")).isFalse(); - assertThat(isValidLabelName("avalid_23name")).isTrue(); - assertThat(isValidLabelName("Ava:lid_23name")).isFalse(); - assertThat(isValidLabelName("a lid_23name")).isFalse(); - assertThat(isValidLabelName(":leading_colon")).isFalse(); - assertThat(isValidLabelName("colon:in:the:middle")).isFalse(); - assertThat(isValidLabelName("a\ud800z")).isFalse(); + static Stream testLabelNameIsValid() { + return Stream.of( + Arguments.of("", false, false, false), + Arguments.of("Avalid_23name", true, true, true), + Arguments.of("_Avalid_23name", true, true, true), + Arguments.of("1valid_23name", false, true, false), + Arguments.of("avalid_23name", true, true, true), + Arguments.of("Ava:lid_23name", false, true, true), + Arguments.of("a lid_23name", false, true, false), + Arguments.of(":leading_colon", false, true, true), + Arguments.of("colon:in:the:middle", false, true, true), + Arguments.of("aΩz", false, true, false), + Arguments.of("a\ud800z", false, false, false)); } @Test From 1a600373416e9073e9ae827bfa9bc846ef9d9d67 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:25:12 +0200 Subject: [PATCH 023/106] use parameterized tests Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNamingTest.java | 366 ++++++------------ 1 file changed, 126 insertions(+), 240 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 583651810..bbc194bce 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -124,7 +124,7 @@ public void testEmptyUnitName() { @SuppressWarnings("unused") @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @ParameterizedTest - @MethodSource("testLabelNameIsValid") + @MethodSource("nameIsValid") public void testLabelNameIsValidUtf8( String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { PrometheusNaming.resetForTest(); @@ -135,7 +135,7 @@ public void testLabelNameIsValidUtf8( @SuppressWarnings("unused") @ParameterizedTest - @MethodSource("testLabelNameIsValid") + @MethodSource("nameIsValid") public void testLabelNameIsValidLegacy( String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { assertMetricName(labelName, legacyCharsetValid); @@ -143,16 +143,18 @@ public void testLabelNameIsValidLegacy( } private static void assertLabelName(String labelName, boolean legacyValid) { - assertThat(isValidLabelName(labelName)).describedAs("isValidLabelName(%s)", labelName) + assertThat(isValidLabelName(labelName)) + .describedAs("isValidLabelName(%s)", labelName) .isEqualTo(legacyValid); } private static void assertMetricName(String labelName, boolean valid) { - assertThat(validateMetricName(labelName)).describedAs("validateMetricName(%s)", labelName) + assertThat(validateMetricName(labelName)) + .describedAs("validateMetricName(%s)", labelName) .isEqualTo(valid ? null : "The metric name contains unsupported characters"); } - static Stream testLabelNameIsValid() { + static Stream nameIsValid() { return Stream.of( Arguments.of("", false, false, false), Arguments.of("Avalid_23name", true, true, true), @@ -167,245 +169,129 @@ static Stream testLabelNameIsValid() { Arguments.of("a\ud800z", false, false, false)); } - @Test - public void testEscapeName() { - // empty string - assertThat(escapeName("", EscapingScheme.UNDERSCORE_ESCAPING)).isEmpty(); - assertThat( - unescapeName( - escapeName("", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEmpty(); - - assertThat(escapeName("", EscapingScheme.DOTS_ESCAPING)).isEmpty(); - assertThat( - unescapeName( - escapeName("", EscapingScheme.DOTS_ESCAPING), EscapingScheme.DOTS_ESCAPING)) - .isEmpty(); - - assertThat(escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING)).isEmpty(); - assertThat( - unescapeName( - escapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEmpty(); - - // legacy valid name - assertThat(escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("no:escaping_required"); - assertThat( - unescapeName( - escapeName("no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("no:escaping_required"); - - // Dots escaping will escape underscores even though it's not strictly - // necessary for compatibility. - assertThat(escapeName("no:escaping_required", EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("no:escaping__required"); - assertThat( - unescapeName( - escapeName("no:escaping_required", EscapingScheme.DOTS_ESCAPING), - EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("no:escaping_required"); - - assertThat(escapeName("no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("no:escaping_required"); - assertThat( - unescapeName( - escapeName("no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("no:escaping_required"); - - // name with dots - needs UTF-8 validation for escaping to occur - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - assertThat(escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("mysystem_prod_west_cpu_load"); - assertThat( - unescapeName( - escapeName("mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("mysystem_prod_west_cpu_load"); - } - - assertThat(escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load"); - assertThat( - unescapeName( - escapeName("mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING), - EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("mysystem.prod.west.cpu.load"); - - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - assertThat(escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"); - assertThat( - unescapeName( - escapeName("mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("mysystem.prod.west.cpu.load"); - } - - // name with dots and underscore - needs UTF-8 validation for escaping to occur - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - assertThat( - escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("mysystem_prod_west_cpu_load_total"); - assertThat( - unescapeName( - escapeName( - "mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("mysystem_prod_west_cpu_load_total"); - } - - assertThat(escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"); - assertThat( - unescapeName( - escapeName("mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING), - EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("mysystem.prod.west.cpu.load_total"); - - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - assertThat( - escapeName( - "mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"); - assertThat( - unescapeName( - escapeName( - "mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("mysystem.prod.west.cpu.load_total"); - } - - // name with dots and colon - needs UTF-8 validation for escaping to occur - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - assertThat(escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("http_status:sum"); - assertThat( - unescapeName( - escapeName("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("http_status:sum"); - } - - assertThat(escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("http_dot_status:sum"); - assertThat( - unescapeName( - escapeName("http.status:sum", EscapingScheme.DOTS_ESCAPING), - EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("http.status:sum"); - - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - assertThat(escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__http_2e_status:sum"); - assertThat( - unescapeName( - escapeName("http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("http.status:sum"); - } - - // name with spaces and emoji - assertThat(escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("label_with__"); - assertThat( - unescapeName( - escapeName("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("label_with__"); - - assertThat(escapeName("label with 😱", EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("label__with____"); - assertThat( - unescapeName( - escapeName("label with 😱", EscapingScheme.DOTS_ESCAPING), - EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("label_with__"); - - assertThat(escapeName("label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__label_20_with_20__1f631_"); - assertThat( - unescapeName( - escapeName("label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("label with 😱"); - - // name with unicode characters > 0x100 - assertThat(escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING)).isEqualTo("__"); - assertThat( - unescapeName( - escapeName("花火", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("__"); - - assertThat(escapeName("花火", EscapingScheme.DOTS_ESCAPING)).isEqualTo("____"); - // Dots-replacement does not know the difference between two replaced - // characters and a single underscore. - assertThat( - unescapeName( - escapeName("花火", EscapingScheme.DOTS_ESCAPING), EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("__"); + @ParameterizedTest + @MethodSource("escapeNameLegacyTestCases") + public void testEscapeNameLegacy( + String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + assertEscape(input, escapingScheme, expected, unescapeExpected); + } - assertThat(escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U___82b1__706b_"); - assertThat( - unescapeName( - escapeName("花火", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("花火"); + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") + @ParameterizedTest + @MethodSource("escapeNameUtf8TestCases") + public void testEscapeNameUtf8(String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + PrometheusNaming.resetForTest(); + assertEscape(input, escapingScheme, expected, unescapeExpected); + } - // name with spaces and edge-case value - assertThat(escapeName("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("label_with__"); - assertThat( - unescapeName( - escapeName("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING), - EscapingScheme.UNDERSCORE_ESCAPING)) - .isEqualTo("label_with__"); + private static void assertEscape( + String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + String escaped = escapeName(input, escapingScheme); + assertThat(escaped).isEqualTo(expected); + assertThat(unescapeName(escaped, escapingScheme)).isEqualTo(unescapeExpected); + } - assertThat(escapeName("label with Ā", EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("label__with____"); - assertThat( - unescapeName( - escapeName("label with Ā", EscapingScheme.DOTS_ESCAPING), - EscapingScheme.DOTS_ESCAPING)) - .isEqualTo("label_with__"); + static Stream escapeNameLegacyTestCases() { + return Stream.of( + Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, "", ""), + Arguments.of("", EscapingScheme.DOTS_ESCAPING, "", ""), + Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, "", ""), + Arguments.of( + "no:escaping_required", + EscapingScheme.UNDERSCORE_ESCAPING, + "no:escaping_required", + "no:escaping_required"), + // Dots escaping will escape underscores even though it's not strictly + // necessary for compatibility. + Arguments.of( + "no:escaping_required", + EscapingScheme.DOTS_ESCAPING, + "no:escaping__required", + "no:escaping_required"), + Arguments.of( + "no:escaping_required", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "no:escaping_required", + "no:escaping_required"), + Arguments.of( + "no:escaping_required", + EscapingScheme.UNDERSCORE_ESCAPING, + "no:escaping_required", + "no:escaping_required"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load", + "mysystem.prod.west.cpu.load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total", + "mysystem.prod.west.cpu.load_total"), + Arguments.of( + "http.status:sum", + EscapingScheme.DOTS_ESCAPING, + "http_dot_status:sum", + "http.status:sum"), + Arguments.of( + "label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), + Arguments.of( + "label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), + Arguments.of( + "label with 😱", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__label_20_with_20__1f631_", + "label with 😱"), + // name with unicode characters > 0x100 + Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__", "__"), + // Dots-replacement does not know the difference between two replaced + Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____", "__"), + Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_", "花火"), + // name with spaces and edge-case value + Arguments.of( + "label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), + Arguments.of( + "label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), + Arguments.of( + "label with Ā", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__label_20_with_20__100_", + "label with Ā")); + } - assertThat(escapeName("label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__label_20_with_20__100_"); - assertThat( - unescapeName( - escapeName("label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING), - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("label with Ā"); + static Stream escapeNameUtf8TestCases() { + return Stream.of( + // name with dots - needs UTF-8 validation for escaping to occur + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load", + "mysystem_prod_west_cpu_load"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load", + "mysystem.prod.west.cpu.load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load_total", + "mysystem_prod_west_cpu_load_total"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total", + "mysystem.prod.west.cpu.load_total"), + Arguments.of( + "http.status:sum", + EscapingScheme.UNDERSCORE_ESCAPING, + "http_status:sum", + "http_status:sum"), + Arguments.of( + "http.status:sum", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__http_2e_status:sum", + "http.status:sum")); } @Test From a1b3226c794dced268d1c6c2efe7fb28ebd35471 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:36:09 +0200 Subject: [PATCH 024/106] use parameterized tests Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNamingTest.java | 80 +++++++++---------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index bbc194bce..af99041b3 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junitpioneer.jupiter.SetSystemProperty; import org.mockito.MockedStatic; @@ -179,7 +180,8 @@ public void testEscapeNameLegacy( @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @ParameterizedTest @MethodSource("escapeNameUtf8TestCases") - public void testEscapeNameUtf8(String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + public void testEscapeNameUtf8( + String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { PrometheusNaming.resetForTest(); assertEscape(input, escapingScheme, expected, unescapeExpected); } @@ -294,49 +296,39 @@ static Stream escapeNameUtf8TestCases() { "http.status:sum")); } - @Test - public void testValueUnescapeErrors() { - // empty string - assertThat(unescapeName("", EscapingScheme.VALUE_ENCODING_ESCAPING)).isEmpty(); - - // basic case, no error - assertThat(unescapeName("U__no:unescapingrequired", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("no:unescapingrequired"); - - // capitals ok, no error - assertThat(unescapeName("U__capitals_2E_ok", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("capitals.ok"); - - // underscores, no error - assertThat(unescapeName("U__underscores__doubled__", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("underscores_doubled_"); - - // invalid single underscore - assertThat(unescapeName("U__underscores_doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__underscores_doubled_"); - - // invalid single underscore, 2 - assertThat(unescapeName("U__underscores__doubled_", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__underscores__doubled_"); - - // giant fake UTF-8 code - assertThat( - unescapeName( - "U__my__hack_2e_attempt_872348732fabdabbab_", - EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__my__hack_2e_attempt_872348732fabdabbab_"); - - // trailing UTF-8 - assertThat(unescapeName("U__my__hack_2e", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__my__hack_2e"); - - // invalid UTF-8 value - assertThat(unescapeName("U__bad__utf_2eg_", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__bad__utf_2eg_"); - - // surrogate UTF-8 value - assertThat(unescapeName("U__bad__utf_D900_", EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo("U__bad__utf_D900_"); + @ParameterizedTest + @CsvSource( + value = { + // empty string + ",", + // basic case, no error + "U__no:unescapingrequired,no:unescapingrequired", + // capitals ok, no error + "U__capitals_2E_ok,capitals.ok", + // underscores, no error + "U__underscores__doubled__,underscores_doubled_", + // invalid single underscore + "U__underscores_doubled_,U__underscores_doubled_", + // invalid single underscore, 2 + "U__underscores__doubled_,U__underscores__doubled_", + // giant fake UTF-8 code + "U__my__hack_2e_attempt_872348732fabdabbab_,U__my__hack_2e_attempt_872348732fabdabbab_", + // trailing UTF-8 + "U__my__hack_2e,U__my__hack_2e", + // invalid UTF-8 value + "U__bad__utf_2eg_,U__bad__utf_2eg_", + // surrogate UTF-8 value + "U__bad__utf_D900_,U__bad__utf_D900_", + }) + public void testValueUnescapeErrors(String escapedName, String expectedUnescapedName) { + if (escapedName == null) { + escapedName = ""; + } + if (expectedUnescapedName == null) { + expectedUnescapedName = ""; + } + assertThat(unescapeName(escapedName, EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo(expectedUnescapedName); } @Test From 2d11b312d0c4cd471201f998928a477ef231d725 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:41:43 +0200 Subject: [PATCH 025/106] use parameterized tests Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNamingTest.java | 82 +++++++------------ 1 file changed, 31 insertions(+), 51 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index af99041b3..78e7a4c5a 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junitpioneer.jupiter.SetSystemProperty; -import org.mockito.MockedStatic; import java.util.stream.Stream; @@ -23,8 +22,6 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateUnitName; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.CALLS_REAL_METHODS; -import static org.mockito.Mockito.mockStatic; class PrometheusNamingTest { @@ -339,62 +336,58 @@ public void testEscapeMetricSnapshotEmpty() { assertThat(original.getMetadata().getName()).isEqualTo("empty"); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { testEscapeMetricSnapshot( "my_metric", "some_label", "labelvalue", - "some help text", "my_metric", "some_label", "labelvalue", - "some help text", EscapingScheme.VALUE_ENCODING_ESCAPING, CounterSnapshot.class); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { testEscapeMetricSnapshot( "my_metric", "some.label", "labelvalue", - "some help text", "my_metric", "U__some_2e_label", "labelvalue", - "some help text", EscapingScheme.VALUE_ENCODING_ESCAPING, CounterSnapshot.class); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotCounterEscapingNeeded() { testEscapeMetricSnapshot( "my.metric", "some?label", "label??value", - "some help text", "U__my_2e_metric", "U__some_3f_label", "label??value", - "some help text", EscapingScheme.VALUE_ENCODING_ESCAPING, CounterSnapshot.class); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotGaugeEscapingNeeded() { testEscapeMetricSnapshot( "unicode.and.dots.花火", "some_label", "label??value", - "some help text", "unicode_dot_and_dot_dots_dot_____", "some_label", "label??value", - "some help text", EscapingScheme.DOTS_ESCAPING, GaugeSnapshot.class); } @@ -403,70 +396,57 @@ private void testEscapeMetricSnapshot( String name, String labelName, String labelValue, - String help, String expectedName, String expectedLabelName, String expectedLabelValue, - String expectedHelp, EscapingScheme escapingScheme, Class snapshotType) { + PrometheusNaming.resetForTest(); - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - MetricSnapshot original = - createTestSnapshot(name, labelName, labelValue, help, 34.2, snapshotType); - MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); - - assertThat(got.getMetadata().getName()).isEqualTo(expectedName); - assertThat(got.getMetadata().getHelp()).isEqualTo(expectedHelp); - assertThat(got.getDataPoints().size()).isEqualTo(1); - - DataPointSnapshot escapedData = got.getDataPoints().get(0); - assertThat((Iterable) escapedData.getLabels()) - .isEqualTo( - Labels.builder() - .label("__name__", expectedName) - .label(expectedLabelName, expectedLabelValue) - .build()); - - assertThat(original.getMetadata().getName()).isEqualTo(name); - assertThat(original.getMetadata().getHelp()).isEqualTo(help); - assertThat(original.getDataPoints().size()).isEqualTo(1); - - DataPointSnapshot originalData = original.getDataPoints().get(0); - assertThat((Iterable) originalData.getLabels()) - .isEqualTo(Labels.builder().label("__name__", name).label(labelName, labelValue).build()); - } + MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, snapshotType); + MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); + + assertThat(got.getMetadata().getName()).isEqualTo(expectedName); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints()).hasSize(1); + + DataPointSnapshot escapedData = got.getDataPoints().get(0); + assertThat((Iterable) escapedData.getLabels()) + .isEqualTo( + Labels.builder() + .label("__name__", expectedName) + .label(expectedLabelName, expectedLabelValue) + .build()); + + assertThat(original.getMetadata().getName()).isEqualTo(name); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints()).hasSize(1); + + DataPointSnapshot originalData = original.getDataPoints().get(0); + assertThat((Iterable) originalData.getLabels()) + .isEqualTo(Labels.builder().label("__name__", name).label(labelName, labelValue).build()); } private MetricSnapshot createTestSnapshot( String name, String labelName, String labelValue, - String help, - double value, Class snapshotType) { Labels labels = Labels.builder().label("__name__", name).label(labelName, labelValue).build(); if (snapshotType.equals(CounterSnapshot.class)) { return CounterSnapshot.builder() .name(name) - .help(help) + .help("some help text") .dataPoint( - CounterSnapshot.CounterDataPointSnapshot.builder() - .value(value) - .labels(labels) - .build()) + CounterSnapshot.CounterDataPointSnapshot.builder().value(34.2).labels(labels).build()) .build(); } else if (snapshotType.equals(GaugeSnapshot.class)) { return GaugeSnapshot.builder() .name(name) - .help(help) + .help("some help text") .dataPoint( - GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value).labels(labels).build()) + GaugeSnapshot.GaugeDataPointSnapshot.builder().value(34.2).labels(labels).build()) .build(); } From 5734772a43c82414ba77d7d5231c13e4d835dade Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:44:41 +0200 Subject: [PATCH 026/106] avoid mocks Signed-off-by: Gregor Zeitlinger --- .../ExpositionFormatsTest.java | 93 +++++++++++-------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 4beeb9802..e3c066d76 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -1,24 +1,39 @@ package io.prometheus.metrics.expositionformats; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.CALLS_REAL_METHODS; -import static org.mockito.Mockito.mockStatic; - -import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.model.snapshots.Exemplar; +import io.prometheus.metrics.model.snapshots.Exemplars; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.HistogramSnapshot; +import io.prometheus.metrics.model.snapshots.InfoSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.Quantiles; +import io.prometheus.metrics.model.snapshots.StateSetSnapshot; +import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.Unit; +import io.prometheus.metrics.model.snapshots.UnknownSnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.MockedStatic; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; class ExpositionFormatsTest { @@ -85,6 +100,11 @@ class ExpositionFormatsTest { .timestampMillis(1690298864383L) .build(); + @AfterEach + void tearDown() { + PrometheusNaming.resetForTest(); + } + @Test void init() { ExpositionFormats formats = ExpositionFormats.init(); @@ -457,42 +477,37 @@ public void testGaugeWithDots() throws IOException { assertPrometheusProtobuf(prometheusProtobuf, gauge); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testGaugeUTF8() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - String prometheusText = - """ + PrometheusNaming.resetForTest(); + String prometheusText = + """ # HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing # TYPE \"gauge.name\" gauge {\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf {\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42 """; - GaugeSnapshot gauge = - GaugeSnapshot.builder() - .name("gauge.name") - .help("gauge\ndoc\nstr\"ing") - .dataPoint( - GaugeDataPointSnapshot.builder() - .value(Double.POSITIVE_INFINITY) - .labels( - Labels.builder() - .label("name.1", "val with\nnew line") - .label("name*2", "val with \\backslash and \"quotes\"") - .build()) - .build()) - .dataPoint( - GaugeDataPointSnapshot.builder() - .value(3.14e42) - .labels( - Labels.builder().label("name.1", "Björn").label("name*2", "佖佥").build()) - .build()) - .build(); - assertPrometheusText(prometheusText, gauge); - } + GaugeSnapshot gauge = + GaugeSnapshot.builder() + .name("gauge.name") + .help("gauge\ndoc\nstr\"ing") + .dataPoint( + GaugeDataPointSnapshot.builder() + .value(Double.POSITIVE_INFINITY) + .labels( + Labels.builder() + .label("name.1", "val with\nnew line") + .label("name*2", "val with \\backslash and \"quotes\"") + .build()) + .build()) + .dataPoint( + GaugeDataPointSnapshot.builder() + .value(3.14e42) + .labels(Labels.builder().label("name.1", "Björn").label("name*2", "佖佥").build()) + .build()) + .build(); + assertPrometheusText(prometheusText, gauge); } @Test From 5eb637a33d0d0b5a9449050653ee7120148574f1 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:45:06 +0200 Subject: [PATCH 027/106] avoid mocks Signed-off-by: Gregor Zeitlinger --- .../metrics/expositionformats/ExpositionFormatsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index e3c066d76..846bdc366 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -483,10 +483,10 @@ public void testGaugeUTF8() throws IOException { PrometheusNaming.resetForTest(); String prometheusText = """ - # HELP \"gauge.name\" gauge\\ndoc\\nstr\"ing - # TYPE \"gauge.name\" gauge - {\"gauge.name\",\"name*2\"=\"val with \\\\backslash and \\\"quotes\\\"\",\"name.1\"=\"val with\\nnew line\"} +Inf - {\"gauge.name\",\"name*2\"=\"佖佥\",\"name.1\"=\"Björn\"} 3.14E42 + # HELP "gauge.name" gauge\\ndoc\\nstr"ing + # TYPE "gauge.name" gauge + {"gauge.name","name*2"="val with \\\\backslash and \\"quotes\\"","name.1"="val with\\nnew line"} +Inf + {"gauge.name","name*2"="佖佥","name.1"="Björn"} 3.14E42 """; GaugeSnapshot gauge = GaugeSnapshot.builder() From ed474d63ff5cb0e16a29dc564fd83edb5a326843 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:51:11 +0200 Subject: [PATCH 028/106] format Signed-off-by: Gregor Zeitlinger --- .../exporter/pushgateway/PushGatewayTest.java | 249 ++++++++---------- .../ExpositionFormatsTest.java | 13 +- .../model/snapshots/PrometheusNamingTest.java | 19 +- 3 files changed, 127 insertions(+), 154 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 4e0277c1c..a91d85398 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -1,28 +1,26 @@ package io.prometheus.metrics.exporter.pushgateway; -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.CALLS_REAL_METHODS; -import static org.mockito.Mockito.mockStatic; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; - import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.ValidationScheme; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.URL; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; +import org.junitpioneer.jupiter.SetSystemProperty; import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.net.URL; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + class PushGatewayTest { private MockServerClient mockServerClient; @@ -40,6 +38,7 @@ public void setUp() { @AfterEach void tearDown() { mockServerClient.stop(); + PrometheusNaming.resetForTest(); } @Test @@ -147,25 +146,21 @@ public void testPushWithGroupingKey() throws IOException { pg.push(); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .build(); - pg.push(); - } + PrometheusNaming.resetForTest(); + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(); } @Test @@ -184,26 +179,23 @@ public void testPushWithMultiGroupingKey() throws IOException { pg.push(); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushWithMultiEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .groupingKey("l.2", "v2") - .build(); - pg.push(); - } + PrometheusNaming.resetForTest(); + + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .groupingKey("l.2", "v2") + .build(); + pg.push(); } @Test @@ -254,25 +246,21 @@ public void testPushCollectorWithGroupingKey() throws IOException { pg.push(gauge); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushCollectorWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - mockServerClient - .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .job("j") - .groupingKey("l.1", "v1") - .build(); - pg.push(gauge); - } + PrometheusNaming.resetForTest(); + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(gauge); } @Test @@ -314,25 +302,21 @@ public void testPushAddWithGroupingKey() throws IOException { pg.pushAdd(); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushAddWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - mockServerClient - .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .groupingKey("l.1", "v1") - .job("j") - .build(); - pg.pushAdd(); - } + PrometheusNaming.resetForTest(); + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(); } @Test @@ -350,25 +334,22 @@ public void testPushAddCollectorWithGroupingKey() throws IOException { pg.pushAdd(gauge); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushAddCollectorWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - mockServerClient - .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .registry(registry) - .groupingKey("l.1", "v1") - .job("j") - .build(); - pg.pushAdd(gauge); - } + PrometheusNaming.resetForTest(); + + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(gauge); } @Test @@ -395,24 +376,21 @@ public void testDeleteWithGroupingKey() throws IOException { pg.delete(); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testDeleteWithEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - mockServerClient - .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("j") - .groupingKey("l.1", "v1") - .build(); - pg.delete(); - } + PrometheusNaming.resetForTest(); + + mockServerClient + .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.delete(); } @Test @@ -432,30 +410,27 @@ public void testInstanceIpGroupingKey() throws IOException { pg.delete(); } + @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testInstanceIpEscapedGroupingKey() throws IOException { - try (MockedStatic mock = - mockStatic(PrometheusNaming.class, CALLS_REAL_METHODS)) { - mock.when(PrometheusNaming::getValidationScheme) - .thenReturn(ValidationScheme.UTF_8_VALIDATION); - - String ip = InetAddress.getLocalHost().getHostAddress(); - assertThat(ip).isNotEmpty(); - mockServerClient - .when( - request() - .withMethod("DELETE") - .withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) - .respond(response().withStatusCode(202)); - PushGateway pg = - PushGateway.builder() - .address("localhost:" + mockServerClient.getPort()) - .job("j") - .groupingKey("l.1", "v1") - .instanceIpGroupingKey() - .build(); - pg.delete(); - } + PrometheusNaming.resetForTest(); + + String ip = InetAddress.getLocalHost().getHostAddress(); + assertThat(ip).isNotEmpty(); + mockServerClient + .when( + request() + .withMethod("DELETE") + .withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .instanceIpGroupingKey() + .build(); + pg.delete(); } @Test diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 846bdc366..496a629f9 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -1,5 +1,8 @@ package io.prometheus.metrics.expositionformats; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; @@ -22,19 +25,15 @@ import io.prometheus.metrics.model.snapshots.Unit; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junitpioneer.jupiter.SetSystemProperty; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - class ExpositionFormatsTest { private final String exemplar1String = diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 78e7a4c5a..fb416bc1d 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,15 +1,5 @@ package io.prometheus.metrics.model.snapshots; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junitpioneer.jupiter.SetSystemProperty; - -import java.util.stream.Stream; - import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeMetricSnapshot; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; @@ -23,6 +13,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetSystemProperty; + class PrometheusNamingTest { @AfterEach From 81802aa0f0f56471e302a4c70cf2fcb686ca539d Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:51:17 +0200 Subject: [PATCH 029/106] format Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/PrometheusNaming.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 3c3723eca..4f31f17b1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,18 +1,17 @@ package io.prometheus.metrics.model.snapshots; + import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesLoader; - import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.lang.Character.MAX_CODE_POINT; -import static java.lang.Character.MAX_LOW_SURROGATE; -import static java.lang.Character.MIN_HIGH_SURROGATE; - /** * Utility for Prometheus Metric and Label naming. * From 075ce06468c657b0c76284ff9a70db3044a18674 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:51:49 +0200 Subject: [PATCH 030/106] format Signed-off-by: Gregor Zeitlinger --- .../exporter/pushgateway/PushGatewayTest.java | 19 +++++++++---------- .../model/snapshots/PrometheusNaming.java | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index a91d85398..00605b44e 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -1,9 +1,18 @@ package io.prometheus.metrics.exporter.pushgateway; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.net.URL; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -11,16 +20,6 @@ import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.URL; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; - class PushGatewayTest { private MockServerClient mockServerClient; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 4f31f17b1..183941197 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.model.snapshots; - import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_CODE_POINT; import static java.lang.Character.MAX_LOW_SURROGATE; import static java.lang.Character.MIN_HIGH_SURROGATE; From 2949497ddec48718d5e865b1e2240180061e2009 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:53:58 +0200 Subject: [PATCH 031/106] format Signed-off-by: Gregor Zeitlinger --- .../metrics/config/ExporterPushgatewayProperties.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 15ab88686..03325e722 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -78,7 +78,8 @@ static ExporterPushgatewayProperties load(Map properties) && !escapingScheme.equals("dots")) { throw new PrometheusPropertiesException( String.format( - "%s.%s: Illegal value. Expecting 'no-escaping', 'values', 'underscores', or 'dots'. Found: %s", + "%s.%s: Illegal value. Expecting 'no-escaping', 'values', 'underscores', " + + "or 'dots'. Found: %s", PREFIX, ESCAPING_SCHEME, escapingScheme)); } } From 50178497bc9ba363645fb4119e6a260b2986bdaf Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 12:57:04 +0200 Subject: [PATCH 032/106] format Signed-off-by: Gregor Zeitlinger --- .../OpenMetricsTextFormatWriter.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 6c5c32b1d..795254faf 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -1,12 +1,10 @@ package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; - -import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; import io.prometheus.metrics.model.snapshots.DistributionDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; @@ -16,10 +14,12 @@ import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; + import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; @@ -28,6 +28,13 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; + /** * Write the OpenMetrics text format as defined on https://openmetrics.io. From 15e98bb2c54ea67a4f92e7b616d5b544c65622eb Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 13:00:13 +0200 Subject: [PATCH 033/106] format Signed-off-by: Gregor Zeitlinger --- .../OpenMetricsTextFormatWriter.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 795254faf..7e163095c 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -1,5 +1,12 @@ package io.prometheus.metrics.expositionformats; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; + import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; @@ -19,7 +26,6 @@ import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; - import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; @@ -28,13 +34,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; - /** * Write the OpenMetrics text format as defined on https://openmetrics.io. From 3b61ad1ce64f07a1eef781e22111b07b762441ea Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 13:01:45 +0200 Subject: [PATCH 034/106] format Signed-off-by: Gregor Zeitlinger --- .../PrometheusTextFormatWriter.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 648d766b8..6692dab6b 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 @@ -1,11 +1,9 @@ package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.*; - -import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; import io.prometheus.metrics.model.snapshots.InfoSnapshot; @@ -13,10 +11,12 @@ import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; + import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; @@ -24,6 +24,13 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp; + /** * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your * Web browser. From 00835eb6450adbce41441eff7583041fa104385d Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 13:02:35 +0200 Subject: [PATCH 035/106] format Signed-off-by: Gregor Zeitlinger --- .../PrometheusTextFormatWriter.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 6692dab6b..8efc2dab3 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 @@ -1,5 +1,12 @@ package io.prometheus.metrics.expositionformats; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; +import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp; + import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; @@ -16,7 +23,6 @@ import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; - import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; @@ -24,13 +30,6 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; -import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp; - /** * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your * Web browser. From be6bd436455d88a87b27e4a35861f198810cffb0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 17:18:35 +0200 Subject: [PATCH 036/106] cleanup Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNamingTest.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index fb416bc1d..6863d22c4 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,5 +1,15 @@ package io.prometheus.metrics.model.snapshots; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.util.stream.Stream; + import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeMetricSnapshot; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; @@ -13,15 +23,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import java.util.stream.Stream; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junitpioneer.jupiter.SetSystemProperty; - class PrometheusNamingTest { @AfterEach @@ -296,7 +297,7 @@ static Stream escapeNameUtf8TestCases() { @CsvSource( value = { // empty string - ",", + "'',''", // basic case, no error "U__no:unescapingrequired,no:unescapingrequired", // capitals ok, no error @@ -317,12 +318,6 @@ static Stream escapeNameUtf8TestCases() { "U__bad__utf_D900_,U__bad__utf_D900_", }) public void testValueUnescapeErrors(String escapedName, String expectedUnescapedName) { - if (escapedName == null) { - escapedName = ""; - } - if (expectedUnescapedName == null) { - expectedUnescapedName = ""; - } assertThat(unescapeName(escapedName, EscapingScheme.VALUE_ENCODING_ESCAPING)) .isEqualTo(expectedUnescapedName); } From 6498aca4d26bacb8ed478f856c685e7f1dbe11d4 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 18:18:23 +0200 Subject: [PATCH 037/106] be strict about validation scheme validation Signed-off-by: Gregor Zeitlinger --- .../metrics/config/NamingProperties.java | 29 +++++++++++++++---- .../metrics/config/ValidationScheme.java | 16 ++++++++++ .../metrics/config/NamingPropertiesTest.java | 29 +++++++++++++++++++ .../expositionformats/TextFormatUtil.java | 2 +- .../metrics/model/snapshots/Labels.java | 1 + .../model/snapshots/MetricMetadata.java | 2 ++ .../model/snapshots/PrometheusNaming.java | 14 +++------ .../model/snapshots/ValidationScheme.java | 13 --------- .../model/snapshots/PrometheusNamingTest.java | 19 ++++++------ 9 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java create mode 100644 prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java delete mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java index 7fe5ebc00..102f65503 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java @@ -1,25 +1,42 @@ package io.prometheus.metrics.config; import java.util.Map; +import javax.annotation.Nullable; public class NamingProperties { private static final String PREFIX = "io.prometheus.naming"; private static final String VALIDATION_SCHEME = "validationScheme"; - private final String validationScheme; + private final ValidationScheme validationScheme; - private NamingProperties(String validation) { + private NamingProperties(ValidationScheme validation) { this.validationScheme = validation; } - public String getValidationScheme() { + public ValidationScheme getValidationScheme() { return validationScheme; } static NamingProperties load(Map properties) throws PrometheusPropertiesException { String validationScheme = Util.loadString(PREFIX + "." + VALIDATION_SCHEME, properties); - return new NamingProperties(validationScheme); + return new NamingProperties(parseValidationScheme(validationScheme)); + } + + static ValidationScheme parseValidationScheme(@Nullable String scheme) { + if (scheme == null || scheme.isEmpty()) { + return ValidationScheme.LEGACY_VALIDATION; + } + + switch (scheme) { + case "utf-8": + return ValidationScheme.UTF_8_VALIDATION; + case "legacy": + return ValidationScheme.LEGACY_VALIDATION; + default: + throw new PrometheusPropertiesException( + "Unknown validation scheme: " + scheme + ". Valid values are: utf-8, legacy."); + } } public static Builder builder() { @@ -28,11 +45,11 @@ public static Builder builder() { public static class Builder { - private String validationScheme; + private ValidationScheme validationScheme; private Builder() {} - public Builder validation(String validationScheme) { + public Builder validation(ValidationScheme validationScheme) { this.validationScheme = validationScheme; return this; } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java new file mode 100644 index 000000000..c0429256a --- /dev/null +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java @@ -0,0 +1,16 @@ +package io.prometheus.metrics.config; + +/** + * ValidationScheme is an enum for determining how metric and label names will be validated by this + * library. + */ +public enum ValidationScheme { + /** + * LEGACY_VALIDATION is a setting that requires that metric and label names conform to the + * original character requirements. + */ + LEGACY_VALIDATION, + + /** UTF_8_VALIDATION only requires that metric and label names be valid UTF-8 strings. */ + UTF_8_VALIDATION +} diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java new file mode 100644 index 000000000..83548c11c --- /dev/null +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java @@ -0,0 +1,29 @@ +package io.prometheus.metrics.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.junit.jupiter.api.Test; + +class NamingPropertiesTest { + @Test + void testBuilder() { + NamingProperties properties = + NamingProperties.builder().validation(ValidationScheme.UTF_8_VALIDATION).build(); + assertThat(properties.getValidationScheme()).isEqualTo(ValidationScheme.UTF_8_VALIDATION); + } + + @Test + void parseValidationScheme() { + assertThat(NamingProperties.parseValidationScheme("utf-8")) + .isEqualTo(ValidationScheme.UTF_8_VALIDATION); + assertThat(NamingProperties.parseValidationScheme("legacy")) + .isEqualTo(ValidationScheme.LEGACY_VALIDATION); + assertThat(NamingProperties.parseValidationScheme(null)) + .isEqualTo(ValidationScheme.LEGACY_VALIDATION); + assertThatCode(() -> NamingProperties.parseValidationScheme("unknown")) + .isInstanceOf(PrometheusPropertiesException.class) + .hasMessageContaining( + "Unknown validation scheme: unknown. Valid values are: utf-8, legacy."); + } +} diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 36eafc886..330790797 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -1,8 +1,8 @@ package io.prometheus.metrics.expositionformats; +import io.prometheus.metrics.config.ValidationScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.ValidationScheme; import java.io.IOException; import java.io.Writer; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index 708220543..a355f283c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -3,6 +3,7 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import io.prometheus.metrics.config.ValidationScheme; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index d9c87089b..10b99f03c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.ValidationScheme; + /** Immutable container for metric metadata: name, help, unit. */ public final class MetricMetadata { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 183941197..028e6797b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -6,6 +6,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesLoader; +import io.prometheus.metrics.config.ValidationScheme; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -28,7 +29,7 @@ public class PrometheusNaming { * is intended to be set by UTF-8-aware binaries as part of their startup via a properties file. */ public static ValidationScheme nameValidationScheme = - initValidationScheme(PrometheusProperties.get()); + PrometheusProperties.get().getNamingProperties().getValidationScheme(); /** Default escaping scheme for names when not specified. */ public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = @@ -77,17 +78,10 @@ public class PrometheusNaming { ".total", ".created", ".bucket", ".info" }; - static ValidationScheme initValidationScheme(PrometheusProperties properties) { - if ("utf-8".equals(properties.getNamingProperties().getValidationScheme())) { - return ValidationScheme.UTF_8_VALIDATION; - } - - return ValidationScheme.LEGACY_VALIDATION; - } - // VisibleForTesting public static void resetForTest() { - nameValidationScheme = initValidationScheme(PrometheusPropertiesLoader.load()); + nameValidationScheme = + PrometheusPropertiesLoader.load().getNamingProperties().getValidationScheme(); } /** diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java deleted file mode 100644 index 2f365733d..000000000 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ValidationScheme.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.prometheus.metrics.model.snapshots; - -// ValidationScheme is an enum for determining how metric and label names will -// be validated by this library. -public enum ValidationScheme { - // LEGACY_VALIDATION is a setting that requires that metric and label names - // conform to the original character requirements. - LEGACY_VALIDATION, - - // UTF_8_VALIDATION only requires that metric and label names be valid UTF-8 - // strings. - UTF_8_VALIDATION -} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 6863d22c4..151420bc9 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,15 +1,5 @@ package io.prometheus.metrics.model.snapshots; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junitpioneer.jupiter.SetSystemProperty; - -import java.util.stream.Stream; - import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeMetricSnapshot; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; @@ -23,6 +13,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetSystemProperty; + class PrometheusNamingTest { @AfterEach From c5b70bc6928c5696da3c32c5235420ba97d1ae0a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 18:25:20 +0200 Subject: [PATCH 038/106] coverage Signed-off-by: Gregor Zeitlinger --- .../metrics/config/PrometheusPropertiesTest.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java index a01e05248..6cd7bb80e 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.HashMap; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -37,15 +38,20 @@ public void testBuilder() { PrometheusProperties defaults = PrometheusPropertiesLoader.load(new HashMap<>()); PrometheusProperties.Builder builder = PrometheusProperties.builder(); builder.defaultMetricsProperties(defaults.getDefaultMetricProperties()); + builder.metricProperties( + Collections.singletonMap( + "http_duration_seconds", + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.2, 0.5, 1.0).build())); builder.exemplarProperties(defaults.getExemplarProperties()); builder.defaultMetricsProperties(defaults.getDefaultMetricProperties()); builder.exporterFilterProperties(defaults.getExporterFilterProperties()); builder.exporterHttpServerProperties(defaults.getExporterHttpServerProperties()); builder.exporterOpenTelemetryProperties(defaults.getExporterOpenTelemetryProperties()); builder.pushgatewayProperties(defaults.getExporterPushgatewayProperties()); + builder.exporterProperties(defaults.getExporterProperties()); + builder.namingProperties(defaults.getNamingProperties()); PrometheusProperties result = builder.build(); assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties()); - assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties()); assertThat(result.getExemplarProperties()).isSameAs(defaults.getExemplarProperties()); assertThat(result.getExporterFilterProperties()) .isSameAs(defaults.getExporterFilterProperties()); @@ -55,5 +61,11 @@ public void testBuilder() { .isSameAs(defaults.getExporterOpenTelemetryProperties()); assertThat(result.getExporterPushgatewayProperties()) .isSameAs(defaults.getExporterPushgatewayProperties()); + assertThat(result.getMetricProperties("http_duration_seconds")) + .isEqualTo( + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.2, 0.5, 1.0).build()); + assertThat(result.getMetricProperties("unknown_metric")).isNull(); + assertThat(result.getExporterProperties()).isSameAs(defaults.getExporterProperties()); + assertThat(result.getNamingProperties()).isSameAs(defaults.getNamingProperties()); } } From bc859f3e6c75cbcad0ec7715b74aadf5517053eb Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 18:30:54 +0200 Subject: [PATCH 039/106] reject empty labels Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/PrometheusNaming.java | 8 ++++++-- .../metrics/model/snapshots/PrometheusNamingTest.java | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 028e6797b..b84486e74 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -121,7 +121,7 @@ public static String validateMetricName(String name) { case LEGACY_VALIDATION: return validateLegacyMetricName(name); case UTF_8_VALIDATION: - if (name.isEmpty() || !StandardCharsets.UTF_8.newEncoder().canEncode(name)) { + if (!isValidUtf8(name)) { return "The metric name contains unsupported characters"; } return null; @@ -165,13 +165,17 @@ public static boolean isValidLabelName(String name) { case LEGACY_VALIDATION: return isValidLegacyLabelName(name); case UTF_8_VALIDATION: - return StandardCharsets.UTF_8.newEncoder().canEncode(name); + return isValidUtf8(name); default: throw new RuntimeException( "Invalid name validation scheme requested: " + getValidationScheme()); } } + private static boolean isValidUtf8(String name) { + return !name.isEmpty() && StandardCharsets.UTF_8.newEncoder().canEncode(name); + } + public static boolean isValidLegacyLabelName(String name) { return LEGACY_LABEL_NAME_PATTERN.matcher(name).matches() && !(name.startsWith("__") diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 151420bc9..90ec64dbc 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -126,8 +126,7 @@ public void testLabelNameIsValidUtf8( String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { PrometheusNaming.resetForTest(); assertMetricName(labelName, utf8Valid); - // for some reason, an empty label name is considered valid in UTF-8 validation - assertLabelName(labelName, utf8Valid || labelName.isEmpty()); + assertLabelName(labelName, utf8Valid); } @SuppressWarnings("unused") From 4e406d025d7053a564dd095ab7cebf11772b44f3 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 18:34:26 +0200 Subject: [PATCH 040/106] coverage Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/config/PrometheusPropertiesTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java index 6cd7bb80e..18b66fa6f 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java @@ -62,6 +62,7 @@ public void testBuilder() { assertThat(result.getExporterPushgatewayProperties()) .isSameAs(defaults.getExporterPushgatewayProperties()); assertThat(result.getMetricProperties("http_duration_seconds")) + .usingRecursiveComparison() .isEqualTo( MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.2, 0.5, 1.0).build()); assertThat(result.getMetricProperties("unknown_metric")).isNull(); From 01d21c6205e90bbbc32b26cb447c461bd79460f5 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 19:11:17 +0200 Subject: [PATCH 041/106] fix test Signed-off-by: Gregor Zeitlinger --- .../prometheus/metrics/exporter/pushgateway/PushGatewayTest.java | 1 + .../metrics/expositionformats/ExpositionFormatsTest.java | 1 + .../prometheus/metrics/model/snapshots/PrometheusNamingTest.java | 1 + 3 files changed, 3 insertions(+) diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 00605b44e..8e574d5fa 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -37,6 +37,7 @@ public void setUp() { @AfterEach void tearDown() { mockServerClient.stop(); + System.clearProperty("io.prometheus.naming.validationScheme"); PrometheusNaming.resetForTest(); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 496a629f9..af3cbe888 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -101,6 +101,7 @@ class ExpositionFormatsTest { @AfterEach void tearDown() { + System.clearProperty("io.prometheus.naming.validationScheme"); PrometheusNaming.resetForTest(); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 90ec64dbc..56fb61261 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -26,6 +26,7 @@ class PrometheusNamingTest { @AfterEach void tearDown() { + System.clearProperty("io.prometheus.naming.validationScheme"); PrometheusNaming.resetForTest(); } From 109e077c84b03a5c9f2a3889f7e1205b76e60ccc Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 19:20:30 +0200 Subject: [PATCH 042/106] coverage Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/EscapingScheme.java | 2 +- .../model/snapshots/EscapingSchemeTest.java | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java index cac199ba7..9de6a797b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java @@ -56,7 +56,7 @@ public static EscapingScheme fromAcceptHeader(String acceptHeader) { return DEFAULT_ESCAPING_SCHEME; } - private static EscapingScheme forString(String value) { + static EscapingScheme forString(String value) { switch (value) { case "allow-utf-8": return NO_ESCAPING; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java new file mode 100644 index 000000000..7bbd6ed1c --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java @@ -0,0 +1,35 @@ +package io.prometheus.metrics.model.snapshots; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.junit.jupiter.api.Test; + +class EscapingSchemeTest { + + @Test + void forString() { + assertThat(EscapingScheme.forString("allow-utf-8")).isEqualTo(EscapingScheme.NO_ESCAPING); + assertThat(EscapingScheme.forString("underscores")) + .isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(EscapingScheme.forString("dots")).isEqualTo(EscapingScheme.DOTS_ESCAPING); + assertThat(EscapingScheme.forString("values")) + .isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThatCode(() -> EscapingScheme.forString("unknown")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void fromAcceptHeader() { + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=allow-utf-8")) + .isEqualTo(EscapingScheme.NO_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=underscores")) + .isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=dots")) + .isEqualTo(EscapingScheme.DOTS_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=values")) + .isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json")) + .isEqualTo(PrometheusNaming.DEFAULT_ESCAPING_SCHEME); + } +} From cddd8296d931b0267e4515ea41156e2cd046fe59 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 19:51:10 +0200 Subject: [PATCH 043/106] ensure binary compatibility Signed-off-by: Gregor Zeitlinger --- .../expositionformats/ExpositionFormatWriter.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java index f0df429ab..287b4108a 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java @@ -2,6 +2,7 @@ import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -9,10 +10,16 @@ public interface ExpositionFormatWriter { boolean accepts(String acceptHeader); - /** Text formats use UTF-8 encoding. */ + /** Writes the given metric snapshots to the output stream using the specified escaping scheme. */ void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException; + /** Writes the given metric snapshots to the output stream using the default escaping scheme. */ + default void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException { + write(out, metricSnapshots, PrometheusNaming.DEFAULT_ESCAPING_SCHEME); + } + + /** Converts the metric snapshots to a debug string using the specified escaping scheme. */ default String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { @@ -23,6 +30,11 @@ default String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme esc } } + /** Converts the metric snapshots to a debug string using the default escaping scheme. */ + default String toDebugString(MetricSnapshots metricSnapshots) { + return toDebugString(metricSnapshots, PrometheusNaming.DEFAULT_ESCAPING_SCHEME); + } + String getContentType(); /** From f37fe6b7c065fd7eb2c83563f2a346955bf42b12 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 19:51:20 +0200 Subject: [PATCH 044/106] javadoc Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/EscapingScheme.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java index 9de6a797b..783029a9b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java @@ -4,19 +4,23 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.ESCAPING_KEY; public enum EscapingScheme { - // NO_ESCAPING indicates that a name will not be escaped. + /** NO_ESCAPING indicates that a name will not be escaped. */ NO_ESCAPING("allow-utf-8"), - // UNDERSCORE_ESCAPING replaces all legacy-invalid characters with underscores. + /** UNDERSCORE_ESCAPING replaces all legacy-invalid characters with underscores. */ UNDERSCORE_ESCAPING("underscores"), - // DOTS_ESCAPING is similar to UNDERSCORE_ESCAPING, except that dots are - // converted to `_dot_` and pre-existing underscores are converted to `__`. + /** + * DOTS_ESCAPING is similar to UNDERSCORE_ESCAPING, except that dots are converted to `_dot_` and + * pre-existing underscores are converted to `__`. + */ DOTS_ESCAPING("dots"), - // VALUE_ENCODING_ESCAPING prepends the name with `U__` and replaces all invalid - // characters with the Unicode value, surrounded by underscores. Single - // underscores are replaced with double underscores. + /** + * VALUE_ENCODING_ESCAPING prepends the name with `U__` and replaces all invalid characters with + * the Unicode value, surrounded by underscores. Single underscores are replaced with double + * underscores. + */ VALUE_ENCODING_ESCAPING("values"), ; @@ -30,10 +34,11 @@ public final String getValue() { this.value = value; } - // fromAcceptHeader returns an EscapingScheme depending on the Accept header. Iff the - // header contains an escaping=allow-utf-8 term, it will select NO_ESCAPING. If a valid - // "escaping" term exists, that will be used. Otherwise, the global default will - // be returned. + /** + * fromAcceptHeader returns an EscapingScheme depending on the Accept header. Iff the header + * contains an escaping=allow-utf-8 term, it will select NO_ESCAPING. If a valid "escaping" term + * exists, that will be used. Otherwise, the global default will be returned. + */ public static EscapingScheme fromAcceptHeader(String acceptHeader) { if (acceptHeader != null) { for (String p : acceptHeader.split(";")) { From cca01f6ee2ee7560d81107dccc968c1eacc924d5 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 8 Aug 2025 19:52:16 +0200 Subject: [PATCH 045/106] coverage Signed-off-by: Gregor Zeitlinger --- .../ExpositionFormatWriterTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java index 60bf91ae0..9f30868ea 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java @@ -4,16 +4,31 @@ import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import org.junit.jupiter.api.Test; class ExpositionFormatWriterTest { private final ExpositionFormatWriter writer = OpenMetricsTextFormatWriter.create(); + @Test + void write() throws IOException { + MetricSnapshots snapshots = new MetricSnapshots(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); + assertThat(out).hasToString("# EOF\n"); + + out.reset(); + writer.write(out, snapshots); + assertThat(out).hasToString("# EOF\n"); + } + @Test void toDebugString() { assertThat(writer.toDebugString(new MetricSnapshots(), EscapingScheme.NO_ESCAPING)) .isEqualTo("# EOF\n"); + assertThat(writer.toDebugString(new MetricSnapshots())).isEqualTo("# EOF\n"); } @Test From cc6571f3ee2c3dcdfb14fb67896c0521fe194abd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 14:54:19 +0200 Subject: [PATCH 046/106] prom name can't be null Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/PrometheusNaming.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index b84486e74..cee28a2bd 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -385,15 +385,10 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche return v; } - String outName; - - // If the name is null, copy as-is, don't try to escape. - if (v.getMetadata().getPrometheusName() == null - || isValidLegacyMetricName(v.getMetadata().getPrometheusName())) { - outName = v.getMetadata().getPrometheusName(); - } else { - outName = escapeName(v.getMetadata().getPrometheusName(), scheme); - } + String outName = + isValidLegacyMetricName(v.getMetadata().getPrometheusName()) + ? v.getMetadata().getPrometheusName() + : escapeName(v.getMetadata().getPrometheusName(), scheme); List outDataPoints = new ArrayList<>(); From 551d6c6af707685482c5bed9ae4a94837ef05691 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 15:08:15 +0200 Subject: [PATCH 047/106] fix prometheus name check Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/MetricMetadata.java | 15 ++++++--------- .../metrics/model/snapshots/PrometheusNaming.java | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 10b99f03c..858de8ab9 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -1,7 +1,5 @@ package io.prometheus.metrics.model.snapshots; -import io.prometheus.metrics.config.ValidationScheme; - /** Immutable container for metric metadata: name, help, unit. */ public final class MetricMetadata { @@ -20,8 +18,11 @@ public final class MetricMetadata { private final String name; /** - * Same as name, except if name contains dots, then the prometheusName is {@code name.replace(".", - * "_")}. + * Same as name that all invalid char (without Unicode support) are replaced by _ + * + *

Multiple metrics with the same prometheusName are not allowed, because they would end up in + * the same time series in Prometheus if {@link EscapingScheme#UNDERSCORE_ESCAPING} or {@link + * EscapingScheme#DOTS_ESCAPING} is used. */ private final String prometheusName; @@ -54,12 +55,8 @@ public MetricMetadata(String name, String help, Unit unit) { this.name = name; this.help = help; this.unit = unit; + this.prometheusName = PrometheusNaming.prometheusName(name); validate(); - this.prometheusName = - name.contains(".") - && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION - ? PrometheusNaming.prometheusName(name) - : name; } /** diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index cee28a2bd..5400fbbbc 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -218,7 +218,7 @@ public static String validateUnitName(String name) { * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { - return name.replace(".", "_"); + return PrometheusNaming.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); } /** From bd7b29e80aa4107cc56c3070e0745a76eaef517c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 16:09:32 +0200 Subject: [PATCH 048/106] use underscore escaping for backwards compatability Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/model/snapshots/MetricMetadata.java | 2 +- .../prometheus/metrics/model/snapshots/PrometheusNaming.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 858de8ab9..3378b6a63 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -71,7 +71,7 @@ public String getName() { } /** - * Same as {@link #getName()} but with dots replaced by underscores. + * Same as {@link #getName()} but with all invalid characters replaced by underscores. * *

This is used by Prometheus exposition formats. */ diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 5400fbbbc..d42c6f77c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -32,8 +32,7 @@ public class PrometheusNaming { PrometheusProperties.get().getNamingProperties().getValidationScheme(); /** Default escaping scheme for names when not specified. */ - public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = - EscapingScheme.VALUE_ENCODING_ESCAPING; + public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = EscapingScheme.UNDERSCORE_ESCAPING; /** * ESCAPING_KEY is the key in an Accept header that defines how metric and label names that do not From 88f9b8b245835b62150e3da90045e5b8172e06ed Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 16:10:39 +0200 Subject: [PATCH 049/106] fix prometheus name check Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/model/snapshots/PrometheusNaming.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index d42c6f77c..b86563f0b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -217,7 +217,7 @@ public static String validateUnitName(String name) { * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { - return PrometheusNaming.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); + return PrometheusNaming.escapeName(name.replace(".", "_"), EscapingScheme.UNDERSCORE_ESCAPING); } /** From be07075e2389a3e8cac55ff287559263ae802f09 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 16:12:26 +0200 Subject: [PATCH 050/106] fix prometheus name check Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/model/snapshots/MetricMetadata.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 3378b6a63..4b9705f22 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -55,8 +55,8 @@ public MetricMetadata(String name, String help, Unit unit) { this.name = name; this.help = help; this.unit = unit; - this.prometheusName = PrometheusNaming.prometheusName(name); validate(); + this.prometheusName = PrometheusNaming.prometheusName(name); } /** From d47af4eca160443289ccc5ea4e950b90df7517c6 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 16:24:45 +0200 Subject: [PATCH 051/106] fix escaping Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNaming.java | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index b86563f0b..dd2d96c7b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -384,11 +384,6 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche return v; } - String outName = - isValidLegacyMetricName(v.getMetadata().getPrometheusName()) - ? v.getMetadata().getPrometheusName() - : escapeName(v.getMetadata().getPrometheusName(), scheme); - List outDataPoints = new ArrayList<>(); for (DataPointSnapshot d : v.getDataPoints()) { @@ -401,18 +396,10 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche for (Label l : d.getLabels()) { if (METRIC_NAME_LABEL.equals(l.getName())) { - if (l.getValue() == null || isValidLegacyMetricName(l.getValue())) { - outLabelsBuilder.label(l.getName(), l.getValue()); - continue; - } outLabelsBuilder.label(l.getName(), escapeName(l.getValue(), scheme)); - continue; - } - if (l.getName() == null || isValidLegacyMetricName(l.getName())) { - outLabelsBuilder.label(l.getName(), l.getValue()); - continue; + } else { + outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); } - outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); } Labels outLabels = outLabelsBuilder.build(); @@ -420,7 +407,8 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche outDataPoints.add(outDataPointSnapshot); } - return createEscapedMetricSnapshot(v, outName, outDataPoints); + return createEscapedMetricSnapshot( + v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); } static boolean metricNeedsEscaping(DataPointSnapshot d) { @@ -588,9 +576,10 @@ private static MetricSnapshot createEscapedMetricSnapshot( * which by definition is a noop). This method does not do any validation of the name. */ public static String escapeName(String name, EscapingScheme scheme) { - if (name.isEmpty()) { + if (name.isEmpty() || isValidLegacyMetricName(name)) { return name; } + StringBuilder escaped = new StringBuilder(); switch (scheme) { case NO_ESCAPING: From fba059426057a033dea3c5bd6c6abae120218514 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 18:58:49 +0200 Subject: [PATCH 052/106] fix escaping Signed-off-by: Gregor Zeitlinger --- .../PrometheusProtobufWriterImpl.java | 12 ++--- .../OpenMetricsTextFormatWriter.java | 35 +++++++-------- .../PrometheusTextFormatWriter.java | 44 ++++++++----------- .../expositionformats/TextFormatUtil.java | 2 +- 4 files changed, 41 insertions(+), 52 deletions(-) diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index 114fdb502..780c4213d 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -63,7 +63,7 @@ public void write( } } - public Metrics.MetricFamily convert(MetricSnapshot snapshot) { + Metrics.MetricFamily convert(MetricSnapshot snapshot) { Metrics.MetricFamily.Builder builder = Metrics.MetricFamily.newBuilder(); if (snapshot instanceof CounterSnapshot) { for (CounterDataPointSnapshot data : ((CounterSnapshot) snapshot).getDataPoints()) { @@ -102,7 +102,7 @@ public Metrics.MetricFamily convert(MetricSnapshot snapshot) { for (StateSetSnapshot.StateSetDataPointSnapshot data : ((StateSetSnapshot) snapshot).getDataPoints()) { for (int i = 0; i < data.size(); i++) { - builder.addMetric(convert(data, snapshot.getMetadata().getPrometheusName(), i)); + builder.addMetric(convert(data, snapshot.getMetadata().getName(), i)); } } setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE); @@ -272,9 +272,9 @@ private void setMetadataUnlessEmpty( return; } if (nameSuffix == null) { - builder.setName(metadata.getPrometheusName()); + builder.setName(metadata.getName()); } else { - builder.setName(metadata.getPrometheusName() + nameSuffix); + builder.setName(metadata.getName() + nameSuffix); } if (metadata.getHelp() != null) { builder.setHelp(metadata.getHelp()); @@ -351,7 +351,7 @@ private void addLabels(Metrics.Metric.Builder metricBuilder, Labels labels) { for (int i = 0; i < labels.size(); i++) { metricBuilder.addLabel( Metrics.LabelPair.newBuilder() - .setName(labels.getPrometheusName(i)) + .setName(labels.getName(i)) .setValue(labels.getValue(i)) .build()); } @@ -361,7 +361,7 @@ private void addLabels(Metrics.Exemplar.Builder metricBuilder, Labels labels) { for (int i = 0; i < labels.size(); i++) { metricBuilder.addLabel( Metrics.LabelPair.newBuilder() - .setName(labels.getPrometheusName(i)) + .setName(labels.getName(i)) .setValue(labels.getValue(i)) .build()); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 7e163095c..d748bf9d0 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -138,7 +138,7 @@ private void writeCounter(Writer writer, CounterSnapshot snapshot) throws IOExce MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "counter", metadata); for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_total", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_total", data.getLabels()); writeDouble(writer, data.getValue()); writeScrapeTimestampAndExemplar(writer, data, data.getExemplar()); writeCreated(writer, metadata, data); @@ -149,7 +149,7 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot) throws IOExceptio MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "gauge", metadata); for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); writeDouble(writer, data.getValue()); if (exemplarsOnAllMetricTypesEnabled) { writeScrapeTimestampAndExemplar(writer, data, data.getExemplar()); @@ -185,7 +185,7 @@ private void writeClassicHistogramBuckets( cumulativeCount += buckets.getCount(i); writeNameAndLabels( writer, - metadata.getPrometheusName(), + metadata.getName(), "_bucket", data.getLabels(), "le", @@ -237,12 +237,7 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce int exemplarIndex = 1; for (Quantile quantile : data.getQuantiles()) { writeNameAndLabels( - writer, - metadata.getPrometheusName(), - null, - data.getLabels(), - "quantile", - quantile.getQuantile()); + writer, metadata.getName(), null, data.getLabels(), "quantile", quantile.getQuantile()); writeDouble(writer, quantile.getValue()); if (exemplars.size() > 0 && exemplarsOnAllMetricTypesEnabled) { exemplarIndex = (exemplarIndex + 1) % exemplars.size(); @@ -261,7 +256,7 @@ private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "info", metadata); for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_info", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_info", data.getLabels()); writer.write("1"); writeScrapeTimestampAndExemplar(writer, data, null); } @@ -272,13 +267,13 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx writeMetadata(writer, "stateset", metadata); for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { for (int i = 0; i < data.size(); i++) { - writer.write(metadata.getPrometheusName()); + writer.write(metadata.getName()); writer.write('{'); for (int j = 0; j < data.getLabels().size(); j++) { if (j > 0) { writer.write(","); } - writer.write(data.getLabels().getPrometheusName(j)); + writer.write(data.getLabels().getName(j)); writer.write("=\""); writeEscapedString(writer, data.getLabels().getValue(j)); writer.write("\""); @@ -286,7 +281,7 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx if (!data.getLabels().isEmpty()) { writer.write(","); } - writer.write(metadata.getPrometheusName()); + writer.write(metadata.getName()); writer.write("=\""); writeEscapedString(writer, data.getName(i)); writer.write("\"} "); @@ -304,7 +299,7 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOExce MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "unknown", metadata); for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); writeDouble(writer, data.getValue()); if (exemplarsOnAllMetricTypesEnabled) { writeScrapeTimestampAndExemplar(writer, data, data.getExemplar()); @@ -323,7 +318,7 @@ private void writeCountAndSum( Exemplars exemplars) throws IOException { if (data.hasCount()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), countSuffix, data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), countSuffix, data.getLabels()); writeLong(writer, data.getCount()); if (exemplarsOnAllMetricTypesEnabled) { writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest()); @@ -332,7 +327,7 @@ private void writeCountAndSum( } } if (data.hasSum()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), sumSuffix, data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), sumSuffix, data.getLabels()); writeDouble(writer, data.getSum()); writeScrapeTimestampAndExemplar(writer, data, null); } @@ -341,7 +336,7 @@ private void writeCountAndSum( private void writeCreated(Writer writer, MetricMetadata metadata, DataPointSnapshot data) throws IOException { if (createdTimestampsEnabled && data.hasCreatedTimestamp()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_created", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_created", data.getLabels()); writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); if (data.hasScrapeTimestamp()) { writer.write(' '); @@ -402,20 +397,20 @@ private void writeScrapeTimestampAndExemplar( private void writeMetadata(Writer writer, String typeName, MetricMetadata metadata) throws IOException { writer.write("# TYPE "); - writeName(writer, metadata.getPrometheusName(), NameType.Metric); + writeName(writer, metadata.getName(), NameType.Metric); writer.write(' '); writer.write(typeName); writer.write('\n'); if (metadata.getUnit() != null) { writer.write("# UNIT "); - writeName(writer, metadata.getPrometheusName(), NameType.Metric); + writeName(writer, metadata.getName(), NameType.Metric); writer.write(' '); writeEscapedString(writer, metadata.getUnit().toString()); writer.write('\n'); } if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); - writeName(writer, metadata.getPrometheusName(), NameType.Metric); + writeName(writer, metadata.getName(), NameType.Metric); writer.write(' '); writeEscapedString(writer, metadata.getHelp()); writer.write('\n'); 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 8efc2dab3..4b30cad25 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 @@ -158,7 +158,7 @@ public void writeCreated(Writer writer, MetricSnapshot snapshot) throws IOExcept writeMetadata(writer, "_created", "gauge", metadata); metadataWritten = true; } - writeNameAndLabels(writer, metadata.getPrometheusName(), "_created", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_created", data.getLabels()); writePrometheusTimestamp(writer, data.getCreatedTimestampMillis(), timestampsInMs); writeScrapeTimestampAndNewline(writer, data); } @@ -170,7 +170,7 @@ private void writeCounter(Writer writer, CounterSnapshot snapshot) throws IOExce MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "_total", "counter", metadata); for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_total", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_total", data.getLabels()); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } @@ -181,7 +181,7 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot) throws IOExceptio MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "", "gauge", metadata); for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } @@ -197,7 +197,7 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IO cumulativeCount += buckets.getCount(i); writeNameAndLabels( writer, - metadata.getPrometheusName(), + metadata.getName(), "_bucket", data.getLabels(), "le", @@ -207,12 +207,12 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IO } if (!snapshot.isGaugeHistogram()) { if (data.hasCount()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_count", data.getLabels()); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasSum()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_sum", data.getLabels()); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } @@ -244,7 +244,7 @@ private void writeGaugeCountSum( writeMetadata(writer, "_gcount", "gauge", metadata); metadataWritten = true; } - writeNameAndLabels(writer, metadata.getPrometheusName(), "_gcount", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_gcount", data.getLabels()); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } @@ -256,7 +256,7 @@ private void writeGaugeCountSum( writeMetadata(writer, "_gsum", "gauge", metadata); metadataWritten = true; } - writeNameAndLabels(writer, metadata.getPrometheusName(), "_gsum", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_gsum", data.getLabels()); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } @@ -276,22 +276,17 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce } for (Quantile quantile : data.getQuantiles()) { writeNameAndLabels( - writer, - metadata.getPrometheusName(), - null, - data.getLabels(), - "quantile", - quantile.getQuantile()); + writer, metadata.getName(), null, data.getLabels(), "quantile", quantile.getQuantile()); writeDouble(writer, quantile.getValue()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasCount()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_count", data.getLabels()); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasSum()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_sum", data.getLabels()); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } @@ -302,7 +297,7 @@ private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "_info", "gauge", metadata); for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), "_info", data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), "_info", data.getLabels()); writer.write("1"); writeScrapeTimestampAndNewline(writer, data); } @@ -313,13 +308,13 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx writeMetadata(writer, "", "gauge", metadata); for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { for (int i = 0; i < data.size(); i++) { - writer.write(metadata.getPrometheusName()); + writer.write(metadata.getName()); writer.write('{'); for (int j = 0; j < data.getLabels().size(); j++) { if (j > 0) { writer.write(","); } - writer.write(data.getLabels().getPrometheusName(j)); + writer.write(data.getLabels().getName(j)); writer.write("=\""); writeEscapedString(writer, data.getLabels().getValue(j)); writer.write("\""); @@ -327,7 +322,7 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx if (!data.getLabels().isEmpty()) { writer.write(","); } - writer.write(metadata.getPrometheusName()); + writer.write(metadata.getName()); writer.write("=\""); writeEscapedString(writer, data.getName(i)); writer.write("\"} "); @@ -345,7 +340,7 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOExce MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "", "untyped", metadata); for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels()); + writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } @@ -382,17 +377,16 @@ private void writeNameAndLabels( private void writeMetadata( Writer writer, String suffix, String typeString, MetricMetadata metadata) throws IOException { + String name = metadata.getName() + (suffix != null ? suffix : ""); if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); - writeName( - writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); + writeName(writer, name, NameType.Metric); writer.write(' '); writeEscapedHelp(writer, metadata.getHelp()); writer.write('\n'); } writer.write("# TYPE "); - writeName( - writer, metadata.getPrometheusName() + (suffix != null ? suffix : ""), NameType.Metric); + writeName(writer, name, NameType.Metric); writer.write(' '); writer.write(typeString); writer.write('\n'); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 330790797..1fc0647cc 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -114,7 +114,7 @@ static void writeLabels( if (i > 0 || metricInsideBraces) { writer.write(","); } - writeName(writer, labels.getPrometheusName(i), NameType.Label); + writeName(writer, labels.getName(i), NameType.Label); writer.write("=\""); writeEscapedString(writer, labels.getValue(i)); writer.write("\""); From 6c11f86789d0731fe2b94af20959c4fbcec72ce2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 18:59:02 +0200 Subject: [PATCH 053/106] fix escaping Signed-off-by: Gregor Zeitlinger --- .../metrics/expositionformats/ExpositionFormatsTest.java | 6 +++--- .../prometheus/metrics/model/snapshots/MetricMetadata.java | 6 +++--- .../metrics/model/snapshots/PrometheusNaming.java | 6 +++++- .../metrics/model/snapshots/PrometheusNamingTest.java | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index af3cbe888..911ad875d 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -2785,10 +2785,10 @@ public void testLabelValueEscape() throws IOException { @ParameterizedTest @CsvSource({ - "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values'", - "'text/plain;version=0.0.4', 'text/plain; version=0.0.4; charset=utf-8; escaping=values'", + "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores'", + "'text/plain;version=0.0.4', 'text/plain; version=0.0.4; charset=utf-8; escaping=underscores'", "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited; escaping=allow-utf-8', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8'", - "'application/openmetrics-text', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values'", + "'application/openmetrics-text', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'", "'application/openmetrics-text;version=0.0.1; escaping=underscores', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'", "'text/plain;version=0.0.4; escaping=allow-utf-8', 'text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8'" }) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 4b9705f22..1709c9d89 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -63,15 +63,15 @@ public MetricMetadata(String name, String help, Unit unit) { * The name does not include the {@code _total} suffix for counter metrics or the {@code _info} * suffix for Info metrics. * - *

The name may contain dots. Use {@link #getPrometheusName()} to get the name in Prometheus - * format, i.e. with dots replaced by underscores. + *

The name may contain any Unicode chars. Use {@link #getPrometheusName()} to get the name in + * legacy Prometheus format, i.e. with all dots and all invalid chars replaced by underscores. */ public String getName() { return name; } /** - * Same as {@link #getName()} but with all invalid characters replaced by underscores. + * Same as {@link #getName()} but with all invalid characters and dots replaced by underscores. * *

This is used by Prometheus exposition formats. */ diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index dd2d96c7b..8b884c356 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -576,7 +576,11 @@ private static MetricSnapshot createEscapedMetricSnapshot( * which by definition is a noop). This method does not do any validation of the name. */ public static String escapeName(String name, EscapingScheme scheme) { - if (name.isEmpty() || isValidLegacyMetricName(name)) { + boolean noEscapeNeeded = + isValidLegacyMetricName(name) + && !(scheme == EscapingScheme.DOTS_ESCAPING + && (name.contains(".") || name.contains("_"))); + if (name.isEmpty() || noEscapeNeeded) { return name; } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 56fb61261..aa8d7aaa3 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -379,7 +379,7 @@ public void testEscapeMetricSnapshotGaugeEscapingNeeded() { "some_label", "label??value", "unicode_dot_and_dot_dots_dot_____", - "some_label", + "some__label", "label??value", EscapingScheme.DOTS_ESCAPING, GaugeSnapshot.class); From f39d9beb28598b76dec08330257865673de664d9 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 11 Aug 2025 19:11:06 +0200 Subject: [PATCH 054/106] fix escaping Signed-off-by: Gregor Zeitlinger --- .../expositionformats/ExpositionFormatsTest.java | 15 +++++++++------ .../metrics/model/snapshots/PrometheusNaming.java | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 911ad875d..edb93d114 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -507,7 +507,10 @@ public void testGaugeUTF8() throws IOException { .labels(Labels.builder().label("name.1", "Björn").label("name*2", "佖佥").build()) .build()) .build(); - assertPrometheusText(prometheusText, gauge); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getPrometheusWriter(PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true)) + .write(out, MetricSnapshots.of((MetricSnapshot) gauge), EscapingScheme.NO_ESCAPING); + assertThat(out).hasToString(prometheusText); } @Test @@ -2830,7 +2833,7 @@ private void assertOpenMetricsText(String expected, MetricSnapshot snapshot) thr ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = OpenMetricsTextFormatWriter.builder().setCreatedTimestampsEnabled(true).build(); - writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } @@ -2842,7 +2845,7 @@ private void assertOpenMetricsTextWithExemplarsOnAllTimeSeries( .setCreatedTimestampsEnabled(true) .setExemplarsOnAllMetricTypesEnabled(true) .build(); - writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } @@ -2850,14 +2853,14 @@ private void assertOpenMetricsTextWithoutCreated(String expected, MetricSnapshot throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = OpenMetricsTextFormatWriter.create(); - writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } private void assertPrometheusText(String expected, MetricSnapshot snapshot) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); getPrometheusWriter(PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true)) - .write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); + .write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } @@ -2871,7 +2874,7 @@ private void assertPrometheusTextWithoutCreated(String expected, MetricSnapshot throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); getPrometheusWriter(PrometheusTextFormatWriter.builder()) - .write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); + .write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 8b884c356..2322594b2 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -46,7 +46,7 @@ public class PrometheusNaming { /** Legal characters for metric names, including dot. */ private static final Pattern LEGACY_METRIC_NAME_PATTERN = - Pattern.compile("^[a-zA-Z_.:][a-zA-Z0-9_.:]*$"); + Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); From 8bfcdfe7f4e2e026883311ad78469dccff191bac Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 08:51:30 +0200 Subject: [PATCH 055/106] remove validation scheme Signed-off-by: Gregor Zeitlinger --- .../metrics/config/NamingProperties.java | 61 --------- .../metrics/config/PrometheusProperties.java | 16 +-- .../config/PrometheusPropertiesLoader.java | 4 +- .../metrics/config/ValidationScheme.java | 16 --- .../metrics/config/NamingPropertiesTest.java | 29 ----- .../config/PrometheusPropertiesTest.java | 2 - .../expositionformats/TextFormatUtil.java | 4 +- .../metrics/model/snapshots/Labels.java | 10 +- .../model/snapshots/PrometheusNaming.java | 119 ++---------------- 9 files changed, 19 insertions(+), 242 deletions(-) delete mode 100644 prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java delete mode 100644 prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java delete mode 100644 prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java deleted file mode 100644 index 102f65503..000000000 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/NamingProperties.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.prometheus.metrics.config; - -import java.util.Map; -import javax.annotation.Nullable; - -public class NamingProperties { - - private static final String PREFIX = "io.prometheus.naming"; - private static final String VALIDATION_SCHEME = "validationScheme"; - private final ValidationScheme validationScheme; - - private NamingProperties(ValidationScheme validation) { - this.validationScheme = validation; - } - - public ValidationScheme getValidationScheme() { - return validationScheme; - } - - static NamingProperties load(Map properties) - throws PrometheusPropertiesException { - String validationScheme = Util.loadString(PREFIX + "." + VALIDATION_SCHEME, properties); - return new NamingProperties(parseValidationScheme(validationScheme)); - } - - static ValidationScheme parseValidationScheme(@Nullable String scheme) { - if (scheme == null || scheme.isEmpty()) { - return ValidationScheme.LEGACY_VALIDATION; - } - - switch (scheme) { - case "utf-8": - return ValidationScheme.UTF_8_VALIDATION; - case "legacy": - return ValidationScheme.LEGACY_VALIDATION; - default: - throw new PrometheusPropertiesException( - "Unknown validation scheme: " + scheme + ". Valid values are: utf-8, legacy."); - } - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private ValidationScheme validationScheme; - - private Builder() {} - - public Builder validation(ValidationScheme validationScheme) { - this.validationScheme = validationScheme; - return this; - } - - public NamingProperties build() { - return new NamingProperties(validationScheme); - } - } -} diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index 49b03fb16..f240c1d81 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -20,7 +20,6 @@ public class PrometheusProperties { private final ExporterHttpServerProperties exporterHttpServerProperties; private final ExporterOpenTelemetryProperties exporterOpenTelemetryProperties; private final ExporterPushgatewayProperties exporterPushgatewayProperties; - private final NamingProperties namingProperties; /** * Get the properties instance. When called for the first time, {@code get()} loads the properties @@ -49,8 +48,7 @@ public PrometheusProperties( ExporterFilterProperties exporterFilterProperties, ExporterHttpServerProperties httpServerConfig, ExporterPushgatewayProperties pushgatewayProperties, - ExporterOpenTelemetryProperties otelConfig, - NamingProperties namingProperties) { + ExporterOpenTelemetryProperties otelConfig) { this.defaultMetricsProperties = defaultMetricsProperties; this.metricProperties.putAll(metricProperties); this.exemplarProperties = exemplarProperties; @@ -59,7 +57,6 @@ public PrometheusProperties( this.exporterHttpServerProperties = httpServerConfig; this.exporterPushgatewayProperties = pushgatewayProperties; this.exporterOpenTelemetryProperties = otelConfig; - this.namingProperties = namingProperties; } /** @@ -103,9 +100,6 @@ public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() { return exporterOpenTelemetryProperties; } - public NamingProperties getNamingProperties() { - return namingProperties; - } public static class Builder { private MetricsProperties defaultMetricsProperties; @@ -116,7 +110,6 @@ public static class Builder { private ExporterHttpServerProperties exporterHttpServerProperties; private ExporterPushgatewayProperties pushgatewayProperties; private ExporterOpenTelemetryProperties otelConfig; - private NamingProperties namingProperties; private Builder() {} @@ -168,10 +161,6 @@ public Builder exporterOpenTelemetryProperties( return this; } - public Builder namingProperties(NamingProperties namingProperties) { - this.namingProperties = namingProperties; - return this; - } public PrometheusProperties build() { return new PrometheusProperties( @@ -182,8 +171,7 @@ public PrometheusProperties build() { exporterFilterProperties, exporterHttpServerProperties, pushgatewayProperties, - otelConfig, - namingProperties); + otelConfig); } } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java index cd4ba3e33..a847a8dba 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java @@ -42,7 +42,6 @@ public static PrometheusProperties load(Map externalProperties) ExporterPushgatewayProperties.load(properties); ExporterOpenTelemetryProperties exporterOpenTelemetryProperties = ExporterOpenTelemetryProperties.load(properties); - NamingProperties namingProperties = NamingProperties.load(properties); validateAllPropertiesProcessed(properties); return new PrometheusProperties( defaultMetricsProperties, @@ -52,8 +51,7 @@ public static PrometheusProperties load(Map externalProperties) exporterFilterProperties, exporterHttpServerProperties, exporterPushgatewayProperties, - exporterOpenTelemetryProperties, - namingProperties); + exporterOpenTelemetryProperties); } // This will remove entries from properties when they are processed. diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java deleted file mode 100644 index c0429256a..000000000 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ValidationScheme.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.prometheus.metrics.config; - -/** - * ValidationScheme is an enum for determining how metric and label names will be validated by this - * library. - */ -public enum ValidationScheme { - /** - * LEGACY_VALIDATION is a setting that requires that metric and label names conform to the - * original character requirements. - */ - LEGACY_VALIDATION, - - /** UTF_8_VALIDATION only requires that metric and label names be valid UTF-8 strings. */ - UTF_8_VALIDATION -} diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java deleted file mode 100644 index 83548c11c..000000000 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/NamingPropertiesTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.prometheus.metrics.config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -import org.junit.jupiter.api.Test; - -class NamingPropertiesTest { - @Test - void testBuilder() { - NamingProperties properties = - NamingProperties.builder().validation(ValidationScheme.UTF_8_VALIDATION).build(); - assertThat(properties.getValidationScheme()).isEqualTo(ValidationScheme.UTF_8_VALIDATION); - } - - @Test - void parseValidationScheme() { - assertThat(NamingProperties.parseValidationScheme("utf-8")) - .isEqualTo(ValidationScheme.UTF_8_VALIDATION); - assertThat(NamingProperties.parseValidationScheme("legacy")) - .isEqualTo(ValidationScheme.LEGACY_VALIDATION); - assertThat(NamingProperties.parseValidationScheme(null)) - .isEqualTo(ValidationScheme.LEGACY_VALIDATION); - assertThatCode(() -> NamingProperties.parseValidationScheme("unknown")) - .isInstanceOf(PrometheusPropertiesException.class) - .hasMessageContaining( - "Unknown validation scheme: unknown. Valid values are: utf-8, legacy."); - } -} diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java index 18b66fa6f..d0205b28e 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java @@ -49,7 +49,6 @@ public void testBuilder() { builder.exporterOpenTelemetryProperties(defaults.getExporterOpenTelemetryProperties()); builder.pushgatewayProperties(defaults.getExporterPushgatewayProperties()); builder.exporterProperties(defaults.getExporterProperties()); - builder.namingProperties(defaults.getNamingProperties()); PrometheusProperties result = builder.build(); assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties()); assertThat(result.getExemplarProperties()).isSameAs(defaults.getExemplarProperties()); @@ -67,6 +66,5 @@ public void testBuilder() { MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.2, 0.5, 1.0).build()); assertThat(result.getMetricProperties("unknown_metric")).isNull(); assertThat(result.getExporterProperties()).isSameAs(defaults.getExporterProperties()); - assertThat(result.getNamingProperties()).isSameAs(defaults.getNamingProperties()); } } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 1fc0647cc..03fc3af78 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -1,6 +1,5 @@ package io.prometheus.metrics.expositionformats; -import io.prometheus.metrics.config.ValidationScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import java.io.IOException; @@ -140,8 +139,7 @@ static void writeName(Writer writer, String name, NameType nameType) throws IOEx } break; case Label: - if (PrometheusNaming.isValidLegacyLabelName(name) - && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { + if (PrometheusNaming.isValidLegacyLabelName(name)) { writer.write(name); return; } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index a355f283c..40311a7e2 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -3,7 +3,6 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; -import io.prometheus.metrics.config.ValidationScheme; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -120,13 +119,10 @@ public static Labels of(String[] names, String[] values) { static String[] makePrometheusNames(String[] names) { String[] prometheusNames = names; for (int i = 0; i < names.length; i++) { - if (names[i].contains(".") - && PrometheusNaming.getValidationScheme() == ValidationScheme.LEGACY_VALIDATION) { - if (prometheusNames == names) { - prometheusNames = Arrays.copyOf(names, names.length); - } - prometheusNames[i] = PrometheusNaming.prometheusName(names[i]); + if (prometheusNames == names) { + prometheusNames = Arrays.copyOf(names, names.length); } + prometheusNames[i] = PrometheusNaming.prometheusName(names[i]); } return prometheusNames; } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 2322594b2..d63d86da6 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -4,9 +4,6 @@ import static java.lang.Character.MAX_LOW_SURROGATE; import static java.lang.Character.MIN_HIGH_SURROGATE; -import io.prometheus.metrics.config.PrometheusProperties; -import io.prometheus.metrics.config.PrometheusPropertiesLoader; -import io.prometheus.metrics.config.ValidationScheme; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -22,15 +19,6 @@ */ public class PrometheusNaming { - /** - * nameValidationScheme determines the method of name validation to be used by all calls to - * validateMetricName() and isValidMetricName(). Setting UTF-8 mode in isolation from other - * components that don't support UTF-8 may result in bugs or other undefined behavior. This value - * is intended to be set by UTF-8-aware binaries as part of their startup via a properties file. - */ - public static ValidationScheme nameValidationScheme = - PrometheusProperties.get().getNamingProperties().getValidationScheme(); - /** Default escaping scheme for names when not specified. */ public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = EscapingScheme.UNDERSCORE_ESCAPING; @@ -44,15 +32,11 @@ public class PrometheusNaming { private static final String METRIC_NAME_LABEL = "__name__"; - /** Legal characters for metric names, including dot. */ - private static final Pattern LEGACY_METRIC_NAME_PATTERN = - Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); - private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); - /** Legal characters for label names, including dot. */ + /** Legal characters for label names. */ private static final Pattern LEGACY_LABEL_NAME_PATTERN = - Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); + Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$"); /** Legal characters for unit names, including dot. */ private static final Pattern UNIT_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_.:]+$"); @@ -77,20 +61,6 @@ public class PrometheusNaming { ".total", ".created", ".bucket", ".info" }; - // VisibleForTesting - public static void resetForTest() { - nameValidationScheme = - PrometheusPropertiesLoader.load().getNamingProperties().getValidationScheme(); - } - - /** - * Get the current validation scheme. This method exists primarily to enable testing different - * validation behaviors while keeping the validation scheme field final and immutable. - */ - public static ValidationScheme getValidationScheme() { - return nameValidationScheme; - } - /** * Test if a metric name is valid. Rules: * @@ -116,18 +86,10 @@ public static boolean isValidMetricName(String name) { } public static String validateMetricName(String name) { - switch (getValidationScheme()) { - case LEGACY_VALIDATION: - return validateLegacyMetricName(name); - case UTF_8_VALIDATION: - if (!isValidUtf8(name)) { - return "The metric name contains unsupported characters"; - } - return null; - default: - throw new RuntimeException( - "Invalid name validation scheme requested: " + getValidationScheme()); + if (isValidUtf8(name)) { + return null; } + return "The metric name contains unsupported characters"; } /** @@ -148,27 +110,11 @@ public static String validateLegacyMetricName(String name) { } public static boolean isValidLegacyMetricName(String name) { - switch (getValidationScheme()) { - case LEGACY_VALIDATION: - return LEGACY_METRIC_NAME_PATTERN.matcher(name).matches(); - case UTF_8_VALIDATION: - return METRIC_NAME_PATTERN.matcher(name).matches(); - default: - throw new RuntimeException( - "Invalid name validation scheme requested: " + getValidationScheme()); - } + return METRIC_NAME_PATTERN.matcher(name).matches(); } public static boolean isValidLabelName(String name) { - switch (getValidationScheme()) { - case LEGACY_VALIDATION: - return isValidLegacyLabelName(name); - case UTF_8_VALIDATION: - return isValidUtf8(name); - default: - throw new RuntimeException( - "Invalid name validation scheme requested: " + getValidationScheme()); - } + return isValidUtf8(name); } private static boolean isValidUtf8(String name) { @@ -187,11 +133,6 @@ public static boolean isValidLegacyLabelName(String name) { * Units may not have illegal characters, and they may not end with a reserved suffix like * 'total'. */ - public static boolean isValidUnitName(String name) { - return validateUnitName(name) == null; - } - - /** Same as {@link #isValidUnitName(String)} but returns an error message. */ public static String validateUnitName(String name) { if (name.isEmpty()) { return "The unit name must not be empty."; @@ -217,7 +158,7 @@ public static String validateUnitName(String name) { * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { - return PrometheusNaming.escapeName(name.replace(".", "_"), EscapingScheme.UNDERSCORE_ESCAPING); + return PrometheusNaming.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); } /** @@ -228,7 +169,7 @@ public static String sanitizeMetricName(String metricName) { if (metricName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid metric name."); } - String sanitizedName = replaceIllegalCharsInMetricName(metricName); + String sanitizedName = metricName; boolean modified = true; while (modified) { modified = false; @@ -270,7 +211,7 @@ public static String sanitizeLabelName(String labelName) { if (labelName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid label name."); } - String sanitizedName = replaceIllegalCharsInLabelName(labelName); + String sanitizedName = labelName; while (sanitizedName.startsWith("__") || sanitizedName.startsWith("_.") || sanitizedName.startsWith("._") @@ -281,8 +222,8 @@ public static String sanitizeLabelName(String labelName) { } /** - * Convert an arbitrary string to a name where {@link #isValidUnitName(String) - * isValidUnitName(name)} is true. + * Convert an arbitrary string to a name where {@link #validateUnitName(String)} is {@code null} + * (i.e. the name is valid). * * @throws IllegalArgumentException if the {@code unitName} cannot be converted, for example if * you call {@code sanitizeUnitName("total")} or {@code sanitizeUnitName("")}. @@ -319,42 +260,6 @@ public static String sanitizeUnitName(String unitName) { return sanitizedName; } - /** Returns a string that matches {@link #LEGACY_METRIC_NAME_PATTERN}. */ - private static String replaceIllegalCharsInMetricName(String name) { - int length = name.length(); - char[] sanitized = new char[length]; - for (int i = 0; i < length; i++) { - char ch = name.charAt(i); - if (ch == '.' - || (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (i > 0 && ch >= '0' && ch <= '9')) { - sanitized[i] = ch; - } else { - sanitized[i] = '_'; - } - } - return new String(sanitized); - } - - /** Returns a string that matches {@link #LEGACY_LABEL_NAME_PATTERN}. */ - private static String replaceIllegalCharsInLabelName(String name) { - int length = name.length(); - char[] sanitized = new char[length]; - for (int i = 0; i < length; i++) { - char ch = name.charAt(i); - if (ch == '.' - || (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (i > 0 && ch >= '0' && ch <= '9')) { - sanitized[i] = ch; - } else { - sanitized[i] = '_'; - } - } - return new String(sanitized); - } - /** Returns a string that matches {@link #UNIT_NAME_PATTERN}. */ private static String replaceIllegalCharsInUnitName(String name) { int length = name.length(); From a928ea718fa0b46be39b74b88e3ba0e4a102aa71 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 10:48:26 +0200 Subject: [PATCH 056/106] remove validation scheme Signed-off-by: Gregor Zeitlinger --- .../exporter/pushgateway/PushGatewayTest.java | 16 ----- .../OpenMetricsTextFormatWriter.java | 2 +- .../PrometheusTextFormatWriter.java | 2 +- .../ExpositionFormatsTest.java | 8 --- .../metrics/model/snapshots/Labels.java | 9 ++- .../model/snapshots/PrometheusNaming.java | 34 +++++----- .../metrics/model/snapshots/LabelsTest.java | 6 -- .../model/snapshots/MetricMetadataTest.java | 14 +--- .../model/snapshots/PrometheusNamingTest.java | 68 +++++-------------- 9 files changed, 42 insertions(+), 117 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 8e574d5fa..17f9b2923 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -37,8 +37,6 @@ public void setUp() { @AfterEach void tearDown() { mockServerClient.stop(); - System.clearProperty("io.prometheus.naming.validationScheme"); - PrometheusNaming.resetForTest(); } @Test @@ -146,10 +144,8 @@ public void testPushWithGroupingKey() throws IOException { pg.push(); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushWithEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) .respond(response().withStatusCode(202)); @@ -179,10 +175,8 @@ public void testPushWithMultiGroupingKey() throws IOException { pg.push(); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushWithMultiEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) @@ -246,10 +240,8 @@ public void testPushCollectorWithGroupingKey() throws IOException { pg.push(gauge); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushCollectorWithEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) .respond(response().withStatusCode(202)); @@ -302,10 +294,8 @@ public void testPushAddWithGroupingKey() throws IOException { pg.pushAdd(); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushAddWithEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); mockServerClient .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) .respond(response().withStatusCode(202)); @@ -334,10 +324,8 @@ public void testPushAddCollectorWithGroupingKey() throws IOException { pg.pushAdd(gauge); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testPushAddCollectorWithEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); mockServerClient .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) @@ -376,10 +364,8 @@ public void testDeleteWithGroupingKey() throws IOException { pg.delete(); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testDeleteWithEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); mockServerClient .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) @@ -410,10 +396,8 @@ public void testInstanceIpGroupingKey() throws IOException { pg.delete(); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testInstanceIpEscapedGroupingKey() throws IOException { - PrometheusNaming.resetForTest(); String ip = InetAddress.getLocalHost().getHostAddress(); assertThat(ip).isNotEmpty(); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index d748bf9d0..6d1422adb 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -362,7 +362,7 @@ private void writeNameAndLabels( boolean metricInsideBraces = false; // If the name does not pass the legacy validity check, we must put the // metric name inside the braces. - if (PrometheusNaming.validateLegacyMetricName(name) != null) { + if (!PrometheusNaming.isValidLegacyMetricName(name)) { metricInsideBraces = true; writer.write('{'); } 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 4b30cad25..8970d1fdd 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 @@ -362,7 +362,7 @@ private void writeNameAndLabels( boolean metricInsideBraces = false; // If the name does not pass the legacy validity check, we must put the // metric name inside the braces. - if (PrometheusNaming.validateLegacyMetricName(name) != null) { + if (!PrometheusNaming.isValidLegacyLabelName(name)) { metricInsideBraces = true; writer.write('{'); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index edb93d114..c45420eed 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -99,12 +99,6 @@ class ExpositionFormatsTest { .timestampMillis(1690298864383L) .build(); - @AfterEach - void tearDown() { - System.clearProperty("io.prometheus.naming.validationScheme"); - PrometheusNaming.resetForTest(); - } - @Test void init() { ExpositionFormats formats = ExpositionFormats.init(); @@ -477,10 +471,8 @@ public void testGaugeWithDots() throws IOException { assertPrometheusProtobuf(prometheusProtobuf, gauge); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testGaugeUTF8() throws IOException { - PrometheusNaming.resetForTest(); String prometheusText = """ # HELP "gauge.name" gauge\\ndoc\\nstr"ing diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index 40311a7e2..51a0b8463 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -119,10 +119,13 @@ public static Labels of(String[] names, String[] values) { static String[] makePrometheusNames(String[] names) { String[] prometheusNames = names; for (int i = 0; i < names.length; i++) { - if (prometheusNames == names) { - prometheusNames = Arrays.copyOf(names, names.length); + String name = names[i]; + if (!PrometheusNaming.isValidLegacyLabelName(name)) { + if (prometheusNames == names) { + prometheusNames = Arrays.copyOf(names, names.length); + } + prometheusNames[i] = PrometheusNaming.prometheusName(name); } - prometheusNames[i] = PrometheusNaming.prometheusName(names[i]); } return prometheusNames; } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index d63d86da6..c14860129 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -65,7 +65,7 @@ public class PrometheusNaming { * Test if a metric name is valid. Rules: * *

    - *
  • The name must match {@link #LEGACY_METRIC_NAME_PATTERN}. + *
  • The name must match {@link #METRIC_NAME_PATTERN}. *
  • The name MUST NOT end with one of the {@link #RESERVED_METRIC_NAME_SUFFIXES}. *
* @@ -85,28 +85,21 @@ public static boolean isValidMetricName(String name) { return validateMetricName(name) == null; } - public static String validateMetricName(String name) { - if (isValidUtf8(name)) { - return null; - } - return "The metric name contains unsupported characters"; - } - /** * Same as {@link #isValidMetricName(String)}, but produces an error message. * *

The name is valid if the error message is {@code null}. */ - public static String validateLegacyMetricName(String name) { + public static String validateMetricName(String name) { for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { if (name.endsWith(reservedSuffix)) { return "The metric name must not include the '" + reservedSuffix + "' suffix."; } } - if (!isValidLegacyMetricName(name)) { - return "The metric name contains unsupported characters"; + if (isValidUtf8(name)) { + return null; } - return null; + return "The metric name contains unsupported characters"; } public static boolean isValidLegacyMetricName(String name) { @@ -114,7 +107,11 @@ public static boolean isValidLegacyMetricName(String name) { } public static boolean isValidLabelName(String name) { - return isValidUtf8(name); + return isValidUtf8(name) + && !(name.startsWith("__") + || name.startsWith("._") + || name.startsWith("..") + || name.startsWith("_.")); } private static boolean isValidUtf8(String name) { @@ -122,17 +119,18 @@ private static boolean isValidUtf8(String name) { } public static boolean isValidLegacyLabelName(String name) { - return LEGACY_LABEL_NAME_PATTERN.matcher(name).matches() - && !(name.startsWith("__") - || name.startsWith("._") - || name.startsWith("..") - || name.startsWith("_.")); + return LEGACY_LABEL_NAME_PATTERN.matcher(name).matches(); } /** * Units may not have illegal characters, and they may not end with a reserved suffix like * 'total'. */ + public static boolean isValidUnitName(String name) { + return validateUnitName(name) == null; + } + + /** Same as {@link #isValidUnitName(String)} but returns an error message. */ public static String validateUnitName(String name) { if (name.isEmpty()) { return "The unit name must not be empty."; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java index 4d68b82a8..5bdd285e3 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java @@ -76,12 +76,6 @@ public void testCompareEquals() { assertLabels(labels1).isEqualTo(labels2); } - @Test - public void testIllegalLabelName() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> Labels.of("my_service/status", "200")); - } - @Test public void testReservedLabelName() { assertThatExceptionOfType(IllegalArgumentException.class) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index fab885488..f2d6a6ba4 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -20,16 +20,6 @@ public void testNullName() { .isThrownBy(() -> new MetricMetadata(null)); } - @Test - public void testIllegalName() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy( - () -> - new MetricMetadata( - "my_namespace/http_server_duration")); // let's see when we decide to allow - // slashes :) - } - @Test public void testSanitizationIllegalCharacters() { MetricMetadata metadata = @@ -37,10 +27,10 @@ public void testSanitizationIllegalCharacters() { sanitizeMetricName("my_namespace/http.server.duration", Unit.SECONDS), "help string", Unit.SECONDS); - assertThat(metadata.getName()).isEqualTo("my_namespace_http.server.duration_seconds"); + assertThat(metadata.getName()).isEqualTo("my_namespace/http.server.duration_seconds"); assertThat(metadata.getPrometheusName()).isEqualTo("my_namespace_http_server_duration_seconds"); assertThat(metadata.getHelp()).isEqualTo("help string"); - assertThat(metadata.getUnit().toString()).isEqualTo("seconds"); + assertThat(metadata.getUnit()).hasToString("seconds"); } @Test diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index aa8d7aaa3..c46fd098c 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -14,28 +14,18 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.util.stream.Stream; -import org.junit.jupiter.api.AfterEach; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; -import org.junitpioneer.jupiter.SetSystemProperty; class PrometheusNamingTest { - @AfterEach - void tearDown() { - System.clearProperty("io.prometheus.naming.validationScheme"); - PrometheusNaming.resetForTest(); - } - @Test public void testSanitizeMetricName() { - assertThat(prometheusName(sanitizeMetricName("0abc.def"))).isEqualTo("_abc_def"); - assertThat(prometheusName(sanitizeMetricName("___ab.:c0"))).isEqualTo("___ab__c0"); - assertThat(sanitizeMetricName("my_prefix/my_metric")).isEqualTo("my_prefix_my_metric"); - assertThat(prometheusName(sanitizeMetricName("my_counter_total"))).isEqualTo("my_counter"); + assertThat(sanitizeMetricName("my_counter_total")).isEqualTo("my_counter"); assertThat(sanitizeMetricName("jvm.info")).isEqualTo("jvm"); assertThat(sanitizeMetricName("jvm_info")).isEqualTo("jvm"); assertThat(sanitizeMetricName("jvm.info")).isEqualTo("jvm"); @@ -46,18 +36,11 @@ public void testSanitizeMetricName() { @Test public void testSanitizeMetricNameWithUnit() { - assertThat(prometheusName(sanitizeMetricName("0abc.def", Unit.RATIO))) - .isEqualTo("_abc_def_" + Unit.RATIO); - assertThat(prometheusName(sanitizeMetricName("___ab.:c0", Unit.RATIO))) - .isEqualTo("___ab__c0_" + Unit.RATIO); - assertThat(sanitizeMetricName("my_prefix/my_metric", Unit.RATIO)) - .isEqualTo("my_prefix_my_metric_" + Unit.RATIO); + assertThat(prometheusName(sanitizeMetricName("def", Unit.RATIO))) + .isEqualTo("def_" + Unit.RATIO); assertThat(prometheusName(sanitizeMetricName("my_counter_total", Unit.RATIO))) .isEqualTo("my_counter_" + Unit.RATIO); assertThat(sanitizeMetricName("jvm.info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("jvm_info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("jvm.info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("a.b", Unit.RATIO)).isEqualTo("a.b_" + Unit.RATIO); assertThat(sanitizeMetricName("_total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); assertThat(sanitizeMetricName("total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); } @@ -119,26 +102,14 @@ public void testEmptyUnitName() { .isThrownBy(() -> sanitizeUnitName("")); } - @SuppressWarnings("unused") - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @ParameterizedTest @MethodSource("nameIsValid") public void testLabelNameIsValidUtf8( - String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { - PrometheusNaming.resetForTest(); + String labelName, boolean utf8Valid) { assertMetricName(labelName, utf8Valid); assertLabelName(labelName, utf8Valid); } - @SuppressWarnings("unused") - @ParameterizedTest - @MethodSource("nameIsValid") - public void testLabelNameIsValidLegacy( - String labelName, boolean legacyValid, boolean utf8Valid, boolean legacyCharsetValid) { - assertMetricName(labelName, legacyCharsetValid); - assertLabelName(labelName, legacyValid); - } - private static void assertLabelName(String labelName, boolean legacyValid) { assertThat(isValidLabelName(labelName)) .describedAs("isValidLabelName(%s)", labelName) @@ -153,17 +124,17 @@ private static void assertMetricName(String labelName, boolean valid) { static Stream nameIsValid() { return Stream.of( - Arguments.of("", false, false, false), - Arguments.of("Avalid_23name", true, true, true), - Arguments.of("_Avalid_23name", true, true, true), - Arguments.of("1valid_23name", false, true, false), - Arguments.of("avalid_23name", true, true, true), - Arguments.of("Ava:lid_23name", false, true, true), - Arguments.of("a lid_23name", false, true, false), - Arguments.of(":leading_colon", false, true, true), - Arguments.of("colon:in:the:middle", false, true, true), - Arguments.of("aΩz", false, true, false), - Arguments.of("a\ud800z", false, false, false)); + Arguments.of("", false), + Arguments.of("Avalid_23name", true), + Arguments.of("_Avalid_23name", true), + Arguments.of("1valid_23name", true), + Arguments.of("avalid_23name", true), + Arguments.of("Ava:lid_23name", true), + Arguments.of("a lid_23name", true), + Arguments.of(":leading_colon", true), + Arguments.of("colon:in:the:middle",true), + Arguments.of("aΩz", true), + Arguments.of("a\ud800z", false)); } @ParameterizedTest @@ -173,12 +144,10 @@ public void testEscapeNameLegacy( assertEscape(input, escapingScheme, expected, unescapeExpected); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @ParameterizedTest @MethodSource("escapeNameUtf8TestCases") public void testEscapeNameUtf8( String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - PrometheusNaming.resetForTest(); assertEscape(input, escapingScheme, expected, unescapeExpected); } @@ -329,7 +298,6 @@ public void testEscapeMetricSnapshotEmpty() { assertThat(original.getMetadata().getName()).isEqualTo("empty"); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { testEscapeMetricSnapshot( @@ -343,7 +311,6 @@ public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { CounterSnapshot.class); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { testEscapeMetricSnapshot( @@ -357,7 +324,6 @@ public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { CounterSnapshot.class); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotCounterEscapingNeeded() { testEscapeMetricSnapshot( @@ -371,7 +337,6 @@ public void testEscapeMetricSnapshotCounterEscapingNeeded() { CounterSnapshot.class); } - @SetSystemProperty(key = "io.prometheus.naming.validationScheme", value = "utf-8") @Test public void testEscapeMetricSnapshotGaugeEscapingNeeded() { testEscapeMetricSnapshot( @@ -394,7 +359,6 @@ private void testEscapeMetricSnapshot( String expectedLabelValue, EscapingScheme escapingScheme, Class snapshotType) { - PrometheusNaming.resetForTest(); MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, snapshotType); MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); From 842b993b29ca570ea91e686083785243744375bd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 14:48:37 +0200 Subject: [PATCH 057/106] __name__ can't be used in labels Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNaming.java | 28 +++++++------------ .../model/snapshots/PrometheusNamingTest.java | 23 ++++++--------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index c14860129..ccca97fe4 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -30,8 +30,6 @@ public class PrometheusNaming { */ public static final String ESCAPING_KEY = "escaping"; - private static final String METRIC_NAME_LABEL = "__name__"; - private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); /** Legal characters for label names. */ @@ -290,7 +288,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche List outDataPoints = new ArrayList<>(); for (DataPointSnapshot d : v.getDataPoints()) { - if (!metricNeedsEscaping(d)) { + if (!metricNeedsEscaping(d, scheme)) { outDataPoints.add(d); continue; } @@ -298,11 +296,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche Labels.Builder outLabelsBuilder = Labels.builder(); for (Label l : d.getLabels()) { - if (METRIC_NAME_LABEL.equals(l.getName())) { - outLabelsBuilder.label(l.getName(), escapeName(l.getValue(), scheme)); - } else { - outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); - } + outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); } Labels outLabels = outLabelsBuilder.build(); @@ -314,13 +308,10 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); } - static boolean metricNeedsEscaping(DataPointSnapshot d) { + static boolean metricNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { Labels labels = d.getLabels(); for (Label l : labels) { - if (l.getName().equals(METRIC_NAME_LABEL) && !isValidLegacyMetricName(l.getValue())) { - return true; - } - if (!isValidLegacyMetricName(l.getName())) { + if (needsEscaping(l.getName(), scheme)) { return true; } } @@ -479,11 +470,7 @@ private static MetricSnapshot createEscapedMetricSnapshot( * which by definition is a noop). This method does not do any validation of the name. */ public static String escapeName(String name, EscapingScheme scheme) { - boolean noEscapeNeeded = - isValidLegacyMetricName(name) - && !(scheme == EscapingScheme.DOTS_ESCAPING - && (name.contains(".") || name.contains("_"))); - if (name.isEmpty() || noEscapeNeeded) { + if (name.isEmpty() || !needsEscaping(name, scheme)) { return name; } @@ -547,6 +534,11 @@ public static String escapeName(String name, EscapingScheme scheme) { } } + private static boolean needsEscaping(String name, EscapingScheme scheme) { + return !isValidLegacyMetricName(name) + || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); + } + /** * Unescapes the incoming name according to the provided escaping scheme if possible. Some schemes * are partially or totally non-roundtripable. If any error is encountered, returns the original diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index c46fd098c..510041f1e 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -104,8 +104,7 @@ public void testEmptyUnitName() { @ParameterizedTest @MethodSource("nameIsValid") - public void testLabelNameIsValidUtf8( - String labelName, boolean utf8Valid) { + public void testLabelNameIsValidUtf8(String labelName, boolean utf8Valid) { assertMetricName(labelName, utf8Valid); assertLabelName(labelName, utf8Valid); } @@ -128,13 +127,13 @@ static Stream nameIsValid() { Arguments.of("Avalid_23name", true), Arguments.of("_Avalid_23name", true), Arguments.of("1valid_23name", true), - Arguments.of("avalid_23name", true), - Arguments.of("Ava:lid_23name", true), + Arguments.of("avalid_23name", true), + Arguments.of("Ava:lid_23name", true), Arguments.of("a lid_23name", true), - Arguments.of(":leading_colon", true), - Arguments.of("colon:in:the:middle",true), + Arguments.of(":leading_colon", true), + Arguments.of("colon:in:the:middle", true), Arguments.of("aΩz", true), - Arguments.of("a\ud800z", false)); + Arguments.of("a\ud800z", false)); } @ParameterizedTest @@ -369,11 +368,7 @@ private void testEscapeMetricSnapshot( DataPointSnapshot escapedData = got.getDataPoints().get(0); assertThat((Iterable) escapedData.getLabels()) - .isEqualTo( - Labels.builder() - .label("__name__", expectedName) - .label(expectedLabelName, expectedLabelValue) - .build()); + .isEqualTo(Labels.builder().label(expectedLabelName, expectedLabelValue).build()); assertThat(original.getMetadata().getName()).isEqualTo(name); assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); @@ -381,7 +376,7 @@ private void testEscapeMetricSnapshot( DataPointSnapshot originalData = original.getDataPoints().get(0); assertThat((Iterable) originalData.getLabels()) - .isEqualTo(Labels.builder().label("__name__", name).label(labelName, labelValue).build()); + .isEqualTo(Labels.builder().label(labelName, labelValue).build()); } private MetricSnapshot createTestSnapshot( @@ -389,7 +384,7 @@ private MetricSnapshot createTestSnapshot( String labelName, String labelValue, Class snapshotType) { - Labels labels = Labels.builder().label("__name__", name).label(labelName, labelValue).build(); + Labels labels = Labels.builder().label(labelName, labelValue).build(); if (snapshotType.equals(CounterSnapshot.class)) { return CounterSnapshot.builder() From e61f1274b6ce4b543cdb31c7175d0adaf7dfb0df Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 16:32:11 +0200 Subject: [PATCH 058/106] escape exemplars Signed-off-by: Gregor Zeitlinger --- .../ExpositionFormatsTest.java | 3 +- .../model/snapshots/PrometheusNaming.java | 109 ++++++++++++++---- 2 files changed, 88 insertions(+), 24 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index c45420eed..cef38b330 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -28,11 +28,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.AfterEach; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.junitpioneer.jupiter.SetSystemProperty; class ExpositionFormatsTest { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index ccca97fe4..70b6ecd39 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.snapshots; +import javax.annotation.Nullable; + import static java.lang.Character.MAX_CODE_POINT; import static java.lang.Character.MAX_LOW_SURROGATE; import static java.lang.Character.MIN_HIGH_SURROGATE; @@ -288,19 +290,13 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche List outDataPoints = new ArrayList<>(); for (DataPointSnapshot d : v.getDataPoints()) { - if (!metricNeedsEscaping(d, scheme)) { + if (!snapshotNeedsEscaping(d, scheme)) { outDataPoints.add(d); continue; } - Labels.Builder outLabelsBuilder = Labels.builder(); - - for (Label l : d.getLabels()) { - outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); - } - - Labels outLabels = outLabelsBuilder.build(); - DataPointSnapshot outDataPointSnapshot = createEscapedDataPointSnapshot(v, d, outLabels); + DataPointSnapshot outDataPointSnapshot = + createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme); outDataPoints.add(outDataPointSnapshot); } @@ -308,8 +304,46 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); } - static boolean metricNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { + private static Labels escapeLabels(Labels labels, EscapingScheme scheme) { + Labels.Builder outLabelsBuilder = Labels.builder(); + + for (Label l : labels) { + outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); + } + + return outLabelsBuilder.build(); + } + + static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { Labels labels = d.getLabels(); + if (labelsNeedsEscaping(labels, scheme)) { + return true; + } + if (d instanceof SummarySnapshot.SummaryDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof HistogramSnapshot.HistogramDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof CounterSnapshot.CounterDataPointSnapshot) { + return exemplarNeedsEscaping( + ((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof UnknownSnapshot.UnknownDataPointSnapshot) { + return exemplarNeedsEscaping( + ((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof GaugeSnapshot.GaugeDataPointSnapshot) { + return exemplarNeedsEscaping( + ((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme); + } + + return false; + } + + private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { for (Label l : labels) { if (needsEscaping(l.getName(), scheme)) { return true; @@ -318,12 +352,26 @@ static boolean metricNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { return false; } + private static boolean exemplarNeedsEscaping(@Nullable Exemplar exemplar, EscapingScheme scheme) { + return exemplar != null && labelsNeedsEscaping(exemplar.getLabels(), scheme); + } + + private static boolean exemplarsNeedsEscaping(Exemplars exemplars, EscapingScheme scheme) { + for (Exemplar exemplar : exemplars) { + if (labelsNeedsEscaping(exemplar.getLabels(), scheme)) { + return true; + } + } + return false; + } + private static DataPointSnapshot createEscapedDataPointSnapshot( - MetricSnapshot v, DataPointSnapshot d, Labels outLabels) { + MetricSnapshot v, DataPointSnapshot d, Labels outLabels, EscapingScheme scheme) { if (v instanceof CounterSnapshot) { return CounterSnapshot.CounterDataPointSnapshot.builder() .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) - .exemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar()) + .exemplar( + escapeExemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme)) .labels(outLabels) .createdTimestampMillis(d.getCreatedTimestampMillis()) .scrapeTimestampMillis(d.getScrapeTimestampMillis()) @@ -331,7 +379,8 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( } else if (v instanceof GaugeSnapshot) { return GaugeSnapshot.GaugeDataPointSnapshot.builder() .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) - .exemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar()) + .exemplar( + escapeExemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme)) .labels(outLabels) .scrapeTimestampMillis(d.getScrapeTimestampMillis()) .build(); @@ -351,7 +400,9 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( .getNativeBucketsForNegativeValues()) .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) - .exemplars(((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars()) + .exemplars( + escapeExemplars( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme)) .labels(outLabels) .createdTimestampMillis(d.getCreatedTimestampMillis()) .scrapeTimestampMillis(d.getScrapeTimestampMillis()) @@ -361,7 +412,9 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) - .exemplars(((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars()) + .exemplars( + escapeExemplars( + ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme)) .labels(outLabels) .createdTimestampMillis(d.getCreatedTimestampMillis()) .scrapeTimestampMillis(d.getScrapeTimestampMillis()) @@ -384,7 +437,8 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( return UnknownSnapshot.UnknownDataPointSnapshot.builder() .labels(outLabels) .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) - .exemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar()) + .exemplar( + escapeExemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme)) .scrapeTimestampMillis(d.getScrapeTimestampMillis()) .build(); } else { @@ -392,6 +446,23 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( } } + private static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { + List escapedExemplars = new ArrayList<>(exemplars.size()); + for (Exemplar exemplar : exemplars) { + Exemplar escaped = escapeExemplar(exemplar, scheme); + escapedExemplars.add(escaped); + } + return Exemplars.of(escapedExemplars); + } + + private static Exemplar escapeExemplar(Exemplar exemplar, EscapingScheme scheme) { + return Exemplar.builder() + .labels(escapeLabels(exemplar.getLabels(), scheme)) + .timestampMillis(exemplar.getTimestampMillis()) + .value(exemplar.getValue()) + .build(); + } + private static MetricSnapshot createEscapedMetricSnapshot( MetricSnapshot v, String outName, List outDataPoints) { if (v instanceof CounterSnapshot) { @@ -479,9 +550,6 @@ public static String escapeName(String name, EscapingScheme scheme) { case NO_ESCAPING: return name; case UNDERSCORE_ESCAPING: - if (isValidLegacyMetricName(name)) { - return name; - } for (int i = 0; i < name.length(); ) { int c = name.codePointAt(i); if (isValidLegacyChar(c, i)) { @@ -509,9 +577,6 @@ public static String escapeName(String name, EscapingScheme scheme) { } return escaped.toString(); case VALUE_ENCODING_ESCAPING: - if (isValidLegacyMetricName(name)) { - return name; - } escaped.append("U__"); for (int i = 0; i < name.length(); ) { int c = name.codePointAt(i); From 203361e8b2d7bf0532e82a6e446f47794784f3b6 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 16:46:31 +0200 Subject: [PATCH 059/106] format Signed-off-by: Gregor Zeitlinger --- .../metrics/config/PrometheusProperties.java | 2 -- .../metrics/exporter/pushgateway/PushGatewayTest.java | 2 -- .../expositionformats/ExpositionFormatsTest.java | 5 ++--- .../metrics/model/snapshots/CounterSnapshot.java | 3 ++- .../metrics/model/snapshots/PrometheusNaming.java | 11 ++++++----- .../metrics/model/snapshots/PrometheusNamingTest.java | 1 - 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index f240c1d81..6d9a37594 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -100,7 +100,6 @@ public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() { return exporterOpenTelemetryProperties; } - public static class Builder { private MetricsProperties defaultMetricsProperties; private Map metricProperties = new HashMap<>(); @@ -161,7 +160,6 @@ public Builder exporterOpenTelemetryProperties( return this; } - public PrometheusProperties build() { return new PrometheusProperties( defaultMetricsProperties, diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 17f9b2923..e6a154b45 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -8,7 +8,6 @@ import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.EscapingScheme; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; @@ -16,7 +15,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetSystemProperty; import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index cef38b330..85271af5d 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -28,7 +28,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -476,8 +475,8 @@ public void testGaugeUTF8() throws IOException { """ # HELP "gauge.name" gauge\\ndoc\\nstr"ing # TYPE "gauge.name" gauge - {"gauge.name","name*2"="val with \\\\backslash and \\"quotes\\"","name.1"="val with\\nnew line"} +Inf - {"gauge.name","name*2"="佖佥","name.1"="Björn"} 3.14E42 + {"gauge.name","name.1"="Björn","name*2"="佖佥"} 3.14E42 + {"gauge.name","name.1"="val with\\nnew line","name*2"="val with \\\\backslash and \\"quotes\\""} +Inf """; GaugeSnapshot gauge = GaugeSnapshot.builder() 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 373c7550e..45ecfb280 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 @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** Immutable snapshot of a Counter. */ public class CounterSnapshot extends MetricSnapshot { @@ -97,7 +98,7 @@ public Builder value(double value) { return this; } - public Builder exemplar(Exemplar exemplar) { + public Builder exemplar(@Nullable Exemplar exemplar) { this.exemplar = exemplar; return this; } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 70b6ecd39..5d5ee5aa4 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,7 +1,5 @@ package io.prometheus.metrics.model.snapshots; -import javax.annotation.Nullable; - import static java.lang.Character.MAX_CODE_POINT; import static java.lang.Character.MAX_LOW_SURROGATE; import static java.lang.Character.MIN_HIGH_SURROGATE; @@ -11,6 +9,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; /** * Utility for Prometheus Metric and Label naming. @@ -449,13 +448,15 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( private static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { List escapedExemplars = new ArrayList<>(exemplars.size()); for (Exemplar exemplar : exemplars) { - Exemplar escaped = escapeExemplar(exemplar, scheme); - escapedExemplars.add(escaped); + escapedExemplars.add(escapeExemplar(exemplar, scheme)); } return Exemplars.of(escapedExemplars); } - private static Exemplar escapeExemplar(Exemplar exemplar, EscapingScheme scheme) { + private static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { + if (exemplar == null) { + return null; + } return Exemplar.builder() .labels(escapeLabels(exemplar.getLabels(), scheme)) .timestampMillis(exemplar.getTimestampMillis()) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 510041f1e..06fc863c7 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -14,7 +14,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.util.stream.Stream; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; From 7f2c52aab78f01e16e1491d4a75efc5422dc9d03 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 17:16:34 +0200 Subject: [PATCH 060/106] fix tests Signed-off-by: Gregor Zeitlinger --- .../metrics/core/metrics/CounterTest.java | 33 ++++++++++--------- .../metrics/core/metrics/HistogramTest.java | 6 ---- .../metrics/core/metrics/InfoTest.java | 33 ++++++++----------- .../PrometheusProtobufWriterImpl.java | 2 +- .../ProtobufExpositionFormatsTest.java | 6 +++- 5 files changed, 37 insertions(+), 43 deletions(-) diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java index 8ad990151..302bdad14 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java @@ -23,6 +23,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class CounterTest { @@ -104,22 +106,21 @@ public void testLabels() { assertThat(getValue(labels, "l", "b")).isCloseTo(3.0, offset(.001)); } - @Test - public void testTotalStrippedFromName() { - for (String name : - new String[] { - "my_counter_total", "my.counter.total", - "my_counter_seconds_total", "my.counter.seconds.total", - "my_counter", "my.counter", - "my_counter_seconds", "my.counter.seconds" - }) { - Counter counter = Counter.builder().name(name).unit(Unit.SECONDS).build(); - Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(counter.collect()); - assertThat(ProtobufUtil.shortDebugString(protobufData)) - .isEqualTo( - "name: \"my_counter_seconds_total\" type: COUNTER metric { counter { value: 0.0 } }"); - } + @ParameterizedTest + @ValueSource( + strings = { + "my_counter_total", + "my_counter_seconds_total", + "my_counter", + "my_counter_seconds", + }) + public void testTotalStrippedFromName(String name) { + Counter counter = Counter.builder().name(name).unit(Unit.SECONDS).build(); + Metrics.MetricFamily protobufData = + new PrometheusProtobufWriterImpl().convert(counter.collect()); + assertThat(ProtobufUtil.shortDebugString(protobufData)) + .isEqualTo( + "name: \"my_counter_seconds_total\" type: COUNTER metric { counter { value: 0.0 } }"); } @Test diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index 29518b463..224433d19 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -1254,12 +1254,6 @@ public void testIllegalLabelNamePrefix() { .isThrownBy(() -> Histogram.builder().name("test").labelNames("__hello")); } - @Test - public void testIllegalName() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> Histogram.builder().name("my_namespace/server.durations")); - } - @Test public void testNoName() { assertThatExceptionOfType(IllegalArgumentException.class) diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index b21288d5f..00a036dd4 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -15,27 +15,21 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class InfoTest { - @Test - public void testInfoStrippedFromName() { - for (String name : - new String[] { - "jvm.runtime", "jvm_runtime", - "jvm.runtime.info", "jvm_runtime_info" - }) { - for (String labelName : new String[] {"my.key", "my_key"}) { - Info info = Info.builder().name(name).labelNames(labelName).build(); - info.addLabelValues("value"); - Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(info.collect()); - assertThat(ProtobufUtil.shortDebugString(protobufData)) - .isEqualTo( - "name: \"jvm_runtime_info\" type: GAUGE metric { label { name: \"my_key\" value:" - + " \"value\" } gauge { value: 1.0 } }"); - } - } + @ParameterizedTest + @ValueSource(strings = {"jvm.runtime", "jvm.runtime.info"}) + public void testInfoStrippedFromName(String name) { + Info info = Info.builder().name(name).labelNames("my.key").build(); + info.addLabelValues("value"); + Metrics.MetricFamily protobufData = new PrometheusProtobufWriterImpl().convert(info.collect()); + assertThat(ProtobufUtil.shortDebugString(protobufData)) + .isEqualTo( + "name: \"jvm.runtime_info\" type: GAUGE metric { label { name: \"my.key\" value:" + + " \"value\" } gauge { value: 1.0 } }"); } @Test @@ -127,7 +121,8 @@ public void testConstLabelsDuplicate2() { private void assertTextFormat(String expected, Info info) throws IOException { OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - writer.write(outputStream, MetricSnapshots.of(info.collect()), EscapingScheme.NO_ESCAPING); + writer.write( + outputStream, MetricSnapshots.of(info.collect()), EscapingScheme.UNDERSCORE_ESCAPING); String result = outputStream.toString(StandardCharsets.UTF_8.name()); if (!result.contains(expected)) { throw new AssertionError(expected + " is not contained in the following output:\n" + result); diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index 780c4213d..bcda8dbda 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -63,7 +63,7 @@ public void write( } } - Metrics.MetricFamily convert(MetricSnapshot snapshot) { + public Metrics.MetricFamily convert(MetricSnapshot snapshot) { Metrics.MetricFamily.Builder builder = Metrics.MetricFamily.newBuilder(); if (snapshot instanceof CounterSnapshot) { for (CounterDataPointSnapshot data : ((CounterSnapshot) snapshot).getDataPoints()) { diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java index 6902c0264..3c2252a0e 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java @@ -5,14 +5,18 @@ import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; class ProtobufExpositionFormatsTest extends ExpositionFormatsTest { @Override protected void assertPrometheusProtobuf(String expected, MetricSnapshot snapshot) { PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl(); - Metrics.MetricFamily protobufData = writer.convert(snapshot); + Metrics.MetricFamily protobufData = + writer.convert( + PrometheusNaming.escapeMetricSnapshot(snapshot, EscapingScheme.UNDERSCORE_ESCAPING)); String actual = ProtobufUtil.shortDebugString(protobufData); assertThat(actual).isEqualTo(expected); } From bdb73bc17eb8ecaaed0b477d3c8eefe9b3408046 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 17:38:11 +0200 Subject: [PATCH 061/106] fix tests Signed-off-by: Gregor Zeitlinger --- .../instrumentation/dropwizard5/DropwizardExportsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java index 4ade85707..c6170fd14 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java @@ -350,8 +350,8 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, _registry.scrape(), EscapingScheme.NO_ESCAPING); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, _registry.scrape(), EscapingScheme.UNDERSCORE_ESCAPING); + return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } From 28b2fa023c4e9019dc82d3a881271b76e98957c2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 18:38:57 +0200 Subject: [PATCH 062/106] extract snapshot escaper Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/PrometheusNaming.java | 437 +---------------- .../model/snapshots/SnapshotEscaper.java | 440 ++++++++++++++++++ .../model/snapshots/PrometheusNamingTest.java | 273 ----------- .../model/snapshots/SnapshotEscaperTest.java | 285 ++++++++++++ 4 files changed, 726 insertions(+), 709 deletions(-) create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java create mode 100644 prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 5d5ee5aa4..3010c887c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,15 +1,7 @@ package io.prometheus.metrics.model.snapshots; -import static java.lang.Character.MAX_CODE_POINT; -import static java.lang.Character.MAX_LOW_SURROGATE; -import static java.lang.Character.MIN_HIGH_SURROGATE; - import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; /** * Utility for Prometheus Metric and Label naming. @@ -155,7 +147,7 @@ public static String validateUnitName(String name) { * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { - return PrometheusNaming.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); + return SnapshotEscaper.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); } /** @@ -275,431 +267,4 @@ private static String replaceIllegalCharsInUnitName(String name) { } return new String(sanitized); } - - /** Escapes the given metric names and labels with the given escaping scheme. */ - public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { - if (v == null) { - return null; - } - - if (scheme == EscapingScheme.NO_ESCAPING) { - return v; - } - - List outDataPoints = new ArrayList<>(); - - for (DataPointSnapshot d : v.getDataPoints()) { - if (!snapshotNeedsEscaping(d, scheme)) { - outDataPoints.add(d); - continue; - } - - DataPointSnapshot outDataPointSnapshot = - createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme); - outDataPoints.add(outDataPointSnapshot); - } - - return createEscapedMetricSnapshot( - v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); - } - - private static Labels escapeLabels(Labels labels, EscapingScheme scheme) { - Labels.Builder outLabelsBuilder = Labels.builder(); - - for (Label l : labels) { - outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); - } - - return outLabelsBuilder.build(); - } - - static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { - Labels labels = d.getLabels(); - if (labelsNeedsEscaping(labels, scheme)) { - return true; - } - if (d instanceof SummarySnapshot.SummaryDataPointSnapshot) { - return exemplarsNeedsEscaping( - ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme); - } - if (d instanceof HistogramSnapshot.HistogramDataPointSnapshot) { - return exemplarsNeedsEscaping( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme); - } - if (d instanceof CounterSnapshot.CounterDataPointSnapshot) { - return exemplarNeedsEscaping( - ((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme); - } - if (d instanceof UnknownSnapshot.UnknownDataPointSnapshot) { - return exemplarNeedsEscaping( - ((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme); - } - if (d instanceof GaugeSnapshot.GaugeDataPointSnapshot) { - return exemplarNeedsEscaping( - ((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme); - } - - return false; - } - - private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { - for (Label l : labels) { - if (needsEscaping(l.getName(), scheme)) { - return true; - } - } - return false; - } - - private static boolean exemplarNeedsEscaping(@Nullable Exemplar exemplar, EscapingScheme scheme) { - return exemplar != null && labelsNeedsEscaping(exemplar.getLabels(), scheme); - } - - private static boolean exemplarsNeedsEscaping(Exemplars exemplars, EscapingScheme scheme) { - for (Exemplar exemplar : exemplars) { - if (labelsNeedsEscaping(exemplar.getLabels(), scheme)) { - return true; - } - } - return false; - } - - private static DataPointSnapshot createEscapedDataPointSnapshot( - MetricSnapshot v, DataPointSnapshot d, Labels outLabels, EscapingScheme scheme) { - if (v instanceof CounterSnapshot) { - return CounterSnapshot.CounterDataPointSnapshot.builder() - .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) - .exemplar( - escapeExemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme)) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof GaugeSnapshot) { - return GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) - .exemplar( - escapeExemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme)) - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof HistogramSnapshot) { - return HistogramSnapshot.HistogramDataPointSnapshot.builder() - .classicHistogramBuckets( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) - .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) - .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) - .nativeZeroThreshold( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) - .nativeBucketsForPositiveValues( - ((HistogramSnapshot.HistogramDataPointSnapshot) d) - .getNativeBucketsForPositiveValues()) - .nativeBucketsForNegativeValues( - ((HistogramSnapshot.HistogramDataPointSnapshot) d) - .getNativeBucketsForNegativeValues()) - .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) - .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) - .exemplars( - escapeExemplars( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme)) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof SummarySnapshot) { - return SummarySnapshot.SummaryDataPointSnapshot.builder() - .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) - .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) - .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) - .exemplars( - escapeExemplars( - ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme)) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof InfoSnapshot) { - return InfoSnapshot.InfoDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = - StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()); - for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { - builder.state(state.getName(), state.isTrue()); - } - return builder.build(); - } else if (v instanceof UnknownSnapshot) { - return UnknownSnapshot.UnknownDataPointSnapshot.builder() - .labels(outLabels) - .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) - .exemplar( - escapeExemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme)) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else { - throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); - } - } - - private static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { - List escapedExemplars = new ArrayList<>(exemplars.size()); - for (Exemplar exemplar : exemplars) { - escapedExemplars.add(escapeExemplar(exemplar, scheme)); - } - return Exemplars.of(escapedExemplars); - } - - private static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { - if (exemplar == null) { - return null; - } - return Exemplar.builder() - .labels(escapeLabels(exemplar.getLabels(), scheme)) - .timestampMillis(exemplar.getTimestampMillis()) - .value(exemplar.getValue()) - .build(); - } - - private static MetricSnapshot createEscapedMetricSnapshot( - MetricSnapshot v, String outName, List outDataPoints) { - if (v instanceof CounterSnapshot) { - CounterSnapshot.Builder builder = - CounterSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((CounterSnapshot.CounterDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof GaugeSnapshot) { - GaugeSnapshot.Builder builder = - GaugeSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((GaugeSnapshot.GaugeDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof HistogramSnapshot) { - HistogramSnapshot.Builder builder = - HistogramSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()) - .gaugeHistogram(((HistogramSnapshot) v).isGaugeHistogram()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((HistogramSnapshot.HistogramDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof SummarySnapshot) { - SummarySnapshot.Builder builder = - SummarySnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((SummarySnapshot.SummaryDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof InfoSnapshot) { - InfoSnapshot.Builder builder = - InfoSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((InfoSnapshot.InfoDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.Builder builder = - StateSetSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((StateSetSnapshot.StateSetDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof UnknownSnapshot) { - UnknownSnapshot.Builder builder = - UnknownSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((UnknownSnapshot.UnknownDataPointSnapshot) d); - } - return builder.build(); - } else { - throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); - } - } - - /** - * Escapes the incoming name according to the provided escaping scheme. Depending on the rules of - * escaping, this may cause no change in the string that is returned (especially NO_ESCAPING, - * which by definition is a noop). This method does not do any validation of the name. - */ - public static String escapeName(String name, EscapingScheme scheme) { - if (name.isEmpty() || !needsEscaping(name, scheme)) { - return name; - } - - StringBuilder escaped = new StringBuilder(); - switch (scheme) { - case NO_ESCAPING: - return name; - case UNDERSCORE_ESCAPING: - for (int i = 0; i < name.length(); ) { - int c = name.codePointAt(i); - if (isValidLegacyChar(c, i)) { - escaped.appendCodePoint(c); - } else { - escaped.append('_'); - } - i += Character.charCount(c); - } - return escaped.toString(); - case DOTS_ESCAPING: - // Do not early return for legacy valid names, we still escape underscores. - for (int i = 0; i < name.length(); ) { - int c = name.codePointAt(i); - if (c == '_') { - escaped.append("__"); - } else if (c == '.') { - escaped.append("_dot_"); - } else if (isValidLegacyChar(c, i)) { - escaped.appendCodePoint(c); - } else { - escaped.append("__"); - } - i += Character.charCount(c); - } - return escaped.toString(); - case VALUE_ENCODING_ESCAPING: - escaped.append("U__"); - for (int i = 0; i < name.length(); ) { - int c = name.codePointAt(i); - if (c == '_') { - escaped.append("__"); - } else if (isValidLegacyChar(c, i)) { - escaped.appendCodePoint(c); - } else if (!isValidUtf8Char(c)) { - escaped.append("_FFFD_"); - } else { - escaped.append('_'); - escaped.append(Integer.toHexString(c)); - escaped.append('_'); - } - i += Character.charCount(c); - } - return escaped.toString(); - default: - throw new IllegalArgumentException("Invalid escaping scheme " + scheme); - } - } - - private static boolean needsEscaping(String name, EscapingScheme scheme) { - return !isValidLegacyMetricName(name) - || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); - } - - /** - * Unescapes the incoming name according to the provided escaping scheme if possible. Some schemes - * are partially or totally non-roundtripable. If any error is encountered, returns the original - * input. - */ - @SuppressWarnings("IncrementInForLoopAndHeader") - static String unescapeName(String name, EscapingScheme scheme) { - if (name.isEmpty()) { - return name; - } - switch (scheme) { - case NO_ESCAPING: - return name; - case UNDERSCORE_ESCAPING: - // It is not possible to unescape from underscore replacement. - return name; - case DOTS_ESCAPING: - name = name.replaceAll("_dot_", "."); - name = name.replaceAll("__", "_"); - return name; - case VALUE_ENCODING_ESCAPING: - Matcher matcher = Pattern.compile("U__").matcher(name); - if (matcher.find()) { - String escapedName = name.substring(matcher.end()); - StringBuilder unescaped = new StringBuilder(); - for (int i = 0; i < escapedName.length(); ) { - // All non-underscores are treated normally. - int c = escapedName.codePointAt(i); - if (c != '_') { - unescaped.appendCodePoint(c); - i += Character.charCount(c); - continue; - } - i++; - if (i >= escapedName.length()) { - return name; - } - // A double underscore is a single underscore. - if (escapedName.codePointAt(i) == '_') { - unescaped.append('_'); - i++; - continue; - } - // We think we are in a UTF-8 code, process it. - int utf8Val = 0; - boolean foundClosingUnderscore = false; - for (int j = 0; i < escapedName.length(); j++) { - // This is too many characters for a UTF-8 value. - if (j >= 6) { - return name; - } - // Found a closing underscore, convert to a char, check validity, and append. - if (escapedName.codePointAt(i) == '_') { - // char utf8Char = (char) utf8Val; - foundClosingUnderscore = true; - if (!isValidUtf8Char(utf8Val)) { - return name; - } - unescaped.appendCodePoint(utf8Val); - i++; - break; - } - char r = Character.toLowerCase(escapedName.charAt(i)); - utf8Val *= 16; - if (r >= '0' && r <= '9') { - utf8Val += r - '0'; - } else if (r >= 'a' && r <= 'f') { - utf8Val += r - 'a' + 10; - } else { - return name; - } - i++; - } - if (!foundClosingUnderscore) { - return name; - } - } - return unescaped.toString(); - } else { - return name; - } - default: - throw new IllegalArgumentException("Invalid escaping scheme " + scheme); - } - } - - static boolean isValidLegacyChar(int c, int i) { - return (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || c == '_' - || c == ':' - || (c >= '0' && c <= '9' && i > 0); - } - - private static boolean isValidUtf8Char(int c) { - return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); - } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java new file mode 100644 index 000000000..851bcd970 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -0,0 +1,440 @@ +package io.prometheus.metrics.model.snapshots; + +import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +class SnapshotEscaper { + static MetricSnapshot createEscapedMetricSnapshot( + MetricSnapshot v, String outName, List outDataPoints) { + if (v instanceof CounterSnapshot) { + CounterSnapshot.Builder builder = + CounterSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((CounterSnapshot.CounterDataPointSnapshot) d); + } + return builder.build(); + } else if (v instanceof GaugeSnapshot) { + GaugeSnapshot.Builder builder = + GaugeSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((GaugeSnapshot.GaugeDataPointSnapshot) d); + } + return builder.build(); + } else if (v instanceof HistogramSnapshot) { + HistogramSnapshot.Builder builder = + HistogramSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()) + .gaugeHistogram(((HistogramSnapshot) v).isGaugeHistogram()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((HistogramSnapshot.HistogramDataPointSnapshot) d); + } + return builder.build(); + } else if (v instanceof SummarySnapshot) { + SummarySnapshot.Builder builder = + SummarySnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((SummarySnapshot.SummaryDataPointSnapshot) d); + } + return builder.build(); + } else if (v instanceof InfoSnapshot) { + InfoSnapshot.Builder builder = + InfoSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((InfoSnapshot.InfoDataPointSnapshot) d); + } + return builder.build(); + } else if (v instanceof StateSetSnapshot) { + StateSetSnapshot.Builder builder = + StateSetSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((StateSetSnapshot.StateSetDataPointSnapshot) d); + } + return builder.build(); + } else if (v instanceof UnknownSnapshot) { + UnknownSnapshot.Builder builder = + UnknownSnapshot.builder() + .name(outName) + .help(v.getMetadata().getHelp()) + .unit(v.getMetadata().getUnit()); + for (DataPointSnapshot d : outDataPoints) { + builder.dataPoint((UnknownSnapshot.UnknownDataPointSnapshot) d); + } + return builder.build(); + } else { + throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); + } + } + + /** Escapes the given metric names and labels with the given escaping scheme. */ + public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { + if (v == null) { + return null; + } + + if (scheme == EscapingScheme.NO_ESCAPING) { + return v; + } + + List outDataPoints = new ArrayList<>(); + + for (DataPointSnapshot d : v.getDataPoints()) { + if (!snapshotNeedsEscaping(d, scheme)) { + outDataPoints.add(d); + continue; + } + + DataPointSnapshot outDataPointSnapshot = + createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme); + outDataPoints.add(outDataPointSnapshot); + } + + return SnapshotEscaper.createEscapedMetricSnapshot( + v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); + } + + private static Labels escapeLabels(Labels labels, EscapingScheme scheme) { + Labels.Builder outLabelsBuilder = Labels.builder(); + + for (Label l : labels) { + outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); + } + + return outLabelsBuilder.build(); + } + + static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { + Labels labels = d.getLabels(); + if (labelsNeedsEscaping(labels, scheme)) { + return true; + } + if (d instanceof SummarySnapshot.SummaryDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof HistogramSnapshot.HistogramDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof CounterSnapshot.CounterDataPointSnapshot) { + return exemplarNeedsEscaping( + ((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof UnknownSnapshot.UnknownDataPointSnapshot) { + return exemplarNeedsEscaping( + ((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof GaugeSnapshot.GaugeDataPointSnapshot) { + return exemplarNeedsEscaping( + ((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme); + } + + return false; + } + + private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { + for (Label l : labels) { + if (needsEscaping(l.getName(), scheme)) { + return true; + } + } + return false; + } + + private static boolean exemplarNeedsEscaping(@Nullable Exemplar exemplar, EscapingScheme scheme) { + return exemplar != null && labelsNeedsEscaping(exemplar.getLabels(), scheme); + } + + private static boolean exemplarsNeedsEscaping(Exemplars exemplars, EscapingScheme scheme) { + for (Exemplar exemplar : exemplars) { + if (labelsNeedsEscaping(exemplar.getLabels(), scheme)) { + return true; + } + } + return false; + } + + private static DataPointSnapshot createEscapedDataPointSnapshot( + MetricSnapshot v, DataPointSnapshot d, Labels outLabels, EscapingScheme scheme) { + if (v instanceof CounterSnapshot) { + return CounterSnapshot.CounterDataPointSnapshot.builder() + .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) + .exemplar( + escapeExemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme)) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof GaugeSnapshot) { + return GaugeSnapshot.GaugeDataPointSnapshot.builder() + .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) + .exemplar( + escapeExemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme)) + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof HistogramSnapshot) { + return HistogramSnapshot.HistogramDataPointSnapshot.builder() + .classicHistogramBuckets( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) + .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) + .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) + .nativeZeroThreshold( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) + .nativeBucketsForPositiveValues( + ((HistogramSnapshot.HistogramDataPointSnapshot) d) + .getNativeBucketsForPositiveValues()) + .nativeBucketsForNegativeValues( + ((HistogramSnapshot.HistogramDataPointSnapshot) d) + .getNativeBucketsForNegativeValues()) + .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) + .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) + .exemplars( + escapeExemplars( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme)) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof SummarySnapshot) { + return SummarySnapshot.SummaryDataPointSnapshot.builder() + .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) + .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) + .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) + .exemplars( + escapeExemplars( + ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme)) + .labels(outLabels) + .createdTimestampMillis(d.getCreatedTimestampMillis()) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof InfoSnapshot) { + return InfoSnapshot.InfoDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else if (v instanceof StateSetSnapshot) { + StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(outLabels) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()); + for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { + builder.state(state.getName(), state.isTrue()); + } + return builder.build(); + } else if (v instanceof UnknownSnapshot) { + return UnknownSnapshot.UnknownDataPointSnapshot.builder() + .labels(outLabels) + .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) + .exemplar( + escapeExemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme)) + .scrapeTimestampMillis(d.getScrapeTimestampMillis()) + .build(); + } else { + throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); + } + } + + private static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { + List escapedExemplars = new ArrayList<>(exemplars.size()); + for (Exemplar exemplar : exemplars) { + escapedExemplars.add(escapeExemplar(exemplar, scheme)); + } + return Exemplars.of(escapedExemplars); + } + + private static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { + if (exemplar == null) { + return null; + } + return Exemplar.builder() + .labels(escapeLabels(exemplar.getLabels(), scheme)) + .timestampMillis(exemplar.getTimestampMillis()) + .value(exemplar.getValue()) + .build(); + } + + /** + * Escapes the incoming name according to the provided escaping scheme. Depending on the rules of + * escaping, this may cause no change in the string that is returned (especially NO_ESCAPING, + * which by definition is a noop). This method does not do any validation of the name. + */ + public static String escapeName(String name, EscapingScheme scheme) { + if (name.isEmpty() || !needsEscaping(name, scheme)) { + return name; + } + + StringBuilder escaped = new StringBuilder(); + switch (scheme) { + case NO_ESCAPING: + return name; + case UNDERSCORE_ESCAPING: + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + case DOTS_ESCAPING: + // Do not early return for legacy valid names, we still escape underscores. + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (c == '.') { + escaped.append("_dot_"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append("__"); + } + i += Character.charCount(c); + } + return escaped.toString(); + case VALUE_ENCODING_ESCAPING: + escaped.append("U__"); + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else if (!isValidUtf8Char(c)) { + escaped.append("_FFFD_"); + } else { + escaped.append('_'); + escaped.append(Integer.toHexString(c)); + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + default: + throw new IllegalArgumentException("Invalid escaping scheme " + scheme); + } + } + + private static boolean needsEscaping(String name, EscapingScheme scheme) { + return !PrometheusNaming.isValidLegacyMetricName(name) + || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); + } + + /** + * Unescapes the incoming name according to the provided escaping scheme if possible. Some schemes + * are partially or totally non-roundtripable. If any error is encountered, returns the original + * input. + */ + @SuppressWarnings("IncrementInForLoopAndHeader") + static String unescapeName(String name, EscapingScheme scheme) { + if (name.isEmpty()) { + return name; + } + switch (scheme) { + case NO_ESCAPING: + return name; + case UNDERSCORE_ESCAPING: + // It is not possible to unescape from underscore replacement. + return name; + case DOTS_ESCAPING: + name = name.replaceAll("_dot_", "."); + name = name.replaceAll("__", "_"); + return name; + case VALUE_ENCODING_ESCAPING: + Matcher matcher = Pattern.compile("U__").matcher(name); + if (matcher.find()) { + String escapedName = name.substring(matcher.end()); + StringBuilder unescaped = new StringBuilder(); + for (int i = 0; i < escapedName.length(); ) { + // All non-underscores are treated normally. + int c = escapedName.codePointAt(i); + if (c != '_') { + unescaped.appendCodePoint(c); + i += Character.charCount(c); + continue; + } + i++; + if (i >= escapedName.length()) { + return name; + } + // A double underscore is a single underscore. + if (escapedName.codePointAt(i) == '_') { + unescaped.append('_'); + i++; + continue; + } + // We think we are in a UTF-8 code, process it. + int utf8Val = 0; + boolean foundClosingUnderscore = false; + for (int j = 0; i < escapedName.length(); j++) { + // This is too many characters for a UTF-8 value. + if (j >= 6) { + return name; + } + // Found a closing underscore, convert to a char, check validity, and append. + if (escapedName.codePointAt(i) == '_') { + // char utf8Char = (char) utf8Val; + foundClosingUnderscore = true; + if (!isValidUtf8Char(utf8Val)) { + return name; + } + unescaped.appendCodePoint(utf8Val); + i++; + break; + } + char r = Character.toLowerCase(escapedName.charAt(i)); + utf8Val *= 16; + if (r >= '0' && r <= '9') { + utf8Val += r - '0'; + } else if (r >= 'a' && r <= 'f') { + utf8Val += r - 'a' + 10; + } else { + return name; + } + i++; + } + if (!foundClosingUnderscore) { + return name; + } + } + return unescaped.toString(); + } else { + return name; + } + default: + throw new IllegalArgumentException("Invalid escaping scheme " + scheme); + } + } + + static boolean isValidLegacyChar(int c, int i) { + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || (c >= '0' && c <= '9' && i > 0); + } + + private static boolean isValidUtf8Char(int c) { + return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); + } +} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 06fc863c7..0efe22775 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,13 +1,10 @@ package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeMetricSnapshot; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeLabelName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeUnitName; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.unescapeName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateMetricName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateUnitName; import static org.assertj.core.api.Assertions.assertThat; @@ -17,7 +14,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; class PrometheusNamingTest { @@ -134,273 +130,4 @@ static Stream nameIsValid() { Arguments.of("aΩz", true), Arguments.of("a\ud800z", false)); } - - @ParameterizedTest - @MethodSource("escapeNameLegacyTestCases") - public void testEscapeNameLegacy( - String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - assertEscape(input, escapingScheme, expected, unescapeExpected); - } - - @ParameterizedTest - @MethodSource("escapeNameUtf8TestCases") - public void testEscapeNameUtf8( - String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - assertEscape(input, escapingScheme, expected, unescapeExpected); - } - - private static void assertEscape( - String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - String escaped = escapeName(input, escapingScheme); - assertThat(escaped).isEqualTo(expected); - assertThat(unescapeName(escaped, escapingScheme)).isEqualTo(unescapeExpected); - } - - static Stream escapeNameLegacyTestCases() { - return Stream.of( - Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, "", ""), - Arguments.of("", EscapingScheme.DOTS_ESCAPING, "", ""), - Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, "", ""), - Arguments.of( - "no:escaping_required", - EscapingScheme.UNDERSCORE_ESCAPING, - "no:escaping_required", - "no:escaping_required"), - // Dots escaping will escape underscores even though it's not strictly - // necessary for compatibility. - Arguments.of( - "no:escaping_required", - EscapingScheme.DOTS_ESCAPING, - "no:escaping__required", - "no:escaping_required"), - Arguments.of( - "no:escaping_required", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "no:escaping_required", - "no:escaping_required"), - Arguments.of( - "no:escaping_required", - EscapingScheme.UNDERSCORE_ESCAPING, - "no:escaping_required", - "no:escaping_required"), - Arguments.of( - "mysystem.prod.west.cpu.load", - EscapingScheme.DOTS_ESCAPING, - "mysystem_dot_prod_dot_west_dot_cpu_dot_load", - "mysystem.prod.west.cpu.load"), - Arguments.of( - "mysystem.prod.west.cpu.load_total", - EscapingScheme.DOTS_ESCAPING, - "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total", - "mysystem.prod.west.cpu.load_total"), - Arguments.of( - "http.status:sum", - EscapingScheme.DOTS_ESCAPING, - "http_dot_status:sum", - "http.status:sum"), - Arguments.of( - "label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), - Arguments.of( - "label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), - Arguments.of( - "label with 😱", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__label_20_with_20__1f631_", - "label with 😱"), - // name with unicode characters > 0x100 - Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__", "__"), - // Dots-replacement does not know the difference between two replaced - Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____", "__"), - Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_", "花火"), - // name with spaces and edge-case value - Arguments.of( - "label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), - Arguments.of( - "label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), - Arguments.of( - "label with Ā", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__label_20_with_20__100_", - "label with Ā")); - } - - static Stream escapeNameUtf8TestCases() { - return Stream.of( - // name with dots - needs UTF-8 validation for escaping to occur - Arguments.of( - "mysystem.prod.west.cpu.load", - EscapingScheme.UNDERSCORE_ESCAPING, - "mysystem_prod_west_cpu_load", - "mysystem_prod_west_cpu_load"), - Arguments.of( - "mysystem.prod.west.cpu.load", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load", - "mysystem.prod.west.cpu.load"), - Arguments.of( - "mysystem.prod.west.cpu.load_total", - EscapingScheme.UNDERSCORE_ESCAPING, - "mysystem_prod_west_cpu_load_total", - "mysystem_prod_west_cpu_load_total"), - Arguments.of( - "mysystem.prod.west.cpu.load_total", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total", - "mysystem.prod.west.cpu.load_total"), - Arguments.of( - "http.status:sum", - EscapingScheme.UNDERSCORE_ESCAPING, - "http_status:sum", - "http_status:sum"), - Arguments.of( - "http.status:sum", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__http_2e_status:sum", - "http.status:sum")); - } - - @ParameterizedTest - @CsvSource( - value = { - // empty string - "'',''", - // basic case, no error - "U__no:unescapingrequired,no:unescapingrequired", - // capitals ok, no error - "U__capitals_2E_ok,capitals.ok", - // underscores, no error - "U__underscores__doubled__,underscores_doubled_", - // invalid single underscore - "U__underscores_doubled_,U__underscores_doubled_", - // invalid single underscore, 2 - "U__underscores__doubled_,U__underscores__doubled_", - // giant fake UTF-8 code - "U__my__hack_2e_attempt_872348732fabdabbab_,U__my__hack_2e_attempt_872348732fabdabbab_", - // trailing UTF-8 - "U__my__hack_2e,U__my__hack_2e", - // invalid UTF-8 value - "U__bad__utf_2eg_,U__bad__utf_2eg_", - // surrogate UTF-8 value - "U__bad__utf_D900_,U__bad__utf_D900_", - }) - public void testValueUnescapeErrors(String escapedName, String expectedUnescapedName) { - assertThat(unescapeName(escapedName, EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo(expectedUnescapedName); - } - - @Test - public void testEscapeMetricSnapshotEmpty() { - MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); - MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got.getMetadata().getName()).isEqualTo("empty"); - assertThat(original.getMetadata().getName()).isEqualTo("empty"); - } - - @Test - public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { - testEscapeMetricSnapshot( - "my_metric", - "some_label", - "labelvalue", - "my_metric", - "some_label", - "labelvalue", - EscapingScheme.VALUE_ENCODING_ESCAPING, - CounterSnapshot.class); - } - - @Test - public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { - testEscapeMetricSnapshot( - "my_metric", - "some.label", - "labelvalue", - "my_metric", - "U__some_2e_label", - "labelvalue", - EscapingScheme.VALUE_ENCODING_ESCAPING, - CounterSnapshot.class); - } - - @Test - public void testEscapeMetricSnapshotCounterEscapingNeeded() { - testEscapeMetricSnapshot( - "my.metric", - "some?label", - "label??value", - "U__my_2e_metric", - "U__some_3f_label", - "label??value", - EscapingScheme.VALUE_ENCODING_ESCAPING, - CounterSnapshot.class); - } - - @Test - public void testEscapeMetricSnapshotGaugeEscapingNeeded() { - testEscapeMetricSnapshot( - "unicode.and.dots.花火", - "some_label", - "label??value", - "unicode_dot_and_dot_dots_dot_____", - "some__label", - "label??value", - EscapingScheme.DOTS_ESCAPING, - GaugeSnapshot.class); - } - - private void testEscapeMetricSnapshot( - String name, - String labelName, - String labelValue, - String expectedName, - String expectedLabelName, - String expectedLabelValue, - EscapingScheme escapingScheme, - Class snapshotType) { - - MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, snapshotType); - MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); - - assertThat(got.getMetadata().getName()).isEqualTo(expectedName); - assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(got.getDataPoints()).hasSize(1); - - DataPointSnapshot escapedData = got.getDataPoints().get(0); - assertThat((Iterable) escapedData.getLabels()) - .isEqualTo(Labels.builder().label(expectedLabelName, expectedLabelValue).build()); - - assertThat(original.getMetadata().getName()).isEqualTo(name); - assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); - assertThat(original.getDataPoints()).hasSize(1); - - DataPointSnapshot originalData = original.getDataPoints().get(0); - assertThat((Iterable) originalData.getLabels()) - .isEqualTo(Labels.builder().label(labelName, labelValue).build()); - } - - private MetricSnapshot createTestSnapshot( - String name, - String labelName, - String labelValue, - Class snapshotType) { - Labels labels = Labels.builder().label(labelName, labelValue).build(); - - if (snapshotType.equals(CounterSnapshot.class)) { - return CounterSnapshot.builder() - .name(name) - .help("some help text") - .dataPoint( - CounterSnapshot.CounterDataPointSnapshot.builder().value(34.2).labels(labels).build()) - .build(); - } else if (snapshotType.equals(GaugeSnapshot.class)) { - return GaugeSnapshot.builder() - .name(name) - .help("some help text") - .dataPoint( - GaugeSnapshot.GaugeDataPointSnapshot.builder().value(34.2).labels(labels).build()) - .build(); - } - - throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); - } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java new file mode 100644 index 000000000..f755a64c9 --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -0,0 +1,285 @@ +package io.prometheus.metrics.model.snapshots; + +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.unescapeName; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +class SnapshotEscaperTest { + + @ParameterizedTest + @MethodSource("escapeNameLegacyTestCases") + public void testEscapeNameLegacy( + String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + assertEscape(input, escapingScheme, expected, unescapeExpected); + } + + @ParameterizedTest + @MethodSource("escapeNameUtf8TestCases") + public void testEscapeNameUtf8( + String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + assertEscape(input, escapingScheme, expected, unescapeExpected); + } + + private static void assertEscape( + String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { + String escaped = escapeName(input, escapingScheme); + assertThat(escaped).isEqualTo(expected); + assertThat(unescapeName(escaped, escapingScheme)).isEqualTo(unescapeExpected); + } + + static Stream escapeNameLegacyTestCases() { + return Stream.of( + Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, "", ""), + Arguments.of("", EscapingScheme.DOTS_ESCAPING, "", ""), + Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, "", ""), + Arguments.of( + "no:escaping_required", + EscapingScheme.UNDERSCORE_ESCAPING, + "no:escaping_required", + "no:escaping_required"), + // Dots escaping will escape underscores even though it's not strictly + // necessary for compatibility. + Arguments.of( + "no:escaping_required", + EscapingScheme.DOTS_ESCAPING, + "no:escaping__required", + "no:escaping_required"), + Arguments.of( + "no:escaping_required", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "no:escaping_required", + "no:escaping_required"), + Arguments.of( + "no:escaping_required", + EscapingScheme.UNDERSCORE_ESCAPING, + "no:escaping_required", + "no:escaping_required"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load", + "mysystem.prod.west.cpu.load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total", + "mysystem.prod.west.cpu.load_total"), + Arguments.of( + "http.status:sum", + EscapingScheme.DOTS_ESCAPING, + "http_dot_status:sum", + "http.status:sum"), + Arguments.of( + "label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), + Arguments.of( + "label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), + Arguments.of( + "label with 😱", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__label_20_with_20__1f631_", + "label with 😱"), + // name with unicode characters > 0x100 + Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__", "__"), + // Dots-replacement does not know the difference between two replaced + Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____", "__"), + Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_", "花火"), + // name with spaces and edge-case value + Arguments.of( + "label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), + Arguments.of( + "label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), + Arguments.of( + "label with Ā", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__label_20_with_20__100_", + "label with Ā")); + } + + static Stream escapeNameUtf8TestCases() { + return Stream.of( + // name with dots - needs UTF-8 validation for escaping to occur + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load", + "mysystem_prod_west_cpu_load"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load", + "mysystem.prod.west.cpu.load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load_total", + "mysystem_prod_west_cpu_load_total"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total", + "mysystem.prod.west.cpu.load_total"), + Arguments.of( + "http.status:sum", + EscapingScheme.UNDERSCORE_ESCAPING, + "http_status:sum", + "http_status:sum"), + Arguments.of( + "http.status:sum", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__http_2e_status:sum", + "http.status:sum")); + } + + @ParameterizedTest + @CsvSource( + value = { + // empty string + "'',''", + // basic case, no error + "U__no:unescapingrequired,no:unescapingrequired", + // capitals ok, no error + "U__capitals_2E_ok,capitals.ok", + // underscores, no error + "U__underscores__doubled__,underscores_doubled_", + // invalid single underscore + "U__underscores_doubled_,U__underscores_doubled_", + // invalid single underscore, 2 + "U__underscores__doubled_,U__underscores__doubled_", + // giant fake UTF-8 code + "U__my__hack_2e_attempt_872348732fabdabbab_,U__my__hack_2e_attempt_872348732fabdabbab_", + // trailing UTF-8 + "U__my__hack_2e,U__my__hack_2e", + // invalid UTF-8 value + "U__bad__utf_2eg_,U__bad__utf_2eg_", + // surrogate UTF-8 value + "U__bad__utf_D900_,U__bad__utf_D900_", + }) + public void testValueUnescapeErrors(String escapedName, String expectedUnescapedName) { + assertThat(unescapeName(escapedName, EscapingScheme.VALUE_ENCODING_ESCAPING)) + .isEqualTo(expectedUnescapedName); + } + + @Test + public void testEscapeMetricSnapshotEmpty() { + MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(got.getMetadata().getName()).isEqualTo("empty"); + assertThat(original.getMetadata().getName()).isEqualTo("empty"); + } + + @Test + public void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { + testEscapeMetricSnapshot( + "my_metric", + "some_label", + "labelvalue", + "my_metric", + "some_label", + "labelvalue", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + public void testEscapeMetricSnapshotLabelNameEscapingNeeded() { + testEscapeMetricSnapshot( + "my_metric", + "some.label", + "labelvalue", + "my_metric", + "U__some_2e_label", + "labelvalue", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + public void testEscapeMetricSnapshotCounterEscapingNeeded() { + testEscapeMetricSnapshot( + "my.metric", + "some?label", + "label??value", + "U__my_2e_metric", + "U__some_3f_label", + "label??value", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + public void testEscapeMetricSnapshotGaugeEscapingNeeded() { + testEscapeMetricSnapshot( + "unicode.and.dots.花火", + "some_label", + "label??value", + "unicode_dot_and_dot_dots_dot_____", + "some__label", + "label??value", + EscapingScheme.DOTS_ESCAPING, + GaugeSnapshot.class); + } + + private void testEscapeMetricSnapshot( + String name, + String labelName, + String labelValue, + String expectedName, + String expectedLabelName, + String expectedLabelValue, + EscapingScheme escapingScheme, + Class snapshotType) { + + MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, snapshotType); + MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); + + assertThat(got.getMetadata().getName()).isEqualTo(expectedName); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints()).hasSize(1); + + DataPointSnapshot escapedData = got.getDataPoints().get(0); + assertThat((Iterable) escapedData.getLabels()) + .isEqualTo(Labels.builder().label(expectedLabelName, expectedLabelValue).build()); + + assertThat(original.getMetadata().getName()).isEqualTo(name); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints()).hasSize(1); + + DataPointSnapshot originalData = original.getDataPoints().get(0); + assertThat((Iterable) originalData.getLabels()) + .isEqualTo(Labels.builder().label(labelName, labelValue).build()); + } + + private MetricSnapshot createTestSnapshot( + String name, + String labelName, + String labelValue, + Class snapshotType) { + Labels labels = Labels.builder().label(labelName, labelValue).build(); + + if (snapshotType.equals(CounterSnapshot.class)) { + return CounterSnapshot.builder() + .name(name) + .help("some help text") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder().value(34.2).labels(labels).build()) + .build(); + } else if (snapshotType.equals(GaugeSnapshot.class)) { + return GaugeSnapshot.builder() + .name(name) + .help("some help text") + .dataPoint( + GaugeSnapshot.GaugeDataPointSnapshot.builder().value(34.2).labels(labels).build()) + .build(); + } + + throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); + } +} From ccef1a8b01b190a41770fcdebfa823d455eba857 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 18:45:21 +0200 Subject: [PATCH 063/106] extract snapshot escaper Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/model/snapshots/SnapshotEscaper.java | 2 +- .../prometheus/metrics/model/snapshots/MetricSnapshotsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 851bcd970..51fe94b04 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -106,7 +106,7 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche outDataPoints.add(outDataPointSnapshot); } - return SnapshotEscaper.createEscapedMetricSnapshot( + return createEscapedMetricSnapshot( v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java index ed2f66fec..04a824f02 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java @@ -11,7 +11,7 @@ class MetricSnapshotsTest { @Test public void testEmpty() { MetricSnapshots snapshots = MetricSnapshots.builder().build(); - assertThat(snapshots.stream().findAny().isPresent()).isFalse(); + assertThat(snapshots.stream().findAny()).isNotPresent(); } @Test From 001ab867dda878fa4a9b81fd58321d118a59ade0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 18:48:43 +0200 Subject: [PATCH 064/106] extract snapshot escaper Signed-off-by: Gregor Zeitlinger --- .../exporter/pushgateway/PushGateway.java | 2 +- .../PrometheusProtobufWriterImpl.java | 5 +- .../ProtobufExpositionFormatsTest.java | 3 +- .../OpenMetricsTextFormatWriter.java | 3 +- .../PrometheusTextFormatWriter.java | 5 +- .../model/snapshots/SnapshotEscaper.java | 60 ++++++++++--------- 6 files changed, 43 insertions(+), 35 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 596657340..0696caa6d 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,7 +1,7 @@ package io.prometheus.metrics.exporter.pushgateway; import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeName; import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index bcda8dbda..1d1816a08 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -21,6 +21,7 @@ import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantiles; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; @@ -43,7 +44,7 @@ public String getContentType() { public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) { StringBuilder stringBuilder = new StringBuilder(); for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { stringBuilder.append(TextFormat.printer().printToString(convert(snapshot))); } @@ -56,7 +57,7 @@ public void write( OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) throws IOException { for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { convert(snapshot).writeDelimitedTo(out); } diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java index 3c2252a0e..cec126cf8 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java @@ -8,6 +8,7 @@ import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; class ProtobufExpositionFormatsTest extends ExpositionFormatsTest { @@ -16,7 +17,7 @@ protected void assertPrometheusProtobuf(String expected, MetricSnapshot snapshot PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl(); Metrics.MetricFamily protobufData = writer.convert( - PrometheusNaming.escapeMetricSnapshot(snapshot, EscapingScheme.UNDERSCORE_ESCAPING)); + SnapshotEscaper.escapeMetricSnapshot(snapshot, EscapingScheme.UNDERSCORE_ESCAPING)); String actual = ProtobufUtil.shortDebugString(protobufData); assertThat(actual).isEqualTo(expected); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 6d1422adb..959724e94 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -23,6 +23,7 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; @@ -111,7 +112,7 @@ public void write( throws IOException { Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { writeCounter(writer, (CounterSnapshot) snapshot); 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 8970d1fdd..24292116b 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 @@ -20,6 +20,7 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; @@ -113,7 +114,7 @@ public void write( // "summary". Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { writeCounter(writer, (CounterSnapshot) snapshot); @@ -134,7 +135,7 @@ public void write( } if (writeCreatedTimestamps) { for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = PrometheusNaming.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { writeCreated(writer, snapshot); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 51fe94b04..5de7b041f 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -10,7 +10,38 @@ import java.util.regex.Pattern; import javax.annotation.Nullable; -class SnapshotEscaper { +public class SnapshotEscaper { + + private SnapshotEscaper() { + } + + /** Escapes the given metric names and labels with the given escaping scheme. */ + public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { + if (v == null) { + return null; + } + + if (scheme == EscapingScheme.NO_ESCAPING) { + return v; + } + + List outDataPoints = new ArrayList<>(); + + for (DataPointSnapshot d : v.getDataPoints()) { + if (!snapshotNeedsEscaping(d, scheme)) { + outDataPoints.add(d); + continue; + } + + DataPointSnapshot outDataPointSnapshot = + createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme); + outDataPoints.add(outDataPointSnapshot); + } + + return createEscapedMetricSnapshot( + v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); + } + static MetricSnapshot createEscapedMetricSnapshot( MetricSnapshot v, String outName, List outDataPoints) { if (v instanceof CounterSnapshot) { @@ -83,33 +114,6 @@ static MetricSnapshot createEscapedMetricSnapshot( } } - /** Escapes the given metric names and labels with the given escaping scheme. */ - public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { - if (v == null) { - return null; - } - - if (scheme == EscapingScheme.NO_ESCAPING) { - return v; - } - - List outDataPoints = new ArrayList<>(); - - for (DataPointSnapshot d : v.getDataPoints()) { - if (!snapshotNeedsEscaping(d, scheme)) { - outDataPoints.add(d); - continue; - } - - DataPointSnapshot outDataPointSnapshot = - createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme); - outDataPoints.add(outDataPointSnapshot); - } - - return createEscapedMetricSnapshot( - v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); - } - private static Labels escapeLabels(Labels labels, EscapingScheme scheme) { Labels.Builder outLabelsBuilder = Labels.builder(); From eee53be72a35e34f29796d593a5fe57b7380e8df Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 18:50:18 +0200 Subject: [PATCH 065/106] extract snapshot escaper Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/SnapshotEscaper.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 5de7b041f..cb815f273 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -12,8 +12,7 @@ public class SnapshotEscaper { - private SnapshotEscaper() { - } + private SnapshotEscaper() {} /** Escapes the given metric names and labels with the given escaping scheme. */ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { @@ -33,13 +32,12 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche continue; } - DataPointSnapshot outDataPointSnapshot = - createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme); - outDataPoints.add(outDataPointSnapshot); + outDataPoints.add( + createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme)); } return createEscapedMetricSnapshot( - v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); + v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); } static MetricSnapshot createEscapedMetricSnapshot( From c8c1e9b8a526c0f986cb96e3697ec892dc429e61 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 12 Aug 2025 19:46:04 +0200 Subject: [PATCH 066/106] avoid redundant escaping Signed-off-by: Gregor Zeitlinger --- .../PrometheusProtobufWriterImpl.java | 99 +++++----- .../OpenMetricsTextFormatWriter.java | 180 +++++++++++------- .../PrometheusTextFormatWriter.java | 150 +++++++++------ .../expositionformats/TextFormatUtil.java | 8 +- .../model/snapshots/SnapshotEscaper.java | 19 +- 5 files changed, 281 insertions(+), 175 deletions(-) diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index 1d1816a08..6c3e2a841 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.expositionformats.internal; import static io.prometheus.metrics.expositionformats.internal.ProtobufUtil.timestampFromMillis; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import com.google.protobuf.TextFormat; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; @@ -19,7 +20,6 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; @@ -46,7 +46,7 @@ public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme esca for (MetricSnapshot s : metricSnapshots) { MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { - stringBuilder.append(TextFormat.printer().printToString(convert(snapshot))); + stringBuilder.append(TextFormat.printer().printToString(convert(snapshot, escapingScheme))); } } return stringBuilder.toString(); @@ -59,88 +59,95 @@ public void write( for (MetricSnapshot s : metricSnapshots) { MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); if (!snapshot.getDataPoints().isEmpty()) { - convert(snapshot).writeDelimitedTo(out); + convert(snapshot, escapingScheme).writeDelimitedTo(out); } } } - public Metrics.MetricFamily convert(MetricSnapshot snapshot) { + Metrics.MetricFamily convert(MetricSnapshot snapshot, EscapingScheme scheme) { Metrics.MetricFamily.Builder builder = Metrics.MetricFamily.newBuilder(); if (snapshot instanceof CounterSnapshot) { for (CounterDataPointSnapshot data : ((CounterSnapshot) snapshot).getDataPoints()) { - builder.addMetric(convert(data)); + builder.addMetric(convert(data, scheme)); } - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), "_total", Metrics.MetricType.COUNTER); + setMetadataUnlessEmpty( + builder, snapshot.getMetadata(), "_total", Metrics.MetricType.COUNTER, scheme); } else if (snapshot instanceof GaugeSnapshot) { for (GaugeSnapshot.GaugeDataPointSnapshot data : ((GaugeSnapshot) snapshot).getDataPoints()) { - builder.addMetric(convert(data)); + builder.addMetric(convert(data, scheme)); } - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE); + setMetadataUnlessEmpty( + builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE, scheme); } else if (snapshot instanceof HistogramSnapshot) { HistogramSnapshot histogram = (HistogramSnapshot) snapshot; for (HistogramSnapshot.HistogramDataPointSnapshot data : histogram.getDataPoints()) { - builder.addMetric(convert(data)); + builder.addMetric(convert(data, scheme)); } Metrics.MetricType type = histogram.isGaugeHistogram() ? Metrics.MetricType.GAUGE_HISTOGRAM : Metrics.MetricType.HISTOGRAM; - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, type); + setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, type, scheme); } else if (snapshot instanceof SummarySnapshot) { for (SummarySnapshot.SummaryDataPointSnapshot data : ((SummarySnapshot) snapshot).getDataPoints()) { if (data.hasCount() || data.hasSum() || data.getQuantiles().size() > 0) { - builder.addMetric(convert(data)); + builder.addMetric(convert(data, scheme)); } } - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.SUMMARY); + setMetadataUnlessEmpty( + builder, snapshot.getMetadata(), null, Metrics.MetricType.SUMMARY, scheme); } else if (snapshot instanceof InfoSnapshot) { for (InfoSnapshot.InfoDataPointSnapshot data : ((InfoSnapshot) snapshot).getDataPoints()) { - builder.addMetric(convert(data)); + builder.addMetric(convert(data, scheme)); } - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), "_info", Metrics.MetricType.GAUGE); + setMetadataUnlessEmpty( + builder, snapshot.getMetadata(), "_info", Metrics.MetricType.GAUGE, scheme); } else if (snapshot instanceof StateSetSnapshot) { for (StateSetSnapshot.StateSetDataPointSnapshot data : ((StateSetSnapshot) snapshot).getDataPoints()) { for (int i = 0; i < data.size(); i++) { - builder.addMetric(convert(data, snapshot.getMetadata().getName(), i)); + builder.addMetric(convert(data, snapshot.getMetadata().getPrometheusName(), i, scheme)); } } - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE); + setMetadataUnlessEmpty( + builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE, scheme); } else if (snapshot instanceof UnknownSnapshot) { for (UnknownSnapshot.UnknownDataPointSnapshot data : ((UnknownSnapshot) snapshot).getDataPoints()) { - builder.addMetric(convert(data)); + builder.addMetric(convert(data, scheme)); } - setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.UNTYPED); + setMetadataUnlessEmpty( + builder, snapshot.getMetadata(), null, Metrics.MetricType.UNTYPED, scheme); } return builder.build(); } - private Metrics.Metric.Builder convert(CounterDataPointSnapshot data) { + private Metrics.Metric.Builder convert(CounterDataPointSnapshot data, EscapingScheme scheme) { Metrics.Counter.Builder counterBuilder = Metrics.Counter.newBuilder(); counterBuilder.setValue(data.getValue()); if (data.getExemplar() != null) { - counterBuilder.setExemplar(convert(data.getExemplar())); + counterBuilder.setExemplar(convert(data.getExemplar(), scheme)); } Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); metricBuilder.setCounter(counterBuilder.build()); setScrapeTimestamp(metricBuilder, data); return metricBuilder; } - private Metrics.Metric.Builder convert(GaugeSnapshot.GaugeDataPointSnapshot data) { + private Metrics.Metric.Builder convert(GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) { Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); gaugeBuilder.setValue(data.getValue()); Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); metricBuilder.setGauge(gaugeBuilder); setScrapeTimestamp(metricBuilder, data); return metricBuilder; } - private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnapshot data) { + private Metrics.Metric.Builder convert( + HistogramSnapshot.HistogramDataPointSnapshot data, EscapingScheme scheme) { Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); Metrics.Histogram.Builder histogramBuilder = Metrics.Histogram.newBuilder(); if (data.hasNativeHistogramData()) { @@ -158,7 +165,7 @@ private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnaps Metrics.Bucket.newBuilder() .setCumulativeCount(getNativeCount(data)) .setUpperBound(Double.POSITIVE_INFINITY); - bucketBuilder.setExemplar(convert(exemplar)); + bucketBuilder.setExemplar(convert(exemplar, scheme)); histogramBuilder.addBucket(bucketBuilder); } } @@ -177,13 +184,13 @@ private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnaps .setUpperBound(upperBound); Exemplar exemplar = data.getExemplars().get(lowerBound, upperBound); if (exemplar != null) { - bucketBuilder.setExemplar(convert(exemplar)); + bucketBuilder.setExemplar(convert(exemplar, scheme)); } histogramBuilder.addBucket(bucketBuilder); lowerBound = upperBound; } } - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); setScrapeTimestamp(metricBuilder, data); if (data.hasCount()) { histogramBuilder.setSampleCount(data.getCount()); @@ -195,7 +202,7 @@ private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnaps return metricBuilder; } - private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot data) { + private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) { Metrics.Summary.Builder summaryBuilder = Metrics.Summary.newBuilder(); if (data.hasCount()) { summaryBuilder.setSampleCount(data.getCount()); @@ -212,27 +219,27 @@ private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot .build()); } Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); metricBuilder.setSummary(summaryBuilder.build()); setScrapeTimestamp(metricBuilder, data); return metricBuilder; } - private Metrics.Metric.Builder convert(InfoSnapshot.InfoDataPointSnapshot data) { + private Metrics.Metric.Builder convert(InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) { Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); gaugeBuilder.setValue(1); - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); metricBuilder.setGauge(gaugeBuilder); setScrapeTimestamp(metricBuilder, data); return metricBuilder; } private Metrics.Metric.Builder convert( - StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i) { + StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) { Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); metricBuilder.addLabel( Metrics.LabelPair.newBuilder().setName(name).setValue(data.getName(i)).build()); if (data.isTrue(i)) { @@ -245,19 +252,20 @@ private Metrics.Metric.Builder convert( return metricBuilder; } - private Metrics.Metric.Builder convert(UnknownSnapshot.UnknownDataPointSnapshot data) { + private Metrics.Metric.Builder convert( + UnknownSnapshot.UnknownDataPointSnapshot data, EscapingScheme scheme) { Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); Metrics.Untyped.Builder untypedBuilder = Metrics.Untyped.newBuilder(); untypedBuilder.setValue(data.getValue()); - addLabels(metricBuilder, data.getLabels()); + addLabels(metricBuilder, data.getLabels(), scheme); metricBuilder.setUntyped(untypedBuilder); return metricBuilder; } - private Metrics.Exemplar.Builder convert(Exemplar exemplar) { + private Metrics.Exemplar.Builder convert(Exemplar exemplar, EscapingScheme scheme) { Metrics.Exemplar.Builder builder = Metrics.Exemplar.newBuilder(); builder.setValue(exemplar.getValue()); - addLabels(builder, exemplar.getLabels()); + addLabels(builder, exemplar.getLabels(), scheme); if (exemplar.hasTimestamp()) { builder.setTimestamp(timestampFromMillis(exemplar.getTimestampMillis())); } @@ -268,14 +276,15 @@ private void setMetadataUnlessEmpty( Metrics.MetricFamily.Builder builder, MetricMetadata metadata, String nameSuffix, - Metrics.MetricType type) { + Metrics.MetricType type, + EscapingScheme scheme) { if (builder.getMetricCount() == 0) { return; } if (nameSuffix == null) { - builder.setName(metadata.getName()); + builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme)); } else { - builder.setName(metadata.getName() + nameSuffix); + builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme) + nameSuffix); } if (metadata.getHelp() != null) { builder.setHelp(metadata.getHelp()); @@ -348,21 +357,23 @@ private void addBuckets( } } - private void addLabels(Metrics.Metric.Builder metricBuilder, Labels labels) { + private void addLabels( + Metrics.Metric.Builder metricBuilder, Labels labels, EscapingScheme scheme) { for (int i = 0; i < labels.size(); i++) { metricBuilder.addLabel( Metrics.LabelPair.newBuilder() - .setName(labels.getName(i)) + .setName(getSnapshotLabelName(labels, i, scheme)) .setValue(labels.getValue(i)) .build()); } } - private void addLabels(Metrics.Exemplar.Builder metricBuilder, Labels labels) { + private void addLabels( + Metrics.Exemplar.Builder metricBuilder, Labels labels, EscapingScheme scheme) { for (int i = 0; i < labels.size(); i++) { metricBuilder.addLabel( Metrics.LabelPair.newBuilder() - .setName(labels.getName(i)) + .setName(getSnapshotLabelName(labels, i, scheme)) .setValue(labels.getValue(i)) .build()); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 959724e94..6400d8246 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -6,6 +6,8 @@ import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; @@ -107,27 +109,26 @@ public String getContentType() { } @Override - public void write( - OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme scheme) throws IOException { Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, scheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { - writeCounter(writer, (CounterSnapshot) snapshot); + writeCounter(writer, (CounterSnapshot) snapshot, scheme); } else if (snapshot instanceof GaugeSnapshot) { - writeGauge(writer, (GaugeSnapshot) snapshot); + writeGauge(writer, (GaugeSnapshot) snapshot, scheme); } else if (snapshot instanceof HistogramSnapshot) { - writeHistogram(writer, (HistogramSnapshot) snapshot); + writeHistogram(writer, (HistogramSnapshot) snapshot, scheme); } else if (snapshot instanceof SummarySnapshot) { - writeSummary(writer, (SummarySnapshot) snapshot); + writeSummary(writer, (SummarySnapshot) snapshot, scheme); } else if (snapshot instanceof InfoSnapshot) { - writeInfo(writer, (InfoSnapshot) snapshot); + writeInfo(writer, (InfoSnapshot) snapshot, scheme); } else if (snapshot instanceof StateSetSnapshot) { - writeStateSet(writer, (StateSetSnapshot) snapshot); + writeStateSet(writer, (StateSetSnapshot) snapshot, scheme); } else if (snapshot instanceof UnknownSnapshot) { - writeUnknown(writer, (UnknownSnapshot) snapshot); + writeUnknown(writer, (UnknownSnapshot) snapshot, scheme); } } } @@ -135,39 +136,45 @@ public void write( writer.flush(); } - private void writeCounter(Writer writer, CounterSnapshot snapshot) throws IOException { + private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "counter", metadata); + writeMetadata(writer, "counter", metadata, scheme); for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), "_total", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme); writeDouble(writer, data.getValue()); - writeScrapeTimestampAndExemplar(writer, data, data.getExemplar()); - writeCreated(writer, metadata, data); + writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); + writeCreated(writer, metadata, data, scheme); } } - private void writeGauge(Writer writer, GaugeSnapshot snapshot) throws IOException { + private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "gauge", metadata); + writeMetadata(writer, "gauge", metadata, scheme); for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); + writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); if (exemplarsOnAllMetricTypesEnabled) { - writeScrapeTimestampAndExemplar(writer, data, data.getExemplar()); + writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); } else { - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } } - private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IOException { + private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); if (snapshot.isGaugeHistogram()) { - writeMetadata(writer, "gaugehistogram", metadata); - writeClassicHistogramBuckets(writer, metadata, "_gcount", "_gsum", snapshot.getDataPoints()); + writeMetadata(writer, "gaugehistogram", metadata, scheme); + writeClassicHistogramBuckets( + writer, metadata, "_gcount", "_gsum", snapshot.getDataPoints(), scheme); } else { - writeMetadata(writer, "histogram", metadata); - writeClassicHistogramBuckets(writer, metadata, "_count", "_sum", snapshot.getDataPoints()); + writeMetadata(writer, "histogram", metadata, scheme); + writeClassicHistogramBuckets( + writer, metadata, "_count", "_sum", snapshot.getDataPoints(), scheme); } } @@ -176,7 +183,8 @@ private void writeClassicHistogramBuckets( MetricMetadata metadata, String countSuffix, String sumSuffix, - List dataList) + List dataList, + EscapingScheme scheme) throws IOException { for (HistogramSnapshot.HistogramDataPointSnapshot data : dataList) { ClassicHistogramBuckets buckets = getClassicBuckets(data); @@ -186,9 +194,10 @@ private void writeClassicHistogramBuckets( cumulativeCount += buckets.getCount(i); writeNameAndLabels( writer, - metadata.getName(), + getMetadataName(metadata, scheme), "_bucket", data.getLabels(), + scheme, "le", buckets.getUpperBound(i)); writeLong(writer, cumulativeCount); @@ -198,13 +207,13 @@ private void writeClassicHistogramBuckets( } else { exemplar = exemplars.get(buckets.getUpperBound(i - 1), buckets.getUpperBound(i)); } - writeScrapeTimestampAndExemplar(writer, data, exemplar); + writeScrapeTimestampAndExemplar(writer, data, exemplar, scheme); } // In OpenMetrics format, histogram _count and _sum are either both present or both absent. if (data.hasCount() && data.hasSum()) { - writeCountAndSum(writer, metadata, data, countSuffix, sumSuffix, exemplars); + writeCountAndSum(writer, metadata, data, countSuffix, sumSuffix, exemplars, scheme); } - writeCreated(writer, metadata, data); + writeCreated(writer, metadata, data, scheme); } } @@ -218,7 +227,8 @@ private ClassicHistogramBuckets getClassicBuckets( } } - private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOException { + private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) + throws IOException { boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { @@ -226,7 +236,7 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce continue; } if (!metadataWritten) { - writeMetadata(writer, "summary", metadata); + writeMetadata(writer, "summary", metadata, scheme); metadataWritten = true; } Exemplars exemplars = data.getExemplars(); @@ -238,51 +248,61 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce int exemplarIndex = 1; for (Quantile quantile : data.getQuantiles()) { writeNameAndLabels( - writer, metadata.getName(), null, data.getLabels(), "quantile", quantile.getQuantile()); + writer, + getMetadataName(metadata, scheme), + null, + data.getLabels(), + scheme, + "quantile", + quantile.getQuantile()); writeDouble(writer, quantile.getValue()); if (exemplars.size() > 0 && exemplarsOnAllMetricTypesEnabled) { exemplarIndex = (exemplarIndex + 1) % exemplars.size(); - writeScrapeTimestampAndExemplar(writer, data, exemplars.get(exemplarIndex)); + writeScrapeTimestampAndExemplar(writer, data, exemplars.get(exemplarIndex), scheme); } else { - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } // Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics. - writeCountAndSum(writer, metadata, data, "_count", "_sum", exemplars); - writeCreated(writer, metadata, data); + writeCountAndSum(writer, metadata, data, "_count", "_sum", exemplars, scheme); + writeCreated(writer, metadata, data, scheme); } } - private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException { + private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "info", metadata); + writeMetadata(writer, "info", metadata, scheme); for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), "_info", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_info", data.getLabels(), scheme); writer.write("1"); - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } - private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOException { + private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "stateset", metadata); + writeMetadata(writer, "stateset", metadata, scheme); for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { for (int i = 0; i < data.size(); i++) { - writer.write(metadata.getName()); + writer.write(getMetadataName(metadata, scheme)); writer.write('{'); - for (int j = 0; j < data.getLabels().size(); j++) { + Labels labels = data.getLabels(); + for (int j = 0; j < labels.size(); j++) { if (j > 0) { writer.write(","); } - writer.write(data.getLabels().getName(j)); + writer.write(getSnapshotLabelName(labels, j, scheme)); writer.write("=\""); - writeEscapedString(writer, data.getLabels().getValue(j)); + writeEscapedString(writer, labels.getValue(j)); writer.write("\""); } - if (!data.getLabels().isEmpty()) { + if (!labels.isEmpty()) { writer.write(","); } - writer.write(metadata.getName()); + writer.write(getMetadataName(metadata, scheme)); writer.write("=\""); writeEscapedString(writer, data.getName(i)); writer.write("\"} "); @@ -291,21 +311,22 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx } else { writer.write("0"); } - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } } - private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOException { + private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "unknown", metadata); + writeMetadata(writer, "unknown", metadata, scheme); for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); + writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); if (exemplarsOnAllMetricTypesEnabled) { - writeScrapeTimestampAndExemplar(writer, data, data.getExemplar()); + writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); } else { - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } } @@ -316,28 +337,33 @@ private void writeCountAndSum( DistributionDataPointSnapshot data, String countSuffix, String sumSuffix, - Exemplars exemplars) + Exemplars exemplars, + EscapingScheme scheme) throws IOException { if (data.hasCount()) { - writeNameAndLabels(writer, metadata.getName(), countSuffix, data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), countSuffix, data.getLabels(), scheme); writeLong(writer, data.getCount()); if (exemplarsOnAllMetricTypesEnabled) { - writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest()); + writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest(), scheme); } else { - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } if (data.hasSum()) { - writeNameAndLabels(writer, metadata.getName(), sumSuffix, data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), sumSuffix, data.getLabels(), scheme); writeDouble(writer, data.getSum()); - writeScrapeTimestampAndExemplar(writer, data, null); + writeScrapeTimestampAndExemplar(writer, data, null, scheme); } } - private void writeCreated(Writer writer, MetricMetadata metadata, DataPointSnapshot data) + private void writeCreated( + Writer writer, MetricMetadata metadata, DataPointSnapshot data, EscapingScheme scheme) throws IOException { if (createdTimestampsEnabled && data.hasCreatedTimestamp()) { - writeNameAndLabels(writer, metadata.getName(), "_created", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_created", data.getLabels(), scheme); writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); if (data.hasScrapeTimestamp()) { writer.write(' '); @@ -347,9 +373,10 @@ private void writeCreated(Writer writer, MetricMetadata metadata, DataPointSnaps } } - private void writeNameAndLabels(Writer writer, String name, String suffix, Labels labels) + private void writeNameAndLabels( + Writer writer, String name, String suffix, Labels labels, EscapingScheme escapingScheme) throws IOException { - writeNameAndLabels(writer, name, suffix, labels, null, 0.0); + writeNameAndLabels(writer, name, suffix, labels, escapingScheme, null, 0.0); } private void writeNameAndLabels( @@ -357,6 +384,7 @@ private void writeNameAndLabels( String name, String suffix, Labels labels, + EscapingScheme escapingScheme, String additionalLabelName, double additionalLabelValue) throws IOException { @@ -369,7 +397,13 @@ private void writeNameAndLabels( } writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { - writeLabels(writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces); + writeLabels( + writer, + labels, + additionalLabelName, + additionalLabelValue, + metricInsideBraces, + escapingScheme); } else if (metricInsideBraces) { writer.write('}'); } @@ -377,14 +411,15 @@ private void writeNameAndLabels( } private void writeScrapeTimestampAndExemplar( - Writer writer, DataPointSnapshot data, Exemplar exemplar) throws IOException { + Writer writer, DataPointSnapshot data, Exemplar exemplar, EscapingScheme scheme) + throws IOException { if (data.hasScrapeTimestamp()) { writer.write(' '); writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); } if (exemplar != null) { writer.write(" # "); - writeLabels(writer, exemplar.getLabels(), null, 0, false); + writeLabels(writer, exemplar.getLabels(), null, 0, false, scheme); writer.write(' '); writeDouble(writer, exemplar.getValue()); if (exemplar.hasTimestamp()) { @@ -395,23 +430,24 @@ private void writeScrapeTimestampAndExemplar( writer.write('\n'); } - private void writeMetadata(Writer writer, String typeName, MetricMetadata metadata) + private void writeMetadata( + Writer writer, String typeName, MetricMetadata metadata, EscapingScheme scheme) throws IOException { writer.write("# TYPE "); - writeName(writer, metadata.getName(), NameType.Metric); + writeName(writer, getMetadataName(metadata, scheme), NameType.Metric); writer.write(' '); writer.write(typeName); writer.write('\n'); if (metadata.getUnit() != null) { writer.write("# UNIT "); - writeName(writer, metadata.getName(), NameType.Metric); + writeName(writer, getMetadataName(metadata, scheme), NameType.Metric); writer.write(' '); writeEscapedString(writer, metadata.getUnit().toString()); writer.write('\n'); } if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); - writeName(writer, metadata.getName(), NameType.Metric); + writeName(writer, getMetadataName(metadata, scheme), NameType.Metric); writer.write(' '); writeEscapedString(writer, metadata.getHelp()); writer.write('\n'); 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 24292116b..83cb1040c 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 @@ -6,6 +6,9 @@ import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; @@ -20,7 +23,6 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; -import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; @@ -106,43 +108,42 @@ public String getContentType() { } @Override - public void write( - OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) + public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme scheme) throws IOException { // See https://prometheus.io/docs/instrumenting/exposition_formats/ // "unknown", "gauge", "counter", "stateset", "info", "histogram", "gaugehistogram", and // "summary". Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = escapeMetricSnapshot(s, scheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { - writeCounter(writer, (CounterSnapshot) snapshot); + writeCounter(writer, (CounterSnapshot) snapshot, scheme); } else if (snapshot instanceof GaugeSnapshot) { - writeGauge(writer, (GaugeSnapshot) snapshot); + writeGauge(writer, (GaugeSnapshot) snapshot, scheme); } else if (snapshot instanceof HistogramSnapshot) { - writeHistogram(writer, (HistogramSnapshot) snapshot); + writeHistogram(writer, (HistogramSnapshot) snapshot, scheme); } else if (snapshot instanceof SummarySnapshot) { - writeSummary(writer, (SummarySnapshot) snapshot); + writeSummary(writer, (SummarySnapshot) snapshot, scheme); } else if (snapshot instanceof InfoSnapshot) { - writeInfo(writer, (InfoSnapshot) snapshot); + writeInfo(writer, (InfoSnapshot) snapshot, scheme); } else if (snapshot instanceof StateSetSnapshot) { - writeStateSet(writer, (StateSetSnapshot) snapshot); + writeStateSet(writer, (StateSetSnapshot) snapshot, scheme); } else if (snapshot instanceof UnknownSnapshot) { - writeUnknown(writer, (UnknownSnapshot) snapshot); + writeUnknown(writer, (UnknownSnapshot) snapshot, scheme); } } } if (writeCreatedTimestamps) { for (MetricSnapshot s : metricSnapshots) { - MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme); + MetricSnapshot snapshot = escapeMetricSnapshot(s, scheme); if (!snapshot.getDataPoints().isEmpty()) { if (snapshot instanceof CounterSnapshot) { - writeCreated(writer, snapshot); + writeCreated(writer, snapshot, scheme); } else if (snapshot instanceof HistogramSnapshot) { - writeCreated(writer, snapshot); + writeCreated(writer, snapshot, scheme); } else if (snapshot instanceof SummarySnapshot) { - writeCreated(writer, snapshot); + writeCreated(writer, snapshot, scheme); } } } @@ -150,47 +151,52 @@ public void write( writer.flush(); } - public void writeCreated(Writer writer, MetricSnapshot snapshot) throws IOException { + public void writeCreated(Writer writer, MetricSnapshot snapshot, EscapingScheme scheme) + throws IOException { boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); for (DataPointSnapshot data : snapshot.getDataPoints()) { if (data.hasCreatedTimestamp()) { if (!metadataWritten) { - writeMetadata(writer, "_created", "gauge", metadata); + writeMetadata(writer, "_created", "gauge", metadata, scheme); metadataWritten = true; } - writeNameAndLabels(writer, metadata.getName(), "_created", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_created", data.getLabels(), scheme); writePrometheusTimestamp(writer, data.getCreatedTimestampMillis(), timestampsInMs); writeScrapeTimestampAndNewline(writer, data); } } } - private void writeCounter(Writer writer, CounterSnapshot snapshot) throws IOException { + private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingScheme scheme) + throws IOException { if (!snapshot.getDataPoints().isEmpty()) { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "_total", "counter", metadata); + writeMetadata(writer, "_total", "counter", metadata, scheme); for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), "_total", data.getLabels()); + writeNameAndLabels(writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } } } - private void writeGauge(Writer writer, GaugeSnapshot snapshot) throws IOException { + private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "", "gauge", metadata); + writeMetadata(writer, "", "gauge", metadata, scheme); for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); + writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } } - private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IOException { + private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "", "histogram", metadata); + writeMetadata(writer, "", "histogram", metadata, scheme); for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { ClassicHistogramBuckets buckets = getClassicBuckets(data); long cumulativeCount = 0; @@ -198,9 +204,10 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IO cumulativeCount += buckets.getCount(i); writeNameAndLabels( writer, - metadata.getName(), + getMetadataName(metadata, scheme), "_bucket", data.getLabels(), + scheme, "le", buckets.getUpperBound(i)); writeLong(writer, cumulativeCount); @@ -208,19 +215,21 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IO } if (!snapshot.isGaugeHistogram()) { if (data.hasCount()) { - writeNameAndLabels(writer, metadata.getName(), "_count", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasSum()) { - writeNameAndLabels(writer, metadata.getName(), "_sum", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } } } if (snapshot.isGaugeHistogram()) { - writeGaugeCountSum(writer, snapshot, metadata); + writeGaugeCountSum(writer, snapshot, metadata, scheme); } } @@ -235,17 +244,19 @@ private ClassicHistogramBuckets getClassicBuckets( } private void writeGaugeCountSum( - Writer writer, HistogramSnapshot snapshot, MetricMetadata metadata) throws IOException { + Writer writer, HistogramSnapshot snapshot, MetricMetadata metadata, EscapingScheme scheme) + throws IOException { // Prometheus text format does not support gaugehistogram's _gcount and _gsum. // So we append _gcount and _gsum as gauge metrics. boolean metadataWritten = false; for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { if (data.hasCount()) { if (!metadataWritten) { - writeMetadata(writer, "_gcount", "gauge", metadata); + writeMetadata(writer, "_gcount", "gauge", metadata, scheme); metadataWritten = true; } - writeNameAndLabels(writer, metadata.getName(), "_gcount", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_gcount", data.getLabels(), scheme); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } @@ -254,17 +265,19 @@ private void writeGaugeCountSum( for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { if (data.hasSum()) { if (!metadataWritten) { - writeMetadata(writer, "_gsum", "gauge", metadata); + writeMetadata(writer, "_gsum", "gauge", metadata, scheme); metadataWritten = true; } - writeNameAndLabels(writer, metadata.getName(), "_gsum", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_gsum", data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } } } - private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOException { + private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) + throws IOException { boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { @@ -272,50 +285,61 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce continue; } if (!metadataWritten) { - writeMetadata(writer, "", "summary", metadata); + writeMetadata(writer, "", "summary", metadata, scheme); metadataWritten = true; } for (Quantile quantile : data.getQuantiles()) { writeNameAndLabels( - writer, metadata.getName(), null, data.getLabels(), "quantile", quantile.getQuantile()); + writer, + getMetadataName(metadata, scheme), + null, + data.getLabels(), + scheme, + "quantile", + quantile.getQuantile()); writeDouble(writer, quantile.getValue()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasCount()) { - writeNameAndLabels(writer, metadata.getName(), "_count", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme); writeLong(writer, data.getCount()); writeScrapeTimestampAndNewline(writer, data); } if (data.hasSum()) { - writeNameAndLabels(writer, metadata.getName(), "_sum", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme); writeDouble(writer, data.getSum()); writeScrapeTimestampAndNewline(writer, data); } } } - private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException { + private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "_info", "gauge", metadata); + writeMetadata(writer, "_info", "gauge", metadata, scheme); for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), "_info", data.getLabels()); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_info", data.getLabels(), scheme); writer.write("1"); writeScrapeTimestampAndNewline(writer, data); } } - private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOException { + private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "", "gauge", metadata); + writeMetadata(writer, "", "gauge", metadata, scheme); for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { for (int i = 0; i < data.size(); i++) { - writer.write(metadata.getName()); + writer.write(getMetadataName(metadata, scheme)); writer.write('{'); for (int j = 0; j < data.getLabels().size(); j++) { if (j > 0) { writer.write(","); } - writer.write(data.getLabels().getName(j)); + writer.write(getSnapshotLabelName(data.getLabels(), j, scheme)); writer.write("=\""); writeEscapedString(writer, data.getLabels().getValue(j)); writer.write("\""); @@ -323,7 +347,7 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx if (!data.getLabels().isEmpty()) { writer.write(","); } - writer.write(metadata.getName()); + writer.write(getMetadataName(metadata, scheme)); writer.write("=\""); writeEscapedString(writer, data.getName(i)); writer.write("\"} "); @@ -337,19 +361,21 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx } } - private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOException { + private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme) + throws IOException { MetricMetadata metadata = snapshot.getMetadata(); - writeMetadata(writer, "", "untyped", metadata); + writeMetadata(writer, "", "untyped", metadata, scheme); for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, metadata.getName(), null, data.getLabels()); + writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } } - private void writeNameAndLabels(Writer writer, String name, String suffix, Labels labels) + private void writeNameAndLabels( + Writer writer, String name, String suffix, Labels labels, EscapingScheme escapingScheme) throws IOException { - writeNameAndLabels(writer, name, suffix, labels, null, 0.0); + writeNameAndLabels(writer, name, suffix, labels, escapingScheme, null, 0.0); } private void writeNameAndLabels( @@ -357,6 +383,7 @@ private void writeNameAndLabels( String name, String suffix, Labels labels, + EscapingScheme scheme, String additionalLabelName, double additionalLabelValue) throws IOException { @@ -369,7 +396,13 @@ private void writeNameAndLabels( } writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { - writeLabels(writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces); + writeLabels( + writer, + labels, + additionalLabelName, + additionalLabelValue, + metricInsideBraces, + scheme); } else if (metricInsideBraces) { writer.write('}'); } @@ -377,8 +410,13 @@ private void writeNameAndLabels( } private void writeMetadata( - Writer writer, String suffix, String typeString, MetricMetadata metadata) throws IOException { - String name = metadata.getName() + (suffix != null ? suffix : ""); + Writer writer, + String suffix, + String typeString, + MetricMetadata metadata, + EscapingScheme scheme) + throws IOException { + String name = getMetadataName(metadata, scheme) + (suffix != null ? suffix : ""); if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { writer.write("# HELP "); writeName(writer, name, NameType.Metric); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 03fc3af78..0a70a3d25 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -1,7 +1,10 @@ package io.prometheus.metrics.expositionformats; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; + import java.io.IOException; import java.io.Writer; @@ -104,7 +107,8 @@ static void writeLabels( Labels labels, String additionalLabelName, double additionalLabelValue, - boolean metricInsideBraces) + boolean metricInsideBraces, + EscapingScheme scheme) throws IOException { if (!metricInsideBraces) { writer.write('{'); @@ -113,7 +117,7 @@ static void writeLabels( if (i > 0 || metricInsideBraces) { writer.write(","); } - writeName(writer, labels.getName(i), NameType.Label); + writeName(writer, SnapshotEscaper.getSnapshotLabelName(labels, i, scheme), NameType.Label); writer.write("=\""); writeEscapedString(writer, labels.getValue(i)); writer.write("\""); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index cb815f273..15e8d214d 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -20,7 +20,8 @@ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingSche return null; } - if (scheme == EscapingScheme.NO_ESCAPING) { + if (scheme == EscapingScheme.NO_ESCAPING || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + // we re-use the prometheus name for underscore escaping as an optimization return v; } @@ -439,4 +440,20 @@ static boolean isValidLegacyChar(int c, int i) { private static boolean isValidUtf8Char(int c) { return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); } + + public static String getSnapshotLabelName(Labels labels, int index, EscapingScheme scheme) { + if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + return labels.getPrometheusName(index); + } else { + return labels.getName(index); + } + } + + public static String getMetadataName(MetricMetadata metadata, EscapingScheme scheme) { + if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + return metadata.getPrometheusName(); + } else { + return metadata.getName(); + } + } } From a57b4e6805daba5b8c1a8fe4d98360ff9d84a96b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 08:16:29 +0200 Subject: [PATCH 067/106] coverage Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/SnapshotEscaper.java | 5 ++-- .../model/snapshots/SnapshotEscaperTest.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 15e8d214d..6273d233a 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -15,7 +15,8 @@ public class SnapshotEscaper { private SnapshotEscaper() {} /** Escapes the given metric names and labels with the given escaping scheme. */ - public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { + @Nullable + public static MetricSnapshot escapeMetricSnapshot(@Nullable MetricSnapshot v, EscapingScheme scheme) { if (v == null) { return null; } @@ -338,7 +339,7 @@ public static String escapeName(String name, EscapingScheme scheme) { } } - private static boolean needsEscaping(String name, EscapingScheme scheme) { + public static boolean needsEscaping(String name, EscapingScheme scheme) { return !PrometheusNaming.isValidLegacyMetricName(name) || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index f755a64c9..944d39f31 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -2,6 +2,7 @@ import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.unescapeName; import static org.assertj.core.api.Assertions.assertThat; @@ -282,4 +283,30 @@ private MetricSnapshot createTestSnapshot( throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); } + + @Test + void escapeIsNoop() { + MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); + assertThat(original) + .isSameAs(escapeMetricSnapshot(original, EscapingScheme.NO_ESCAPING)) + .isSameAs(escapeMetricSnapshot(original, EscapingScheme.UNDERSCORE_ESCAPING)); + assertThat(escapeMetricSnapshot(null, EscapingScheme.NO_ESCAPING)).isNull(); + } + + @Test + void metadataName() { + MetricMetadata metadata = new MetricMetadata("test."); + assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.NO_ESCAPING)) + .isEqualTo("test."); + assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("test_"); + } + + @Test + void snapshotLabelName() { + Labels labels = Labels.builder().label("test.", "value").build(); + assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.NO_ESCAPING)).isEqualTo("test."); + assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("test_"); + } } From d4d4516b1a88a3a291fa79f4aa104481b859f9af Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 08:24:15 +0200 Subject: [PATCH 068/106] unescape is not used Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/SnapshotEscaper.java | 88 ----------- .../model/snapshots/SnapshotEscaperTest.java | 140 ++++-------------- 2 files changed, 25 insertions(+), 203 deletions(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 6273d233a..32294db70 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -6,8 +6,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nullable; public class SnapshotEscaper { @@ -344,92 +342,6 @@ public static boolean needsEscaping(String name, EscapingScheme scheme) { || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); } - /** - * Unescapes the incoming name according to the provided escaping scheme if possible. Some schemes - * are partially or totally non-roundtripable. If any error is encountered, returns the original - * input. - */ - @SuppressWarnings("IncrementInForLoopAndHeader") - static String unescapeName(String name, EscapingScheme scheme) { - if (name.isEmpty()) { - return name; - } - switch (scheme) { - case NO_ESCAPING: - return name; - case UNDERSCORE_ESCAPING: - // It is not possible to unescape from underscore replacement. - return name; - case DOTS_ESCAPING: - name = name.replaceAll("_dot_", "."); - name = name.replaceAll("__", "_"); - return name; - case VALUE_ENCODING_ESCAPING: - Matcher matcher = Pattern.compile("U__").matcher(name); - if (matcher.find()) { - String escapedName = name.substring(matcher.end()); - StringBuilder unescaped = new StringBuilder(); - for (int i = 0; i < escapedName.length(); ) { - // All non-underscores are treated normally. - int c = escapedName.codePointAt(i); - if (c != '_') { - unescaped.appendCodePoint(c); - i += Character.charCount(c); - continue; - } - i++; - if (i >= escapedName.length()) { - return name; - } - // A double underscore is a single underscore. - if (escapedName.codePointAt(i) == '_') { - unescaped.append('_'); - i++; - continue; - } - // We think we are in a UTF-8 code, process it. - int utf8Val = 0; - boolean foundClosingUnderscore = false; - for (int j = 0; i < escapedName.length(); j++) { - // This is too many characters for a UTF-8 value. - if (j >= 6) { - return name; - } - // Found a closing underscore, convert to a char, check validity, and append. - if (escapedName.codePointAt(i) == '_') { - // char utf8Char = (char) utf8Val; - foundClosingUnderscore = true; - if (!isValidUtf8Char(utf8Val)) { - return name; - } - unescaped.appendCodePoint(utf8Val); - i++; - break; - } - char r = Character.toLowerCase(escapedName.charAt(i)); - utf8Val *= 16; - if (r >= '0' && r <= '9') { - utf8Val += r - '0'; - } else if (r >= 'a' && r <= 'f') { - utf8Val += r - 'a' + 10; - } else { - return name; - } - i++; - } - if (!foundClosingUnderscore) { - return name; - } - } - return unescaped.toString(); - } else { - return name; - } - default: - throw new IllegalArgumentException("Invalid escaping scheme " + scheme); - } - } - static boolean isValidLegacyChar(int c, int i) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index 944d39f31..ad5436875 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -3,169 +3,79 @@ import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeName; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.unescapeName; import static org.assertj.core.api.Assertions.assertThat; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; class SnapshotEscaperTest { @ParameterizedTest @MethodSource("escapeNameLegacyTestCases") - public void testEscapeNameLegacy( - String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - assertEscape(input, escapingScheme, expected, unescapeExpected); - } - - @ParameterizedTest - @MethodSource("escapeNameUtf8TestCases") - public void testEscapeNameUtf8( - String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - assertEscape(input, escapingScheme, expected, unescapeExpected); - } - - private static void assertEscape( - String input, EscapingScheme escapingScheme, String expected, String unescapeExpected) { - String escaped = escapeName(input, escapingScheme); - assertThat(escaped).isEqualTo(expected); - assertThat(unescapeName(escaped, escapingScheme)).isEqualTo(unescapeExpected); + public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) { + assertThat(escapeName(input, escapingScheme)).isEqualTo(expected); } static Stream escapeNameLegacyTestCases() { return Stream.of( - Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, "", ""), - Arguments.of("", EscapingScheme.DOTS_ESCAPING, "", ""), - Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, "", ""), + Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, ""), + Arguments.of("", EscapingScheme.DOTS_ESCAPING, ""), + Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, ""), Arguments.of( - "no:escaping_required", - EscapingScheme.UNDERSCORE_ESCAPING, - "no:escaping_required", - "no:escaping_required"), + "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), // Dots escaping will escape underscores even though it's not strictly // necessary for compatibility. + Arguments.of("no:escaping_required", EscapingScheme.DOTS_ESCAPING, "no:escaping__required"), Arguments.of( - "no:escaping_required", - EscapingScheme.DOTS_ESCAPING, - "no:escaping__required", - "no:escaping_required"), - Arguments.of( - "no:escaping_required", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "no:escaping_required", - "no:escaping_required"), + "no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING, "no:escaping_required"), Arguments.of( - "no:escaping_required", - EscapingScheme.UNDERSCORE_ESCAPING, - "no:escaping_required", - "no:escaping_required"), + "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), Arguments.of( "mysystem.prod.west.cpu.load", EscapingScheme.DOTS_ESCAPING, - "mysystem_dot_prod_dot_west_dot_cpu_dot_load", - "mysystem.prod.west.cpu.load"), + "mysystem_dot_prod_dot_west_dot_cpu_dot_load"), Arguments.of( "mysystem.prod.west.cpu.load_total", EscapingScheme.DOTS_ESCAPING, - "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total", - "mysystem.prod.west.cpu.load_total"), - Arguments.of( - "http.status:sum", - EscapingScheme.DOTS_ESCAPING, - "http_dot_status:sum", - "http.status:sum"), - Arguments.of( - "label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), - Arguments.of( - "label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), + "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"), + Arguments.of("http.status:sum", EscapingScheme.DOTS_ESCAPING, "http_dot_status:sum"), + Arguments.of("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), + Arguments.of("label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____"), Arguments.of( - "label with 😱", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__label_20_with_20__1f631_", - "label with 😱"), + "label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__1f631_"), // name with unicode characters > 0x100 - Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__", "__"), + Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__"), // Dots-replacement does not know the difference between two replaced - Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____", "__"), - Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_", "花火"), + Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____"), + Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_"), // name with spaces and edge-case value + Arguments.of("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), + Arguments.of("label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____"), Arguments.of( - "label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__", "label_with__"), - Arguments.of( - "label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____", "label_with__"), - Arguments.of( - "label with Ā", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__label_20_with_20__100_", - "label with Ā")); - } - - static Stream escapeNameUtf8TestCases() { - return Stream.of( + "label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__100_"), // name with dots - needs UTF-8 validation for escaping to occur Arguments.of( "mysystem.prod.west.cpu.load", EscapingScheme.UNDERSCORE_ESCAPING, - "mysystem_prod_west_cpu_load", "mysystem_prod_west_cpu_load"), Arguments.of( "mysystem.prod.west.cpu.load", EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load", - "mysystem.prod.west.cpu.load"), + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"), Arguments.of( "mysystem.prod.west.cpu.load_total", EscapingScheme.UNDERSCORE_ESCAPING, - "mysystem_prod_west_cpu_load_total", "mysystem_prod_west_cpu_load_total"), Arguments.of( "mysystem.prod.west.cpu.load_total", EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total", - "mysystem.prod.west.cpu.load_total"), + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"), + Arguments.of("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING, "http_status:sum"), Arguments.of( - "http.status:sum", - EscapingScheme.UNDERSCORE_ESCAPING, - "http_status:sum", - "http_status:sum"), - Arguments.of( - "http.status:sum", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__http_2e_status:sum", - "http.status:sum")); - } - - @ParameterizedTest - @CsvSource( - value = { - // empty string - "'',''", - // basic case, no error - "U__no:unescapingrequired,no:unescapingrequired", - // capitals ok, no error - "U__capitals_2E_ok,capitals.ok", - // underscores, no error - "U__underscores__doubled__,underscores_doubled_", - // invalid single underscore - "U__underscores_doubled_,U__underscores_doubled_", - // invalid single underscore, 2 - "U__underscores__doubled_,U__underscores__doubled_", - // giant fake UTF-8 code - "U__my__hack_2e_attempt_872348732fabdabbab_,U__my__hack_2e_attempt_872348732fabdabbab_", - // trailing UTF-8 - "U__my__hack_2e,U__my__hack_2e", - // invalid UTF-8 value - "U__bad__utf_2eg_,U__bad__utf_2eg_", - // surrogate UTF-8 value - "U__bad__utf_D900_,U__bad__utf_D900_", - }) - public void testValueUnescapeErrors(String escapedName, String expectedUnescapedName) { - assertThat(unescapeName(escapedName, EscapingScheme.VALUE_ENCODING_ESCAPING)) - .isEqualTo(expectedUnescapedName); + "http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__http_2e_status:sum")); } @Test From a210cb8dce39fba482023d7679c38c56ba21b50c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 08:29:34 +0200 Subject: [PATCH 069/106] prepare move snapshot escaper Signed-off-by: Gregor Zeitlinger --- .../exporter/pushgateway/PushGateway.java | 2 +- .../PrometheusProtobufWriterImpl.java | 13 ++- .../ProtobufExpositionFormatsTest.java | 3 +- .../PrometheusTextFormatWriter.java | 10 +- .../expositionformats/TextFormatUtil.java | 1 - .../model/snapshots/PrometheusNaming.java | 87 ++++++++++++++++- .../model/snapshots/SnapshotEscaper.java | 94 +------------------ .../model/snapshots/PrometheusNamingTest.java | 66 +++++++++++++ .../model/snapshots/SnapshotEscaperTest.java | 70 -------------- 9 files changed, 170 insertions(+), 176 deletions(-) diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 0696caa6d..596657340 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,7 +1,7 @@ package io.prometheus.metrics.exporter.pushgateway; import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index 6c3e2a841..1d2d4c57a 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -64,7 +64,7 @@ public void write( } } - Metrics.MetricFamily convert(MetricSnapshot snapshot, EscapingScheme scheme) { + public Metrics.MetricFamily convert(MetricSnapshot snapshot, EscapingScheme scheme) { Metrics.MetricFamily.Builder builder = Metrics.MetricFamily.newBuilder(); if (snapshot instanceof CounterSnapshot) { for (CounterDataPointSnapshot data : ((CounterSnapshot) snapshot).getDataPoints()) { @@ -136,7 +136,8 @@ private Metrics.Metric.Builder convert(CounterDataPointSnapshot data, EscapingSc return metricBuilder; } - private Metrics.Metric.Builder convert(GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) { + private Metrics.Metric.Builder convert( + GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) { Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); gaugeBuilder.setValue(data.getValue()); Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); @@ -202,7 +203,8 @@ private Metrics.Metric.Builder convert( return metricBuilder; } - private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) { + private Metrics.Metric.Builder convert( + SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) { Metrics.Summary.Builder summaryBuilder = Metrics.Summary.newBuilder(); if (data.hasCount()) { summaryBuilder.setSampleCount(data.getCount()); @@ -225,7 +227,8 @@ private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot return metricBuilder; } - private Metrics.Metric.Builder convert(InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) { + private Metrics.Metric.Builder convert( + InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) { Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); gaugeBuilder.setValue(1); @@ -236,7 +239,7 @@ private Metrics.Metric.Builder convert(InfoSnapshot.InfoDataPointSnapshot data, } private Metrics.Metric.Builder convert( - StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) { + StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) { Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); addLabels(metricBuilder, data.getLabels(), scheme); diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java index cec126cf8..7902fe3ab 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java @@ -7,7 +7,6 @@ import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.SnapshotEscaper; class ProtobufExpositionFormatsTest extends ExpositionFormatsTest { @@ -17,7 +16,7 @@ protected void assertPrometheusProtobuf(String expected, MetricSnapshot snapshot PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl(); Metrics.MetricFamily protobufData = writer.convert( - SnapshotEscaper.escapeMetricSnapshot(snapshot, EscapingScheme.UNDERSCORE_ESCAPING)); + snapshot, EscapingScheme.UNDERSCORE_ESCAPING); String actual = ProtobufUtil.shortDebugString(protobufData); assertThat(actual).isEqualTo(expected); } 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 83cb1040c..285e4b1fb 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 @@ -175,7 +175,8 @@ private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingSchem MetricMetadata metadata = snapshot.getMetadata(); writeMetadata(writer, "_total", "counter", metadata, scheme); for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { - writeNameAndLabels(writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme); + writeNameAndLabels( + writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme); writeDouble(writer, data.getValue()); writeScrapeTimestampAndNewline(writer, data); } @@ -397,12 +398,7 @@ private void writeNameAndLabels( writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric); if (!labels.isEmpty() || additionalLabelName != null) { writeLabels( - writer, - labels, - additionalLabelName, - additionalLabelValue, - metricInsideBraces, - scheme); + writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces, scheme); } else if (metricInsideBraces) { writer.write('}'); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index 0a70a3d25..c60f08b42 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -4,7 +4,6 @@ import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.SnapshotEscaper; - import java.io.IOException; import java.io.Writer; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 3010c887c..799bf7a62 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,5 +1,9 @@ package io.prometheus.metrics.model.snapshots; +import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; @@ -147,7 +151,7 @@ public static String validateUnitName(String name) { * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { - return SnapshotEscaper.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); + return escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); } /** @@ -267,4 +271,85 @@ private static String replaceIllegalCharsInUnitName(String name) { } return new String(sanitized); } + + /** + * Escapes the incoming name according to the provided escaping scheme. Depending on the rules of + * escaping, this may cause no change in the string that is returned (especially NO_ESCAPING, + * which by definition is a noop). This method does not do any validation of the name. + */ + public static String escapeName(String name, EscapingScheme scheme) { + if (name.isEmpty() || !needsEscaping(name, scheme)) { + return name; + } + + StringBuilder escaped = new StringBuilder(); + switch (scheme) { + case NO_ESCAPING: + return name; + case UNDERSCORE_ESCAPING: + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + case DOTS_ESCAPING: + // Do not early return for legacy valid names, we still escape underscores. + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (c == '.') { + escaped.append("_dot_"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append("__"); + } + i += Character.charCount(c); + } + return escaped.toString(); + case VALUE_ENCODING_ESCAPING: + escaped.append("U__"); + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else if (!isValidUtf8Char(c)) { + escaped.append("_FFFD_"); + } else { + escaped.append('_'); + escaped.append(Integer.toHexString(c)); + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + default: + throw new IllegalArgumentException("Invalid escaping scheme " + scheme); + } + } + + public static boolean needsEscaping(String name, EscapingScheme scheme) { + return !isValidLegacyMetricName(name) + || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); + } + + static boolean isValidLegacyChar(int c, int i) { + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || (c >= '0' && c <= '9' && i > 0); + } + + private static boolean isValidUtf8Char(int c) { + return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 32294db70..38fd78ee4 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -1,9 +1,5 @@ package io.prometheus.metrics.model.snapshots; -import static java.lang.Character.MAX_CODE_POINT; -import static java.lang.Character.MAX_LOW_SURROGATE; -import static java.lang.Character.MIN_HIGH_SURROGATE; - import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; @@ -14,7 +10,8 @@ private SnapshotEscaper() {} /** Escapes the given metric names and labels with the given escaping scheme. */ @Nullable - public static MetricSnapshot escapeMetricSnapshot(@Nullable MetricSnapshot v, EscapingScheme scheme) { + public static MetricSnapshot escapeMetricSnapshot( + @Nullable MetricSnapshot v, EscapingScheme scheme) { if (v == null) { return null; } @@ -37,7 +34,7 @@ public static MetricSnapshot escapeMetricSnapshot(@Nullable MetricSnapshot v, Es } return createEscapedMetricSnapshot( - v, escapeName(v.getMetadata().getName(), scheme), outDataPoints); + v, PrometheusNaming.escapeName(v.getMetadata().getName(), scheme), outDataPoints); } static MetricSnapshot createEscapedMetricSnapshot( @@ -116,7 +113,7 @@ private static Labels escapeLabels(Labels labels, EscapingScheme scheme) { Labels.Builder outLabelsBuilder = Labels.builder(); for (Label l : labels) { - outLabelsBuilder.label(escapeName(l.getName(), scheme), l.getValue()); + outLabelsBuilder.label(PrometheusNaming.escapeName(l.getName(), scheme), l.getValue()); } return outLabelsBuilder.build(); @@ -153,7 +150,7 @@ static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { for (Label l : labels) { - if (needsEscaping(l.getName(), scheme)) { + if (PrometheusNaming.needsEscaping(l.getName(), scheme)) { return true; } } @@ -273,87 +270,6 @@ private static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingSche .build(); } - /** - * Escapes the incoming name according to the provided escaping scheme. Depending on the rules of - * escaping, this may cause no change in the string that is returned (especially NO_ESCAPING, - * which by definition is a noop). This method does not do any validation of the name. - */ - public static String escapeName(String name, EscapingScheme scheme) { - if (name.isEmpty() || !needsEscaping(name, scheme)) { - return name; - } - - StringBuilder escaped = new StringBuilder(); - switch (scheme) { - case NO_ESCAPING: - return name; - case UNDERSCORE_ESCAPING: - for (int i = 0; i < name.length(); ) { - int c = name.codePointAt(i); - if (isValidLegacyChar(c, i)) { - escaped.appendCodePoint(c); - } else { - escaped.append('_'); - } - i += Character.charCount(c); - } - return escaped.toString(); - case DOTS_ESCAPING: - // Do not early return for legacy valid names, we still escape underscores. - for (int i = 0; i < name.length(); ) { - int c = name.codePointAt(i); - if (c == '_') { - escaped.append("__"); - } else if (c == '.') { - escaped.append("_dot_"); - } else if (isValidLegacyChar(c, i)) { - escaped.appendCodePoint(c); - } else { - escaped.append("__"); - } - i += Character.charCount(c); - } - return escaped.toString(); - case VALUE_ENCODING_ESCAPING: - escaped.append("U__"); - for (int i = 0; i < name.length(); ) { - int c = name.codePointAt(i); - if (c == '_') { - escaped.append("__"); - } else if (isValidLegacyChar(c, i)) { - escaped.appendCodePoint(c); - } else if (!isValidUtf8Char(c)) { - escaped.append("_FFFD_"); - } else { - escaped.append('_'); - escaped.append(Integer.toHexString(c)); - escaped.append('_'); - } - i += Character.charCount(c); - } - return escaped.toString(); - default: - throw new IllegalArgumentException("Invalid escaping scheme " + scheme); - } - } - - public static boolean needsEscaping(String name, EscapingScheme scheme) { - return !PrometheusNaming.isValidLegacyMetricName(name) - || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); - } - - static boolean isValidLegacyChar(int c, int i) { - return (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || c == '_' - || c == ':' - || (c >= '0' && c <= '9' && i > 0); - } - - private static boolean isValidUtf8Char(int c) { - return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); - } - public static String getSnapshotLabelName(Labels labels, int index, EscapingScheme scheme) { if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { return labels.getPrometheusName(index); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index 0efe22775..d0c646f6b 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeLabelName; @@ -130,4 +131,69 @@ static Stream nameIsValid() { Arguments.of("aΩz", true), Arguments.of("a\ud800z", false)); } + + @ParameterizedTest + @MethodSource("escapeNameLegacyTestCases") + public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) { + assertThat(escapeName(input, escapingScheme)).isEqualTo(expected); + } + + static Stream escapeNameLegacyTestCases() { + return Stream.of( + Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, ""), + Arguments.of("", EscapingScheme.DOTS_ESCAPING, ""), + Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, ""), + Arguments.of( + "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), + // Dots escaping will escape underscores even though it's not strictly + // necessary for compatibility. + Arguments.of("no:escaping_required", EscapingScheme.DOTS_ESCAPING, "no:escaping__required"), + Arguments.of( + "no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING, "no:escaping_required"), + Arguments.of( + "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"), + Arguments.of("http.status:sum", EscapingScheme.DOTS_ESCAPING, "http_dot_status:sum"), + Arguments.of("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), + Arguments.of("label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____"), + Arguments.of( + "label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__1f631_"), + // name with unicode characters > 0x100 + Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__"), + // Dots-replacement does not know the difference between two replaced + Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____"), + Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_"), + // name with spaces and edge-case value + Arguments.of("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), + Arguments.of("label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____"), + Arguments.of( + "label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__100_"), + // name with dots - needs UTF-8 validation for escaping to occur + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load_total"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"), + Arguments.of("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING, "http_status:sum"), + Arguments.of( + "http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__http_2e_status:sum")); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index ad5436875..655e9e767 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -1,83 +1,13 @@ package io.prometheus.metrics.model.snapshots; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeName; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import static org.assertj.core.api.Assertions.assertThat; -import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; class SnapshotEscaperTest { - @ParameterizedTest - @MethodSource("escapeNameLegacyTestCases") - public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) { - assertThat(escapeName(input, escapingScheme)).isEqualTo(expected); - } - - static Stream escapeNameLegacyTestCases() { - return Stream.of( - Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, ""), - Arguments.of("", EscapingScheme.DOTS_ESCAPING, ""), - Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, ""), - Arguments.of( - "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), - // Dots escaping will escape underscores even though it's not strictly - // necessary for compatibility. - Arguments.of("no:escaping_required", EscapingScheme.DOTS_ESCAPING, "no:escaping__required"), - Arguments.of( - "no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING, "no:escaping_required"), - Arguments.of( - "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), - Arguments.of( - "mysystem.prod.west.cpu.load", - EscapingScheme.DOTS_ESCAPING, - "mysystem_dot_prod_dot_west_dot_cpu_dot_load"), - Arguments.of( - "mysystem.prod.west.cpu.load_total", - EscapingScheme.DOTS_ESCAPING, - "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"), - Arguments.of("http.status:sum", EscapingScheme.DOTS_ESCAPING, "http_dot_status:sum"), - Arguments.of("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), - Arguments.of("label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____"), - Arguments.of( - "label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__1f631_"), - // name with unicode characters > 0x100 - Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__"), - // Dots-replacement does not know the difference between two replaced - Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____"), - Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_"), - // name with spaces and edge-case value - Arguments.of("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), - Arguments.of("label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____"), - Arguments.of( - "label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__100_"), - // name with dots - needs UTF-8 validation for escaping to occur - Arguments.of( - "mysystem.prod.west.cpu.load", - EscapingScheme.UNDERSCORE_ESCAPING, - "mysystem_prod_west_cpu_load"), - Arguments.of( - "mysystem.prod.west.cpu.load", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"), - Arguments.of( - "mysystem.prod.west.cpu.load_total", - EscapingScheme.UNDERSCORE_ESCAPING, - "mysystem_prod_west_cpu_load_total"), - Arguments.of( - "mysystem.prod.west.cpu.load_total", - EscapingScheme.VALUE_ENCODING_ESCAPING, - "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"), - Arguments.of("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING, "http_status:sum"), - Arguments.of( - "http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__http_2e_status:sum")); - } - @Test public void testEscapeMetricSnapshotEmpty() { MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); From 7244c59ca8a9bbd49bca0592f6581cd634897a6a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 08:39:58 +0200 Subject: [PATCH 070/106] move snapshot escaper Signed-off-by: Gregor Zeitlinger --- .../PrometheusProtobufWriterImpl.java | 4 ++-- .../ProtobufExpositionFormatsTest.java | 4 +--- .../OpenMetricsTextFormatWriter.java | 5 ++--- .../PrometheusTextFormatWriter.java | 6 +++--- .../expositionformats}/SnapshotEscaper.java | 19 +++++++++++++++++-- .../expositionformats/TextFormatUtil.java | 1 - .../SnapshotEscaperTest.java | 17 +++++++++++++---- 7 files changed, 38 insertions(+), 18 deletions(-) rename {prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots => prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats}/SnapshotEscaper.java (92%) rename {prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots => prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats}/SnapshotEscaperTest.java (85%) diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index 1d2d4c57a..e783f2ae7 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -1,10 +1,11 @@ package io.prometheus.metrics.expositionformats.internal; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.expositionformats.internal.ProtobufUtil.timestampFromMillis; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import com.google.protobuf.TextFormat; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; +import io.prometheus.metrics.expositionformats.SnapshotEscaper; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; @@ -21,7 +22,6 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; import io.prometheus.metrics.model.snapshots.Quantiles; -import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java index 7902fe3ab..8996700a3 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java @@ -7,7 +7,6 @@ import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.SnapshotEscaper; class ProtobufExpositionFormatsTest extends ExpositionFormatsTest { @@ -15,8 +14,7 @@ class ProtobufExpositionFormatsTest extends ExpositionFormatsTest { protected void assertPrometheusProtobuf(String expected, MetricSnapshot snapshot) { PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl(); Metrics.MetricFamily protobufData = - writer.convert( - snapshot, EscapingScheme.UNDERSCORE_ESCAPING); + writer.convert(snapshot, EscapingScheme.UNDERSCORE_ESCAPING); String actual = ProtobufUtil.shortDebugString(protobufData); assertThat(actual).isEqualTo(expected); } diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 6400d8246..e0ebfef67 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -1,13 +1,13 @@ package io.prometheus.metrics.expositionformats; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getMetadataName; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; @@ -25,7 +25,6 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; -import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; 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 285e4b1fb..41caa60f3 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 @@ -1,14 +1,14 @@ package io.prometheus.metrics.expositionformats; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getMetadataName; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java similarity index 92% rename from prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java rename to prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java index 38fd78ee4..3963567f2 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java @@ -1,5 +1,21 @@ -package io.prometheus.metrics.model.snapshots; +package io.prometheus.metrics.expositionformats; +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.DataPointSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.model.snapshots.Exemplar; +import io.prometheus.metrics.model.snapshots.Exemplars; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; +import io.prometheus.metrics.model.snapshots.HistogramSnapshot; +import io.prometheus.metrics.model.snapshots.InfoSnapshot; +import io.prometheus.metrics.model.snapshots.Label; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricMetadata; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.StateSetSnapshot; +import io.prometheus.metrics.model.snapshots.SummarySnapshot; +import io.prometheus.metrics.model.snapshots.UnknownSnapshot; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; @@ -203,7 +219,6 @@ private static DataPointSnapshot createEscapedDataPointSnapshot( .nativeBucketsForNegativeValues( ((HistogramSnapshot.HistogramDataPointSnapshot) d) .getNativeBucketsForNegativeValues()) - .count(((HistogramSnapshot.HistogramDataPointSnapshot) d).getCount()) .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) .exemplars( escapeExemplars( diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index c60f08b42..bb0688f16 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -3,7 +3,6 @@ import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import java.io.IOException; import java.io.Writer; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/SnapshotEscaperTest.java similarity index 85% rename from prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java rename to prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/SnapshotEscaperTest.java index 655e9e767..233d2c693 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/SnapshotEscaperTest.java @@ -1,9 +1,18 @@ -package io.prometheus.metrics.model.snapshots; +package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; -import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.DataPointSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; +import io.prometheus.metrics.model.snapshots.Label; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricMetadata; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import java.util.Objects; import org.junit.jupiter.api.Test; class SnapshotEscaperTest { @@ -12,7 +21,7 @@ class SnapshotEscaperTest { public void testEscapeMetricSnapshotEmpty() { MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); - assertThat(got.getMetadata().getName()).isEqualTo("empty"); + assertThat(Objects.requireNonNull(got).getMetadata().getName()).isEqualTo("empty"); assertThat(original.getMetadata().getName()).isEqualTo("empty"); } From fbd94e69afae7192edbe0b68bf4243628a90e61b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 08:56:03 +0200 Subject: [PATCH 071/106] coverage Signed-off-by: Gregor Zeitlinger --- .../ExpositionFormatsTest.java | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 85271af5d..744fbcf38 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -266,11 +266,11 @@ public void testCounterMinimal() throws IOException { @Test public void testCounterWithDots() throws IOException { String openMetricsText = - "# TYPE my_request_count counter\n" - + "my_request_count_total{http_path=\"/hello\"} 3.0 # " - + exemplarWithDotsString - + "\n" - + "# EOF\n"; + """ + # TYPE U__my_2e_request_2e_count counter + U__my_2e_request_2e_count_total{U__http_2e_path="/hello"} 3.0 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383 + # EOF + """; String prometheusText = """ # TYPE my_request_count_total counter @@ -417,10 +417,10 @@ public void testGaugeMinimal() throws IOException { public void testGaugeWithDots() throws IOException { String openMetricsText = """ - # TYPE my_temperature_celsius gauge - # UNIT my_temperature_celsius celsius - # HELP my_temperature_celsius Temperature - my_temperature_celsius{location_id="data-center-1"} 23.0 + # TYPE U__my_2e_temperature_2e_celsius gauge + # UNIT U__my_2e_temperature_2e_celsius celsius + # HELP U__my_2e_temperature_2e_celsius Temperature + U__my_2e_temperature_2e_celsius{U__location_2e_id="data-center-1"} 23.0 # EOF """; String openMetricsTextWithExemplarsOnAllTimeSeries = @@ -1016,11 +1016,11 @@ public void testSummaryEmptyAndNonEmpty() throws IOException { public void testSummaryWithDots() throws IOException { String openMetricsText = """ - # TYPE my_request_duration_seconds summary - # UNIT my_request_duration_seconds seconds - # HELP my_request_duration_seconds Request duration in seconds - my_request_duration_seconds_count{http_path="/hello"} 1 - my_request_duration_seconds_sum{http_path="/hello"} 0.03 + # TYPE U__my_2e_request_2e_duration_2e_seconds summary + # UNIT U__my_2e_request_2e_duration_2e_seconds seconds + # HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds + U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 1 + U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 0.03 # EOF """; String openMetricsTextWithExemplarsOnAllTimeSeries = @@ -1878,15 +1878,15 @@ public void testClassicGaugeHistogramCountAndSum() throws IOException { @Test public void testClassicHistogramWithDots() throws IOException { String openMetricsText = - "# TYPE my_request_duration_seconds histogram\n" - + "# UNIT my_request_duration_seconds seconds\n" - + "# HELP my_request_duration_seconds Request duration in seconds\n" - + "my_request_duration_seconds_bucket{http_path=\"/hello\",le=\"+Inf\"} 130 # " - + exemplarWithDotsString - + "\n" - + "my_request_duration_seconds_count{http_path=\"/hello\"} 130\n" - + "my_request_duration_seconds_sum{http_path=\"/hello\"} 0.01\n" - + "# EOF\n"; + """ + # TYPE U__my_2e_request_2e_duration_2e_seconds histogram + # UNIT U__my_2e_request_2e_duration_2e_seconds seconds + # HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds + U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 130 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383 + U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 130 + U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 0.01 + # EOF + """; String openMetricsTextWithExemplarsOnAllTimeSeries = "# TYPE my_request_duration_seconds histogram\n" + "# UNIT my_request_duration_seconds seconds\n" @@ -2299,15 +2299,15 @@ public void testNativeHistogramMinimal() throws IOException { @Test public void testNativeHistogramWithDots() throws IOException { String openMetricsText = - "# TYPE my_request_duration_seconds histogram\n" - + "# UNIT my_request_duration_seconds seconds\n" - + "# HELP my_request_duration_seconds Request duration in seconds\n" - + "my_request_duration_seconds_bucket{http_path=\"/hello\",le=\"+Inf\"} 4 # " - + exemplarWithDotsString - + "\n" - + "my_request_duration_seconds_count{http_path=\"/hello\"} 4\n" - + "my_request_duration_seconds_sum{http_path=\"/hello\"} 3.2\n" - + "# EOF\n"; + """ + # TYPE U__my_2e_request_2e_duration_2e_seconds histogram + # UNIT U__my_2e_request_2e_duration_2e_seconds seconds + # HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds + U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 4 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383 + U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 4 + U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 3.2 + # EOF + """; String openMetricsTextWithExemplarsOnAllTimeSeries = "# TYPE my_request_duration_seconds histogram\n" + "# UNIT my_request_duration_seconds seconds\n" @@ -2409,9 +2409,9 @@ public void testInfo() throws IOException { public void testInfoWithDots() throws IOException { String openMetricsText = """ - # TYPE jvm_status info - # HELP jvm_status JVM status info - jvm_status_info{jvm_version="1.2.3"} 1 + # TYPE U__jvm_2e_status info + # HELP U__jvm_2e_status JVM status info + U__jvm_2e_status_info{U__jvm_2e_version="1.2.3"} 1 # EOF """; String prometheusText = @@ -2536,10 +2536,10 @@ public void testStateSetMinimal() throws IOException { public void testStateSetWithDots() throws IOException { String openMetricsText = """ - # TYPE my_application_state stateset - # HELP my_application_state My application state - my_application_state{data_center="us east",my_application_state="feature.enabled"} 1 - my_application_state{data_center="us east",my_application_state="is.alpha.version"} 0 + # TYPE U__my_2e_application_2e_state stateset + # HELP U__my_2e_application_2e_state My application state + U__my_2e_application_2e_state{U__data_2e_center="us east",U__my_2e_application_2e_state="feature.enabled"} 1 + U__my_2e_application_2e_state{U__data_2e_center="us east",U__my_2e_application_2e_state="is.alpha.version"} 0 # EOF """; String prometheusText = @@ -2673,10 +2673,10 @@ public void testUnknownMinimal() throws IOException { public void testUnknownWithDots() throws IOException { String openMetrics = """ - # TYPE some_unknown_metric_bytes unknown - # UNIT some_unknown_metric_bytes bytes - # HELP some_unknown_metric_bytes help message - some_unknown_metric_bytes{test_env="7"} 0.7 + # TYPE U__some_2e_unknown_2e_metric__bytes unknown + # UNIT U__some_2e_unknown_2e_metric__bytes bytes + # HELP U__some_2e_unknown_2e_metric__bytes help message + U__some_2e_unknown_2e_metric__bytes{U__test_2e_env="7"} 0.7 # EOF """; String openMetricsWithExemplarsOnAllTimeSeries = @@ -2823,7 +2823,7 @@ private void assertOpenMetricsText(String expected, MetricSnapshot snapshot) thr ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = OpenMetricsTextFormatWriter.builder().setCreatedTimestampsEnabled(true).build(); - writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(out).hasToString(expected); } From 6f1b6a3bac02ce790b98b94ba5ac2bf7d18d8dc3 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 09:42:52 +0200 Subject: [PATCH 072/106] fix Signed-off-by: Gregor Zeitlinger --- .../io/prometheus/metrics/core/metrics/CounterTest.java | 3 ++- .../io/prometheus/metrics/core/metrics/HistogramTest.java | 6 ++++-- .../java/io/prometheus/metrics/core/metrics/InfoTest.java | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java index 302bdad14..129516022 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java @@ -12,6 +12,7 @@ import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Label; import io.prometheus.metrics.model.snapshots.Labels; @@ -117,7 +118,7 @@ public void testLabels() { public void testTotalStrippedFromName(String name) { Counter counter = Counter.builder().name(name).unit(Unit.SECONDS).build(); Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(counter.collect()); + new PrometheusProtobufWriterImpl().convert(counter.collect(), EscapingScheme.NO_ESCAPING); assertThat(ProtobufUtil.shortDebugString(protobufData)) .isEqualTo( "name: \"my_counter_seconds_total\" type: COUNTER metric { counter { value: 0.0 } }"); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index 224433d19..9eab941bf 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -86,7 +86,8 @@ private void run() throws NoSuchFieldException, IllegalAccessException { } } Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(histogram.collect()); + new PrometheusProtobufWriterImpl() + .convert(histogram.collect(), EscapingScheme.NO_ESCAPING); String expectedWithMetadata = "name: \"test\" type: HISTOGRAM metric { histogram { " + expected + " } }"; assertThat(ProtobufUtil.shortDebugString(protobufData)) @@ -941,7 +942,8 @@ public void testDefaults() throws IOException { """; // protobuf - Metrics.MetricFamily protobufData = new PrometheusProtobufWriterImpl().convert(snapshot); + Metrics.MetricFamily protobufData = + new PrometheusProtobufWriterImpl().convert(snapshot, EscapingScheme.NO_ESCAPING); assertThat(ProtobufUtil.shortDebugString(protobufData)).isEqualTo(expectedProtobuf); // text diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index 00a036dd4..b9f4e40c1 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -25,7 +25,8 @@ class InfoTest { public void testInfoStrippedFromName(String name) { Info info = Info.builder().name(name).labelNames("my.key").build(); info.addLabelValues("value"); - Metrics.MetricFamily protobufData = new PrometheusProtobufWriterImpl().convert(info.collect()); + Metrics.MetricFamily protobufData = + new PrometheusProtobufWriterImpl().convert(info.collect(), EscapingScheme.NO_ESCAPING); assertThat(ProtobufUtil.shortDebugString(protobufData)) .isEqualTo( "name: \"jvm.runtime_info\" type: GAUGE metric { label { name: \"my.key\" value:" From 47554b240276e6488a3f74db6f0e1b903a2e7039 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 09:51:40 +0200 Subject: [PATCH 073/106] fix Signed-off-by: Gregor Zeitlinger --- .../instrumentation/dropwizard/DropwizardExportsTest.java | 2 +- .../dropwizard5/labels/CustomLabelMapperTest.java | 4 ++-- .../instrumentation/guava/CacheMetricsCollectorTest.java | 2 +- .../io/prometheus/metrics/instrumentation/jvm/TestUtil.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java index 8b4ac999c..0be6dbccf 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java @@ -341,7 +341,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, _registry.scrape(), EscapingScheme.NO_ESCAPING); + writer.write(out, _registry.scrape(), EscapingScheme.UNDERSCORE_ESCAPING); return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java index 0623ea531..f10c61d35 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java @@ -219,8 +219,8 @@ private String convertToOpenMetricsFormat(MetricSnapshots snapshots) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING); + return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } 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 edd423d6c..4e74a5ea8 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 @@ -162,7 +162,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { writer.write(out, registry.scrape(), EscapingScheme.NO_ESCAPING); - return out.toString(StandardCharsets.UTF_8.name()); + return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java index 56683f75a..09923a8be 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java @@ -13,6 +13,6 @@ static String convertToOpenMetricsFormat(MetricSnapshots snapshots) throws IOExc ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); - return out.toString(StandardCharsets.UTF_8.name()); + return out.toString(StandardCharsets.UTF_8); } } From 1f52452092bceeec10a412c22d5edb9394d0e6ff Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 11:44:58 +0200 Subject: [PATCH 074/106] don't validate in escape Signed-off-by: Gregor Zeitlinger --- .../PrometheusProtobufWriterImpl.java | 4 +- .../OpenMetricsTextFormatWriter.java | 5 +- .../PrometheusTextFormatWriter.java | 6 +- .../expositionformats/SnapshotEscaper.java | 303 ------------------ .../expositionformats/TextFormatUtil.java | 1 + .../model/snapshots/CounterSnapshot.java | 45 ++- .../model/snapshots/DataPointSnapshot.java | 8 +- .../DistributionDataPointSnapshot.java | 9 +- .../model/snapshots/GaugeSnapshot.java | 38 ++- .../model/snapshots/HistogramSnapshot.java | 89 ++++- .../metrics/model/snapshots/InfoSnapshot.java | 33 +- .../model/snapshots/MetricMetadata.java | 4 + .../model/snapshots/MetricSnapshot.java | 32 +- .../model/snapshots/SnapshotEscaper.java | 132 ++++++++ .../model/snapshots/StateSetSnapshot.java | 70 +++- .../model/snapshots/SummarySnapshot.java | 54 +++- .../model/snapshots/UnknownSnapshot.java | 38 ++- .../model/snapshots}/SnapshotEscaperTest.java | 14 +- 18 files changed, 504 insertions(+), 381 deletions(-) delete mode 100644 prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java rename {prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats => prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots}/SnapshotEscaperTest.java (87%) diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index e783f2ae7..1d2d4c57a 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -1,11 +1,10 @@ package io.prometheus.metrics.expositionformats.internal; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.expositionformats.internal.ProtobufUtil.timestampFromMillis; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import com.google.protobuf.TextFormat; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; -import io.prometheus.metrics.expositionformats.SnapshotEscaper; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; @@ -22,6 +21,7 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; import io.prometheus.metrics.model.snapshots.Quantiles; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index e0ebfef67..6400d8246 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -1,13 +1,13 @@ package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getMetadataName; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; @@ -25,6 +25,7 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Quantile; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; 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 41caa60f3..285e4b1fb 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 @@ -1,14 +1,14 @@ package io.prometheus.metrics.expositionformats; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.escapeMetricSnapshot; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getMetadataName; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java deleted file mode 100644 index 3963567f2..000000000 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/SnapshotEscaper.java +++ /dev/null @@ -1,303 +0,0 @@ -package io.prometheus.metrics.expositionformats; - -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; -import io.prometheus.metrics.model.snapshots.Exemplar; -import io.prometheus.metrics.model.snapshots.Exemplars; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.HistogramSnapshot; -import io.prometheus.metrics.model.snapshots.InfoSnapshot; -import io.prometheus.metrics.model.snapshots.Label; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.StateSetSnapshot; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; -import io.prometheus.metrics.model.snapshots.UnknownSnapshot; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nullable; - -public class SnapshotEscaper { - - private SnapshotEscaper() {} - - /** Escapes the given metric names and labels with the given escaping scheme. */ - @Nullable - public static MetricSnapshot escapeMetricSnapshot( - @Nullable MetricSnapshot v, EscapingScheme scheme) { - if (v == null) { - return null; - } - - if (scheme == EscapingScheme.NO_ESCAPING || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { - // we re-use the prometheus name for underscore escaping as an optimization - return v; - } - - List outDataPoints = new ArrayList<>(); - - for (DataPointSnapshot d : v.getDataPoints()) { - if (!snapshotNeedsEscaping(d, scheme)) { - outDataPoints.add(d); - continue; - } - - outDataPoints.add( - createEscapedDataPointSnapshot(v, d, escapeLabels(d.getLabels(), scheme), scheme)); - } - - return createEscapedMetricSnapshot( - v, PrometheusNaming.escapeName(v.getMetadata().getName(), scheme), outDataPoints); - } - - static MetricSnapshot createEscapedMetricSnapshot( - MetricSnapshot v, String outName, List outDataPoints) { - if (v instanceof CounterSnapshot) { - CounterSnapshot.Builder builder = - CounterSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((CounterSnapshot.CounterDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof GaugeSnapshot) { - GaugeSnapshot.Builder builder = - GaugeSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((GaugeSnapshot.GaugeDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof HistogramSnapshot) { - HistogramSnapshot.Builder builder = - HistogramSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()) - .gaugeHistogram(((HistogramSnapshot) v).isGaugeHistogram()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((HistogramSnapshot.HistogramDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof SummarySnapshot) { - SummarySnapshot.Builder builder = - SummarySnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((SummarySnapshot.SummaryDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof InfoSnapshot) { - InfoSnapshot.Builder builder = - InfoSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((InfoSnapshot.InfoDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.Builder builder = - StateSetSnapshot.builder().name(outName).help(v.getMetadata().getHelp()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((StateSetSnapshot.StateSetDataPointSnapshot) d); - } - return builder.build(); - } else if (v instanceof UnknownSnapshot) { - UnknownSnapshot.Builder builder = - UnknownSnapshot.builder() - .name(outName) - .help(v.getMetadata().getHelp()) - .unit(v.getMetadata().getUnit()); - for (DataPointSnapshot d : outDataPoints) { - builder.dataPoint((UnknownSnapshot.UnknownDataPointSnapshot) d); - } - return builder.build(); - } else { - throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); - } - } - - private static Labels escapeLabels(Labels labels, EscapingScheme scheme) { - Labels.Builder outLabelsBuilder = Labels.builder(); - - for (Label l : labels) { - outLabelsBuilder.label(PrometheusNaming.escapeName(l.getName(), scheme), l.getValue()); - } - - return outLabelsBuilder.build(); - } - - static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { - Labels labels = d.getLabels(); - if (labelsNeedsEscaping(labels, scheme)) { - return true; - } - if (d instanceof SummarySnapshot.SummaryDataPointSnapshot) { - return exemplarsNeedsEscaping( - ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme); - } - if (d instanceof HistogramSnapshot.HistogramDataPointSnapshot) { - return exemplarsNeedsEscaping( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme); - } - if (d instanceof CounterSnapshot.CounterDataPointSnapshot) { - return exemplarNeedsEscaping( - ((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme); - } - if (d instanceof UnknownSnapshot.UnknownDataPointSnapshot) { - return exemplarNeedsEscaping( - ((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme); - } - if (d instanceof GaugeSnapshot.GaugeDataPointSnapshot) { - return exemplarNeedsEscaping( - ((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme); - } - - return false; - } - - private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { - for (Label l : labels) { - if (PrometheusNaming.needsEscaping(l.getName(), scheme)) { - return true; - } - } - return false; - } - - private static boolean exemplarNeedsEscaping(@Nullable Exemplar exemplar, EscapingScheme scheme) { - return exemplar != null && labelsNeedsEscaping(exemplar.getLabels(), scheme); - } - - private static boolean exemplarsNeedsEscaping(Exemplars exemplars, EscapingScheme scheme) { - for (Exemplar exemplar : exemplars) { - if (labelsNeedsEscaping(exemplar.getLabels(), scheme)) { - return true; - } - } - return false; - } - - private static DataPointSnapshot createEscapedDataPointSnapshot( - MetricSnapshot v, DataPointSnapshot d, Labels outLabels, EscapingScheme scheme) { - if (v instanceof CounterSnapshot) { - return CounterSnapshot.CounterDataPointSnapshot.builder() - .value(((CounterSnapshot.CounterDataPointSnapshot) d).getValue()) - .exemplar( - escapeExemplar(((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme)) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof GaugeSnapshot) { - return GaugeSnapshot.GaugeDataPointSnapshot.builder() - .value(((GaugeSnapshot.GaugeDataPointSnapshot) d).getValue()) - .exemplar( - escapeExemplar(((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme)) - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof HistogramSnapshot) { - return HistogramSnapshot.HistogramDataPointSnapshot.builder() - .classicHistogramBuckets( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getClassicBuckets()) - .nativeSchema(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeSchema()) - .nativeZeroCount(((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroCount()) - .nativeZeroThreshold( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getNativeZeroThreshold()) - .nativeBucketsForPositiveValues( - ((HistogramSnapshot.HistogramDataPointSnapshot) d) - .getNativeBucketsForPositiveValues()) - .nativeBucketsForNegativeValues( - ((HistogramSnapshot.HistogramDataPointSnapshot) d) - .getNativeBucketsForNegativeValues()) - .sum(((HistogramSnapshot.HistogramDataPointSnapshot) d).getSum()) - .exemplars( - escapeExemplars( - ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme)) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof SummarySnapshot) { - return SummarySnapshot.SummaryDataPointSnapshot.builder() - .quantiles(((SummarySnapshot.SummaryDataPointSnapshot) d).getQuantiles()) - .count(((SummarySnapshot.SummaryDataPointSnapshot) d).getCount()) - .sum(((SummarySnapshot.SummaryDataPointSnapshot) d).getSum()) - .exemplars( - escapeExemplars( - ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme)) - .labels(outLabels) - .createdTimestampMillis(d.getCreatedTimestampMillis()) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof InfoSnapshot) { - return InfoSnapshot.InfoDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else if (v instanceof StateSetSnapshot) { - StateSetSnapshot.StateSetDataPointSnapshot.Builder builder = - StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(outLabels) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()); - for (StateSetSnapshot.State state : ((StateSetSnapshot.StateSetDataPointSnapshot) d)) { - builder.state(state.getName(), state.isTrue()); - } - return builder.build(); - } else if (v instanceof UnknownSnapshot) { - return UnknownSnapshot.UnknownDataPointSnapshot.builder() - .labels(outLabels) - .value(((UnknownSnapshot.UnknownDataPointSnapshot) d).getValue()) - .exemplar( - escapeExemplar(((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme)) - .scrapeTimestampMillis(d.getScrapeTimestampMillis()) - .build(); - } else { - throw new IllegalArgumentException("Unknown MetricSnapshot type: " + v.getClass()); - } - } - - private static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { - List escapedExemplars = new ArrayList<>(exemplars.size()); - for (Exemplar exemplar : exemplars) { - escapedExemplars.add(escapeExemplar(exemplar, scheme)); - } - return Exemplars.of(escapedExemplars); - } - - private static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { - if (exemplar == null) { - return null; - } - return Exemplar.builder() - .labels(escapeLabels(exemplar.getLabels(), scheme)) - .timestampMillis(exemplar.getTimestampMillis()) - .value(exemplar.getValue()) - .build(); - } - - public static String getSnapshotLabelName(Labels labels, int index, EscapingScheme scheme) { - if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { - return labels.getPrometheusName(index); - } else { - return labels.getName(index); - } - } - - public static String getMetadataName(MetricMetadata metadata, EscapingScheme scheme) { - if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { - return metadata.getPrometheusName(); - } else { - return metadata.getName(); - } - } -} diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index bb0688f16..c60f08b42 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -3,6 +3,7 @@ import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import java.io.IOException; import java.io.Writer; 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 45ecfb280..0d09d7b66 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 @@ -17,7 +17,12 @@ public class CounterSnapshot extends MetricSnapshot { * @param dataPoints the constructor will create a sorted copy of the collection. */ public CounterSnapshot(MetricMetadata metadata, Collection dataPoints) { - super(metadata, dataPoints); + this(metadata, dataPoints, false); + } + + private CounterSnapshot( + MetricMetadata metadata, Collection dataPoints, boolean internal) { + super(metadata, dataPoints, internal); } @SuppressWarnings("unchecked") @@ -26,6 +31,16 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new CounterSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static class CounterDataPointSnapshot extends DataPointSnapshot { private final double value; @@ -59,10 +74,23 @@ public CounterDataPointSnapshot( Exemplar exemplar, long createdTimestampMillis, long scrapeTimestampMillis) { - super(labels, createdTimestampMillis, scrapeTimestampMillis); + this(value, labels, exemplar, createdTimestampMillis, scrapeTimestampMillis, false); + } + + @SuppressWarnings("this-escape") + public CounterDataPointSnapshot( + double value, + Labels labels, + Exemplar exemplar, + long createdTimestampMillis, + long scrapeTimestampMillis, + boolean internal) { + super(labels, createdTimestampMillis, scrapeTimestampMillis, internal); this.value = value; this.exemplar = exemplar; - validate(); + if (!internal) { + validate(); + } } public double getValue() { @@ -80,6 +108,17 @@ protected void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new CounterSnapshot.CounterDataPointSnapshot( + value, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), + getCreatedTimestampMillis(), + getScrapeTimestampMillis(), + true); + } + public static Builder builder() { return new Builder(); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java index 6b455bff6..35f917d94 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java @@ -7,11 +7,13 @@ public abstract class DataPointSnapshot { private final long scrapeTimestampMillis; protected DataPointSnapshot( - Labels labels, long createdTimestampMillis, long scrapeTimestampMillis) { + Labels labels, long createdTimestampMillis, long scrapeTimestampMillis, boolean internal) { this.labels = labels; this.createdTimestampMillis = createdTimestampMillis; this.scrapeTimestampMillis = scrapeTimestampMillis; - validate(); + if (!internal) { + validate(); + } } private void validate() { @@ -85,4 +87,6 @@ public T scrapeTimestampMillis(long scrapeTimestampMillis) { protected abstract T self(); } + + abstract DataPointSnapshot escape(EscapingScheme escapingScheme); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java index c8092237c..1ae0559e1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java @@ -16,12 +16,15 @@ protected DistributionDataPointSnapshot( Exemplars exemplars, Labels labels, long createdTimestampMillis, - long scrapeTimestampMillis) { - super(labels, createdTimestampMillis, scrapeTimestampMillis); + long scrapeTimestampMillis, + boolean internal) { + super(labels, createdTimestampMillis, scrapeTimestampMillis, internal); this.count = count; this.sum = sum; this.exemplars = exemplars == null ? Exemplars.EMPTY : exemplars; - validate(); + if (!internal) { + validate(); + } } private void validate() { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java index 641aa166a..24f64a6f3 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java @@ -15,7 +15,12 @@ public final class GaugeSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public GaugeSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); + } + + private GaugeSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); } @SuppressWarnings("unchecked") @@ -24,6 +29,16 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new GaugeSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class GaugeDataPointSnapshot extends DataPointSnapshot { private final double value; @@ -48,7 +63,16 @@ public GaugeDataPointSnapshot(double value, Labels labels, Exemplar exemplar) { */ public GaugeDataPointSnapshot( double value, Labels labels, Exemplar exemplar, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); + this(value, labels, exemplar, scrapeTimestampMillis, false); + } + + private GaugeDataPointSnapshot( + double value, + Labels labels, + Exemplar exemplar, + long scrapeTimestampMillis, + boolean internal) { + super(labels, 0L, scrapeTimestampMillis, internal); this.value = value; this.exemplar = exemplar; } @@ -66,6 +90,16 @@ public static Builder builder() { return new Builder(); } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new GaugeSnapshot.GaugeDataPointSnapshot( + value, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), + getCreatedTimestampMillis(), + true); + } + public static class Builder extends DataPointSnapshot.Builder { private Exemplar exemplar = null; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java index 51b529616..a9a612c68 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java @@ -31,7 +31,15 @@ public HistogramSnapshot( boolean isGaugeHistogram, MetricMetadata metadata, Collection data) { - super(metadata, data); + this(isGaugeHistogram, metadata, data, false); + } + + private HistogramSnapshot( + boolean isGaugeHistogram, + MetricMetadata metadata, + Collection data, + boolean internal) { + super(metadata, data, internal); this.gaugeHistogram = isGaugeHistogram; } @@ -45,6 +53,17 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new HistogramSnapshot( + gaugeHistogram, + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class HistogramDataPointSnapshot extends DistributionDataPointSnapshot { // There are two types of histograms: Classic histograms and native histograms. @@ -222,33 +241,55 @@ public HistogramDataPointSnapshot( Exemplars exemplars, long createdTimestampMillis, long scrapeTimestampMillis) { - super( + this( + classicBuckets, + nativeSchema, + sum, + labels, + exemplars, + createdTimestampMillis, + scrapeTimestampMillis, calculateCount( classicBuckets, nativeSchema, nativeZeroCount, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues), - sum, - exemplars, - labels, - createdTimestampMillis, - scrapeTimestampMillis); - this.classicBuckets = classicBuckets; - this.nativeSchema = nativeSchema; - this.nativeZeroCount = nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroCount; - this.nativeZeroThreshold = nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroThreshold; - this.nativeBucketsForPositiveValues = nativeSchema == CLASSIC_HISTOGRAM ? NativeHistogramBuckets.EMPTY - : nativeBucketsForPositiveValues; - this.nativeBucketsForNegativeValues = + : nativeBucketsForPositiveValues, nativeSchema == CLASSIC_HISTOGRAM ? NativeHistogramBuckets.EMPTY - : nativeBucketsForNegativeValues; + : nativeBucketsForNegativeValues, + nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroCount, + nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroThreshold, + false); validate(); } + private HistogramDataPointSnapshot( + ClassicHistogramBuckets classicBuckets, + int nativeSchema, + double sum, + Labels labels, + Exemplars exemplars, + long createdTimestampMillis, + long scrapeTimestampMillis, + long count, + NativeHistogramBuckets nativeBucketsForPositiveValues, + NativeHistogramBuckets nativeBucketsForNegativeValues, + long nativeZeroCount, + double nativeZeroThreshold, + boolean internal) { + super(count, sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis, internal); + this.classicBuckets = classicBuckets; + this.nativeSchema = nativeSchema; + this.nativeZeroCount = nativeZeroCount; + this.nativeZeroThreshold = nativeZeroThreshold; + this.nativeBucketsForPositiveValues = nativeBucketsForPositiveValues; + this.nativeBucketsForNegativeValues = nativeBucketsForNegativeValues; + } + private static long calculateCount( ClassicHistogramBuckets classicBuckets, int nativeSchema, @@ -378,6 +419,24 @@ private void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new HistogramSnapshot.HistogramDataPointSnapshot( + classicBuckets, + nativeSchema, + getSum(), + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplars(getExemplars(), escapingScheme), + getCreatedTimestampMillis(), + getScrapeTimestampMillis(), + getCount(), + nativeBucketsForPositiveValues, + nativeBucketsForNegativeValues, + nativeZeroCount, + nativeZeroThreshold, + true); + } + public static Builder builder() { return new Builder(); } 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 f464c12ae..425673cc6 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 @@ -16,18 +16,33 @@ public final class InfoSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public InfoSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); if (metadata.hasUnit()) { throw new IllegalArgumentException("An Info metric cannot have a unit."); } } + private InfoSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); + } + @SuppressWarnings("unchecked") @Override public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new InfoSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static class InfoDataPointSnapshot extends DataPointSnapshot { /** @@ -46,19 +61,31 @@ public InfoDataPointSnapshot(Labels labels) { * mirroring metrics with given timestamps from other metric sources. */ public InfoDataPointSnapshot(Labels labels, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); + this(labels, scrapeTimestampMillis, false); + } + + private InfoDataPointSnapshot(Labels labels, long scrapeTimestampMillis, boolean internal) { + super(labels, 0L, scrapeTimestampMillis, internal); } public static Builder builder() { return new Builder(); } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new InfoSnapshot.InfoDataPointSnapshot( + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + getScrapeTimestampMillis(), + true); + } + public static class Builder extends DataPointSnapshot.Builder { private Builder() {} public InfoDataPointSnapshot build() { - return new InfoDataPointSnapshot(labels, scrapeTimestampMillis); + return new InfoDataPointSnapshot(labels, scrapeTimestampMillis, true); } @Override diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 1709c9d89..c29987030 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -121,4 +121,8 @@ private void validate() { } } } + + MetricMetadata escape(EscapingScheme escapingScheme) { + return new MetricMetadata(PrometheusNaming.escapeName(name, escapingScheme), help, unit); + } } 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 3e1e49a2f..efa327884 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 @@ -8,23 +8,28 @@ /** Base class for metric snapshots. */ public abstract class MetricSnapshot { - private final MetricMetadata metadata; protected final List dataPoints; protected MetricSnapshot( - MetricMetadata metadata, Collection dataPoints) { - if (metadata == null) { - throw new NullPointerException("metadata"); - } - if (dataPoints == null) { - throw new NullPointerException("dataPoints"); - } + MetricMetadata metadata, + Collection dataPoints, + boolean internal) { this.metadata = metadata; - List dataCopy = new ArrayList<>(dataPoints); - dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels)); - this.dataPoints = Collections.unmodifiableList(dataCopy); - validateLabels(this.dataPoints, metadata); + if (internal) { + this.dataPoints = (List) dataPoints; + } else { + if (metadata == null) { + throw new NullPointerException("metadata"); + } + if (dataPoints == null) { + throw new NullPointerException("dataPoints"); + } + List dataCopy = new ArrayList<>(dataPoints); + dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels)); + this.dataPoints = Collections.unmodifiableList(dataCopy); + validateLabels(this.dataPoints, metadata); + } } public MetricMetadata getMetadata() { @@ -80,4 +85,7 @@ protected MetricMetadata buildMetadata() { protected abstract T self(); } + + abstract MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java new file mode 100644 index 000000000..86cad7986 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -0,0 +1,132 @@ +package io.prometheus.metrics.model.snapshots; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public class SnapshotEscaper { + + private SnapshotEscaper() {} + + /** Escapes the given metric names and labels with the given escaping scheme. */ + @Nullable + public static MetricSnapshot escapeMetricSnapshot( + @Nullable MetricSnapshot v, EscapingScheme scheme) { + if (v == null) { + return null; + } + + if (scheme == EscapingScheme.NO_ESCAPING || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + // we re-use the prometheus name for underscore escaping as an optimization + return v; + } + + List outDataPoints = new ArrayList<>(); + + for (DataPointSnapshot d : v.getDataPoints()) { + if (snapshotNeedsEscaping(d, scheme)) { + outDataPoints.add(d.escape(scheme)); + } else { + outDataPoints.add(d); + } + } + + return v.escape(scheme, outDataPoints); + } + + static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { + Labels labels = d.getLabels(); + if (labelsNeedsEscaping(labels, scheme)) { + return true; + } + if (d instanceof SummarySnapshot.SummaryDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof HistogramSnapshot.HistogramDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof CounterSnapshot.CounterDataPointSnapshot) { + return exemplarNeedsEscaping( + ((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof UnknownSnapshot.UnknownDataPointSnapshot) { + return exemplarNeedsEscaping( + ((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof GaugeSnapshot.GaugeDataPointSnapshot) { + return exemplarNeedsEscaping( + ((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme); + } + + return false; + } + + private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { + for (Label l : labels) { + if (PrometheusNaming.needsEscaping(l.getName(), scheme)) { + return true; + } + } + return false; + } + + private static boolean exemplarNeedsEscaping(@Nullable Exemplar exemplar, EscapingScheme scheme) { + return exemplar != null && labelsNeedsEscaping(exemplar.getLabels(), scheme); + } + + private static boolean exemplarsNeedsEscaping(Exemplars exemplars, EscapingScheme scheme) { + for (Exemplar exemplar : exemplars) { + if (labelsNeedsEscaping(exemplar.getLabels(), scheme)) { + return true; + } + } + return false; + } + + public static String getSnapshotLabelName(Labels labels, int index, EscapingScheme scheme) { + if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + return labels.getPrometheusName(index); + } else { + return labels.getName(index); + } + } + + public static String getMetadataName(MetricMetadata metadata, EscapingScheme scheme) { + if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + return metadata.getPrometheusName(); + } else { + return metadata.getName(); + } + } + + public static Labels escapeLabels(Labels labels, EscapingScheme scheme) { + Labels.Builder outLabelsBuilder = Labels.builder(); + + for (Label l : labels) { + outLabelsBuilder.label(PrometheusNaming.escapeName(l.getName(), scheme), l.getValue()); + } + + return outLabelsBuilder.build(); + } + + public static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { + List escapedExemplars = new ArrayList<>(exemplars.size()); + for (Exemplar exemplar : exemplars) { + escapedExemplars.add(escapeExemplar(exemplar, scheme)); + } + return Exemplars.of(escapedExemplars); + } + + public static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { + if (exemplar == null) { + return null; + } + return Exemplar.builder() + .labels(escapeLabels(exemplar.getLabels(), scheme)) + .timestampMillis(exemplar.getTimestampMillis()) + .value(exemplar.getValue()) + .build(); + } +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java index e49a9b27d..adc0299ac 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java @@ -19,10 +19,15 @@ public final class StateSetSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public StateSetSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); validate(); } + private StateSetSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); + } + private void validate() { if (getMetadata().hasUnit()) { throw new IllegalArgumentException("An state set metric cannot have a unit."); @@ -41,10 +46,20 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new StateSetSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static class StateSetDataPointSnapshot extends DataPointSnapshot implements Iterable { - private final String[] names; - private final boolean[] values; + final String[] names; + final boolean[] values; /** * To create a new {@link StateSetDataPointSnapshot}, you can either call the constructor @@ -67,19 +82,34 @@ public StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels */ public StateSetDataPointSnapshot( String[] names, boolean[] values, Labels labels, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); - if (names.length == 0) { - throw new IllegalArgumentException("StateSet must have at least one state."); - } - if (names.length != values.length) { - throw new IllegalArgumentException("names[] and values[] must have the same length"); + this(names, values, labels, scrapeTimestampMillis, false); + } + + private StateSetDataPointSnapshot( + String[] names, + boolean[] values, + Labels labels, + long scrapeTimestampMillis, + boolean internal) { + super(labels, 0L, scrapeTimestampMillis, false); + if (internal) { + this.names = names; + this.values = values; + } else { + if (names.length == 0) { + throw new IllegalArgumentException("StateSet must have at least one state."); + } + if (names.length != values.length) { + throw new IllegalArgumentException("names[] and values[] must have the same length"); + } + + String[] namesCopy = Arrays.copyOf(names, names.length); + boolean[] valuesCopy = Arrays.copyOf(values, names.length); + sort(namesCopy, valuesCopy); + this.names = namesCopy; + this.values = valuesCopy; + validate(); } - String[] namesCopy = Arrays.copyOf(names, names.length); - boolean[] valuesCopy = Arrays.copyOf(values, names.length); - sort(namesCopy, valuesCopy); - this.names = namesCopy; - this.values = valuesCopy; - validate(); } public int size() { @@ -105,6 +135,16 @@ private void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new StateSetSnapshot.StateSetDataPointSnapshot( + names, + values, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + getScrapeTimestampMillis(), + true); + } + private List asList() { List result = new ArrayList<>(size()); for (int i = 0; i < names.length; i++) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java index ac83ef7ae..9d16b6595 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java @@ -15,7 +15,12 @@ public final class SummarySnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public SummarySnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); + } + + private SummarySnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); } @SuppressWarnings("unchecked") @@ -24,6 +29,16 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new SummarySnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class SummaryDataPointSnapshot extends DistributionDataPointSnapshot { private final Quantiles quantiles; @@ -67,11 +82,31 @@ public SummaryDataPointSnapshot( Exemplars exemplars, long createdTimestampMillis, long scrapeTimestampMillis) { - super(count, sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis); - this.quantiles = quantiles; + this( + count, + sum, + quantiles, + labels, + exemplars, + createdTimestampMillis, + scrapeTimestampMillis, + false); validate(); } + private SummaryDataPointSnapshot( + long count, + double sum, + Quantiles quantiles, + Labels labels, + Exemplars exemplars, + long createdTimestampMillis, + long scrapeTimestampMillis, + boolean internal) { + super(count, sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis, internal); + this.quantiles = quantiles; + } + public Quantiles getQuantiles() { return quantiles; } @@ -87,6 +122,19 @@ private void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new SummarySnapshot.SummaryDataPointSnapshot( + getCount(), + getSum(), + getQuantiles(), + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplars(getExemplars(), escapingScheme), + getCreatedTimestampMillis(), + getScrapeTimestampMillis(), + true); + } + public static Builder builder() { return new Builder(); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java index 120841bb5..a17f725da 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java @@ -16,7 +16,12 @@ public final class UnknownSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public UnknownSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); + } + + private UnknownSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); } @SuppressWarnings("unchecked") @@ -25,6 +30,16 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new UnknownSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class UnknownDataPointSnapshot extends DataPointSnapshot { private final double value; @@ -49,7 +64,16 @@ public UnknownDataPointSnapshot(double value, Labels labels, Exemplar exemplar) */ public UnknownDataPointSnapshot( double value, Labels labels, Exemplar exemplar, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); + this(value, labels, exemplar, scrapeTimestampMillis, false); + } + + private UnknownDataPointSnapshot( + double value, + Labels labels, + Exemplar exemplar, + long scrapeTimestampMillis, + boolean internal) { + super(labels, 0L, scrapeTimestampMillis, internal); this.value = value; this.exemplar = exemplar; } @@ -67,6 +91,16 @@ public static Builder builder() { return new Builder(); } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new UnknownDataPointSnapshot( + value, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), + getScrapeTimestampMillis(), + true); + } + public static class Builder extends DataPointSnapshot.Builder { private Exemplar exemplar = null; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java similarity index 87% rename from prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/SnapshotEscaperTest.java rename to prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index 233d2c693..2353c612e 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -1,17 +1,9 @@ -package io.prometheus.metrics.expositionformats; +package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.escapeMetricSnapshot; -import static io.prometheus.metrics.expositionformats.SnapshotEscaper.getSnapshotLabelName; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import static org.assertj.core.api.Assertions.assertThat; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.Label; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import java.util.Objects; import org.junit.jupiter.api.Test; From 858462dba8b2ef69b9198d31de1d8abe14e80726 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 11:55:22 +0200 Subject: [PATCH 075/106] don't validate in escape Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/SnapshotEscaperTest.java | 20 ++++++++++++++++--- .../model/snapshots/SummarySnapshotTest.java | 7 +++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index 2353c612e..5cd8640d7 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -5,7 +5,11 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Objects; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class SnapshotEscaperTest { @@ -125,15 +129,25 @@ private MetricSnapshot createTestSnapshot( throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); } - @Test - void escapeIsNoop() { - MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); + @ParameterizedTest + @MethodSource("emptySnapshots") + void escapeIsNoop(MetricSnapshot original) { assertThat(original) .isSameAs(escapeMetricSnapshot(original, EscapingScheme.NO_ESCAPING)) .isSameAs(escapeMetricSnapshot(original, EscapingScheme.UNDERSCORE_ESCAPING)); assertThat(escapeMetricSnapshot(null, EscapingScheme.NO_ESCAPING)).isNull(); } + public static Stream emptySnapshots() { + return Stream.of( + Arguments.of(CounterSnapshot.builder().name("empty").build()), + Arguments.of(GaugeSnapshot.builder().name("empty").build()), + Arguments.of(SummarySnapshot.builder().name("empty").build()), + Arguments.of(HistogramSnapshot.builder().name("empty").build()), + Arguments.of(StateSetSnapshot.builder().name("empty").build()), + Arguments.of(UnknownSnapshot.builder().name("empty").build())); + } + @Test void metadataName() { MetricMetadata metadata = new MetricMetadata("test."); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java index c9a12f393..c076c7477 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java @@ -110,4 +110,11 @@ public void testEmptyData() { assertThat(data.hasScrapeTimestamp()).isFalse(); assertThat(data.getExemplars().size()).isZero(); } + + @Test + void escape() { + SummarySnapshot.SummaryDataPointSnapshot data = + SummarySnapshot.SummaryDataPointSnapshot.builder().build(); + assertThat(data.escape(EscapingScheme.UNDERSCORE_ESCAPING)).isEqualTo(data); + } } From f71905ee1317859022ff015ad72175f9d3f450ed Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 12:02:46 +0200 Subject: [PATCH 076/106] fix Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/SummarySnapshotTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java index c076c7477..49b40adbf 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java @@ -114,7 +114,9 @@ public void testEmptyData() { @Test void escape() { SummarySnapshot.SummaryDataPointSnapshot data = - SummarySnapshot.SummaryDataPointSnapshot.builder().build(); - assertThat(data.escape(EscapingScheme.UNDERSCORE_ESCAPING)).isEqualTo(data); + SummarySnapshot.SummaryDataPointSnapshot.builder().sum(12.0).build(); + assertThat(data.escape(EscapingScheme.UNDERSCORE_ESCAPING)) + .usingRecursiveComparison() + .isEqualTo(data); } } From f18e6c40f633597e042794bdfa7d38fea775ccfe Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 12:41:51 +0200 Subject: [PATCH 077/106] fix Signed-off-by: Gregor Zeitlinger --- .../model/snapshots/SnapshotEscaperTest.java | 66 +++++++++++++++++-- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index 5cd8640d7..4e32a5caa 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -131,21 +131,73 @@ private MetricSnapshot createTestSnapshot( @ParameterizedTest @MethodSource("emptySnapshots") - void escapeIsNoop(MetricSnapshot original) { + void escape(MetricSnapshot original) { assertThat(original) .isSameAs(escapeMetricSnapshot(original, EscapingScheme.NO_ESCAPING)) .isSameAs(escapeMetricSnapshot(original, EscapingScheme.UNDERSCORE_ESCAPING)); + assertThat(escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING)) + .usingRecursiveComparison() + .isEqualTo(original); + } + + @Test + void escapeNull() { assertThat(escapeMetricSnapshot(null, EscapingScheme.NO_ESCAPING)).isNull(); } public static Stream emptySnapshots() { return Stream.of( - Arguments.of(CounterSnapshot.builder().name("empty").build()), - Arguments.of(GaugeSnapshot.builder().name("empty").build()), - Arguments.of(SummarySnapshot.builder().name("empty").build()), - Arguments.of(HistogramSnapshot.builder().name("empty").build()), - Arguments.of(StateSetSnapshot.builder().name("empty").build()), - Arguments.of(UnknownSnapshot.builder().name("empty").build())); + Arguments.of( + CounterSnapshot.builder() + .name("empty") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(0).build()) + .build()), + Arguments.of( + GaugeSnapshot.builder() + .name("empty") + .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(0).build()) + .build()), + Arguments.of( + SummarySnapshot.builder() + .name("empty") + .dataPoint( + SummarySnapshot.SummaryDataPointSnapshot.builder().count(0).sum(0.0).build()) + .build()), + Arguments.of( + HistogramSnapshot.builder() + .name("empty") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .count(0) + .sum(0.0) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(0.0, 0) + .bucket(1.0, 0) + .bucket(2.0, 0) + .bucket(Double.POSITIVE_INFINITY, 0) + .build()) + .exemplars( + Exemplars.builder() + .exemplar( + Exemplar.builder() + .labels(Labels.of("exemplar_label", "exemplar_value")) + .value(0.0) + .build()) + .build()) + .build()) + .build()), + Arguments.of( + StateSetSnapshot.builder() + .name("empty") + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder().state("foo", true).build()) + .build()), + Arguments.of( + UnknownSnapshot.builder() + .name("empty") + .dataPoint(UnknownSnapshot.UnknownDataPointSnapshot.builder().value(1.0).build()) + .build())); } @Test From 789e57b2f7b0ff97ffffbf549fb0277e3c3f4c6a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 13:49:05 +0200 Subject: [PATCH 078/106] add docs Signed-off-by: Gregor Zeitlinger --- docs/content/exporters/filter.md | 2 +- docs/content/exporters/httpserver.md | 2 +- docs/content/exporters/pushgateway.md | 2 +- docs/content/exporters/servlet.md | 2 +- docs/content/exporters/spring.md | 2 +- docs/content/exporters/unicode.md | 22 ++++++++++++++++++++++ docs/content/otel/names.md | 16 +++++----------- 7 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 docs/content/exporters/unicode.md diff --git a/docs/content/exporters/filter.md b/docs/content/exporters/filter.md index 01ac48e69..ae7ad9564 100644 --- a/docs/content/exporters/filter.md +++ b/docs/content/exporters/filter.md @@ -1,6 +1,6 @@ --- title: Filter -weight: 2 +weight: 3 --- All exporters support a `name[]` URL parameter for querying only specific metric names. Examples: diff --git a/docs/content/exporters/httpserver.md b/docs/content/exporters/httpserver.md index 4d0aab5f2..801ad60eb 100644 --- a/docs/content/exporters/httpserver.md +++ b/docs/content/exporters/httpserver.md @@ -1,6 +1,6 @@ --- title: HTTPServer -weight: 3 +weight: 4 --- The `HTTPServer` is a standalone server for exposing a metric endpoint. A minimal example diff --git a/docs/content/exporters/pushgateway.md b/docs/content/exporters/pushgateway.md index b4c531022..497aa9b57 100644 --- a/docs/content/exporters/pushgateway.md +++ b/docs/content/exporters/pushgateway.md @@ -1,6 +1,6 @@ --- title: Pushgateway -weight: 5 +weight: 6 --- The [Prometheus Pushgateway](https://github.com/prometheus/pushgateway) exists to allow ephemeral diff --git a/docs/content/exporters/servlet.md b/docs/content/exporters/servlet.md index 93b870b1d..2b0873b70 100644 --- a/docs/content/exporters/servlet.md +++ b/docs/content/exporters/servlet.md @@ -1,6 +1,6 @@ --- title: Servlet -weight: 4 +weight: 5 --- The diff --git a/docs/content/exporters/spring.md b/docs/content/exporters/spring.md index fc0d946dd..45df21431 100644 --- a/docs/content/exporters/spring.md +++ b/docs/content/exporters/spring.md @@ -1,6 +1,6 @@ --- title: Spring -weight: 5 +weight: 7 --- ## Alternative: Use Spring's Built-in Metrics Library diff --git a/docs/content/exporters/unicode.md b/docs/content/exporters/unicode.md new file mode 100644 index 000000000..e7411a36f --- /dev/null +++ b/docs/content/exporters/unicode.md @@ -0,0 +1,22 @@ +--- +title: Unicode +weight: 2 +--- + +The Prometheus Java client library allows all Unicode characters, that can be encoded as UTF-8. + +At scrape time, some characters are replaced based on the `encoding` header according +to +the [Escaping scheme](https://github.com/prometheus/docs/blob/main/docs/instrumenting/escaping_schemes.md). + +For example, if you use the `underscores` escaping scheme, dots in metric and label names are +replaced with underscores, so that the metric name `http.server.duration` becomes +`http_server_duration`. + +Prometheus servers that do not support Unicode at all will not pass the `encoding` header, and the +Prometheus Java client library will replace dots, as well as any character that is not in the legacy +character set (a-zA-Z0-9_:), with underscores by default. + +When `escaping=allow-utf-8` is passed, add valid UTF-8 characters to the metric and label names +without replacing them. This allows you to use dots in metric and label names, as well as +other UTF-8 characters, without any replacements. diff --git a/docs/content/otel/names.md b/docs/content/otel/names.md index 2945d70e9..a5425e07f 100644 --- a/docs/content/otel/names.md +++ b/docs/content/otel/names.md @@ -17,7 +17,7 @@ the Prometheus server as if you had exposed Prometheus metrics directly. The main steps when converting OpenTelemetry metric names to Prometheus metric names are: -- Replace dots with underscores. +- Escape illegal characters as described in [Unicode support] - If the metric has a unit, append the unit to the metric name, like `_seconds`. - If the metric type has a suffix, append it, like `_total` for counters. @@ -29,14 +29,8 @@ OpenTelemetry's [Semantic Conventions for HTTP Metrics](https://opentelemetry.io say that if you instrument an HTTP server with OpenTelemetry, you must have a histogram named `http.server.duration`. -Most names defined in semantic conventions use dots. In the Prometheus server, the dot is an illegal -character (this might change in future versions of the Prometheus server). +Most names defined in semantic conventions use dots. +Dots in metric and label names are now supported in the Prometheus Java client library as +described in [Unicode support]. -The Prometheus Java client library allows dots, so that you can use metric names and label names as -defined in OpenTelemetry's semantic conventions. -The dots will automatically be replaced with underscores if you expose metrics in Prometheus format, -but you will see the original names with dots if you push your metrics in OpenTelemetry format. - -That way, you can use OTel-compliant metric and label names today when instrumenting your -application with the Prometheus Java client, and you are prepared in case your monitoring backend -adds features in the future that require OTel-compliant instrumentation. +[Unicode support]: {{< relref "../exporters/unicode.md" >}} From 97cb296fedbb7482315c0d08df69d1fa9bb5501c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 14:05:32 +0200 Subject: [PATCH 079/106] add docs Signed-off-by: Gregor Zeitlinger --- .github/super-linter.env | 2 -- docs/content/exporters/unicode.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/super-linter.env b/.github/super-linter.env index 826989701..835708fe7 100644 --- a/.github/super-linter.env +++ b/.github/super-linter.env @@ -14,8 +14,6 @@ VALIDATE_GO_MODULES=false VALIDATE_HTML=false # done by checkstyle VALIDATE_JAVA=false -# contradicting with prettier -VALIDATE_JAVASCRIPT_STANDARD=false # we have many duplicate code in our codebase for demo purposes VALIDATE_JSCPD=false VALIDATE_PYTHON_PYLINT=false diff --git a/docs/content/exporters/unicode.md b/docs/content/exporters/unicode.md index e7411a36f..8d6acf4f7 100644 --- a/docs/content/exporters/unicode.md +++ b/docs/content/exporters/unicode.md @@ -15,7 +15,7 @@ replaced with underscores, so that the metric name `http.server.duration` become Prometheus servers that do not support Unicode at all will not pass the `encoding` header, and the Prometheus Java client library will replace dots, as well as any character that is not in the legacy -character set (a-zA-Z0-9_:), with underscores by default. +character set (`a-zA-Z0-9_:`), with underscores by default. When `escaping=allow-utf-8` is passed, add valid UTF-8 characters to the metric and label names without replacing them. This allows you to use dots in metric and label names, as well as From ca301618904c35266768d5837f1f37074d706046 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 16:27:22 +0200 Subject: [PATCH 080/106] move EscapingScheme Signed-off-by: Gregor Zeitlinger --- .../benchmarks/TextFormatUtilBenchmark.java | 2 +- .../metrics/config}/EscapingScheme.java | 17 +++--- .../config/ExporterPushgatewayProperties.java | 53 ++++++++++++------- .../metrics/core/metrics/CounterTest.java | 2 +- .../metrics/core/metrics/HistogramTest.java | 1 + .../metrics/core/metrics/InfoTest.java | 2 +- .../common/PrometheusScrapeHandler.java | 2 +- .../exporter/pushgateway/PushGateway.java | 16 +----- .../exporter/pushgateway/PushGatewayTest.java | 2 +- .../PrometheusProtobufWriterImpl.java | 2 +- .../ProtobufExpositionFormatsTest.java | 2 +- .../ExpositionFormatWriter.java | 7 ++- .../OpenMetricsTextFormatWriter.java | 2 +- .../PrometheusProtobufWriter.java | 2 +- .../PrometheusTextFormatWriter.java | 2 +- .../expositionformats/TextFormatUtil.java | 2 +- .../ExpositionFormatWriterTest.java | 2 +- .../ExpositionFormatsTest.java | 2 +- .../PrometheusProtobufWriterTest.java | 2 +- .../caffeine/CacheMetricsCollectorTest.java | 1 + .../dropwizard/DropwizardExportsTest.java | 2 +- .../dropwizard5/DropwizardExportsTest.java | 2 +- .../labels/CustomLabelMapperTest.java | 2 +- .../guava/CacheMetricsCollectorTest.java | 1 + .../metrics/instrumentation/jvm/TestUtil.java | 2 +- .../model/snapshots/CounterSnapshot.java | 1 + .../model/snapshots/DataPointSnapshot.java | 2 + .../model/snapshots/GaugeSnapshot.java | 1 + .../model/snapshots/HistogramSnapshot.java | 1 + .../metrics/model/snapshots/InfoSnapshot.java | 1 + .../model/snapshots/MetricMetadata.java | 2 + .../model/snapshots/MetricSnapshot.java | 1 + .../model/snapshots/PrometheusNaming.java | 12 +---- .../model/snapshots/SnapshotEscaper.java | 1 + .../model/snapshots/StateSetSnapshot.java | 1 + .../model/snapshots/SummarySnapshot.java | 1 + .../model/snapshots/UnknownSnapshot.java | 1 + .../model/snapshots/EscapingSchemeTest.java | 3 +- .../model/snapshots/PrometheusNamingTest.java | 1 + .../model/snapshots/SnapshotEscaperTest.java | 1 + .../model/snapshots/SummarySnapshotTest.java | 1 + .../bridge/SimpleclientCollectorTest.java | 2 +- 42 files changed, 89 insertions(+), 76 deletions(-) rename {prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots => prometheus-metrics-config/src/main/java/io/prometheus/metrics/config}/EscapingScheme.java (86%) diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java index 04352a829..09314a328 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java @@ -1,9 +1,9 @@ package io.prometheus.metrics.benchmarks; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; import io.prometheus.metrics.model.snapshots.Labels; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java similarity index 86% rename from prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java rename to prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java index 783029a9b..83cf4f575 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/EscapingScheme.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java @@ -1,7 +1,4 @@ -package io.prometheus.metrics.model.snapshots; - -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.DEFAULT_ESCAPING_SCHEME; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.ESCAPING_KEY; +package io.prometheus.metrics.config; public enum EscapingScheme { /** NO_ESCAPING indicates that a name will not be escaped. */ @@ -21,8 +18,12 @@ public enum EscapingScheme { * the Unicode value, surrounded by underscores. Single underscores are replaced with double * underscores. */ - VALUE_ENCODING_ESCAPING("values"), - ; + VALUE_ENCODING_ESCAPING("values"); + + private static final String ESCAPING_KEY = "escaping"; + + /** Default escaping scheme for names when not specified. */ + public static final EscapingScheme DEFAULT = UNDERSCORE_ESCAPING; public final String getValue() { return value; @@ -53,12 +54,12 @@ public static EscapingScheme fromAcceptHeader(String acceptHeader) { return EscapingScheme.forString(value); } catch (IllegalArgumentException e) { // If the escaping parameter is unknown, ignore it. - return DEFAULT_ESCAPING_SCHEME; + return DEFAULT; } } } } - return DEFAULT_ESCAPING_SCHEME; + return DEFAULT; } static EscapingScheme forString(String value) { diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 03325e722..56ab83e53 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.config; import java.util.Map; +import javax.annotation.Nullable; public class ExporterPushgatewayProperties { @@ -9,13 +10,16 @@ public class ExporterPushgatewayProperties { private static final String SCHEME = "scheme"; private static final String ESCAPING_SCHEME = "escapingScheme"; private static final String PREFIX = "io.prometheus.exporter.pushgateway"; - private final String scheme; - private final String address; - private final String job; - private final String escapingScheme; + @Nullable private final String scheme; + @Nullable private final String address; + @Nullable private final String job; + @Nullable private final EscapingScheme escapingScheme; private ExporterPushgatewayProperties( - String address, String job, String scheme, String escapingScheme) { + @Nullable String address, + @Nullable String job, + @Nullable String scheme, + @Nullable EscapingScheme escapingScheme) { this.address = address; this.job = job; this.scheme = scheme; @@ -23,6 +27,7 @@ private ExporterPushgatewayProperties( } /** Address of the Pushgateway in the form {@code host:port}. Default is {@code localhost:9091} */ + @Nullable public String getAddress() { return address; } @@ -31,6 +36,7 @@ public String getAddress() { * {@code job} label for metrics being pushed. Default is the name of the JAR file that is * running. */ + @Nullable public String getJob() { return job; } @@ -39,15 +45,14 @@ public String getJob() { * Scheme to be used when pushing metrics to the pushgateway. Must be "http" or "https". Default * is "http". */ + @Nullable public String getScheme() { return scheme; } - /** - * Escaping scheme to be used when pushing metric data to the pushgateway. Valid values: - * "no-escaping", "values", "underscores", "dots". Default is "no-escaping". - */ - public String getEscapingScheme() { + /** Escaping scheme to be used when pushing metric data to the pushgateway. */ + @Nullable + public EscapingScheme getEscapingScheme() { return escapingScheme; } @@ -71,19 +76,29 @@ static ExporterPushgatewayProperties load(Map properties) } } - if (escapingScheme != null) { - if (!escapingScheme.equals("no-escaping") - && !escapingScheme.equals("values") - && !escapingScheme.equals("underscores") - && !escapingScheme.equals("dots")) { + return new ExporterPushgatewayProperties( + address, job, scheme, parseEscapingScheme(escapingScheme)); + } + + private static @Nullable EscapingScheme parseEscapingScheme(@Nullable String scheme) { + if (scheme == null) { + return null; + } + switch (scheme) { + case "no-escaping": + return EscapingScheme.NO_ESCAPING; + case "values": + return EscapingScheme.VALUE_ENCODING_ESCAPING; + case "underscores": + return EscapingScheme.UNDERSCORE_ESCAPING; + case "dots": + return EscapingScheme.DOTS_ESCAPING; + default: throw new PrometheusPropertiesException( String.format( "%s.%s: Illegal value. Expecting 'no-escaping', 'values', 'underscores', " + "or 'dots'. Found: %s", - PREFIX, ESCAPING_SCHEME, escapingScheme)); - } + PREFIX, ESCAPING_SCHEME, scheme)); } - - return new ExporterPushgatewayProperties(address, job, scheme, escapingScheme); } } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java index 129516022..7ce62ae17 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.MetricsProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; @@ -12,7 +13,6 @@ import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Label; import io.prometheus.metrics.model.snapshots.Labels; diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index 9eab941bf..4031f7cbc 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.MetricsProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.DistributionDataPoint; diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index b9f4e40c1..fc4ef371a 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -3,11 +3,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.Unit; diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index 7510f6978..08e455409 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -1,12 +1,12 @@ package io.prometheus.metrics.exporter.common; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.ExporterFilterProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; import io.prometheus.metrics.expositionformats.ExpositionFormats; import io.prometheus.metrics.model.registry.MetricNameFilter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 596657340..cecdb2373 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -3,6 +3,7 @@ import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesException; @@ -12,7 +13,6 @@ import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -439,19 +439,7 @@ private String getJob(ExporterPushgatewayProperties properties) { private EscapingScheme getEscapingScheme(ExporterPushgatewayProperties properties) { if (properties != null && properties.getEscapingScheme() != null) { - String scheme = properties.getEscapingScheme(); - switch (scheme) { - case "no-escaping": - return EscapingScheme.NO_ESCAPING; - case "values": - return EscapingScheme.VALUE_ENCODING_ESCAPING; - case "underscores": - return EscapingScheme.UNDERSCORE_ESCAPING; - case "dots": - return EscapingScheme.DOTS_ESCAPING; - default: - return EscapingScheme.NO_ESCAPING; - } + return properties.getEscapingScheme(); } return EscapingScheme.NO_ESCAPING; } diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index e6a154b45..9eb958ff5 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -5,9 +5,9 @@ import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java index 1d2d4c57a..8ccf41872 100644 --- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java +++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java @@ -4,13 +4,13 @@ import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import com.google.protobuf.TextFormat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java index 8996700a3..aeb919118 100644 --- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java @@ -2,10 +2,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshot; class ProtobufExpositionFormatsTest extends ExpositionFormatsTest { diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java index 287b4108a..6344930d4 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java @@ -1,8 +1,7 @@ package io.prometheus.metrics.expositionformats; -import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -16,7 +15,7 @@ void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme esc /** Writes the given metric snapshots to the output stream using the default escaping scheme. */ default void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException { - write(out, metricSnapshots, PrometheusNaming.DEFAULT_ESCAPING_SCHEME); + write(out, metricSnapshots, EscapingScheme.DEFAULT); } /** Converts the metric snapshots to a debug string using the specified escaping scheme. */ @@ -32,7 +31,7 @@ default String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme esc /** Converts the metric snapshots to a debug string using the default escaping scheme. */ default String toDebugString(MetricSnapshots metricSnapshots) { - return toDebugString(metricSnapshots, PrometheusNaming.DEFAULT_ESCAPING_SCHEME); + return toDebugString(metricSnapshots, EscapingScheme.DEFAULT); } String getContentType(); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index 6400d8246..c82fafd9e 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -9,11 +9,11 @@ import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; import io.prometheus.metrics.model.snapshots.DistributionDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java index 77f439d23..6701abbce 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.expositionformats; -import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.IOException; import java.io.OutputStream; 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 285e4b1fb..fd2d4b8ee 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 @@ -10,10 +10,10 @@ import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName; import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; import io.prometheus.metrics.model.snapshots.InfoSnapshot; diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java index c60f08b42..33a597896 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.expositionformats; -import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.SnapshotEscaper; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java index 9f30868ea..53af0e8f2 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index 744fbcf38..b78dcc67b 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -3,10 +3,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java index 4b8411eaa..da085bdbd 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import io.prometheus.metrics.model.snapshots.EscapingScheme; +import io.prometheus.metrics.config.EscapingScheme; import org.junit.jupiter.api.Test; class PrometheusProtobufWriterTest { 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 f6b6ae482..015a8730d 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 @@ -9,6 +9,7 @@ import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.*; diff --git a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java index 0be6dbccf..9d21c7381 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java @@ -5,10 +5,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.codahale.metrics.*; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java index c6170fd14..c3a5fa4d5 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java @@ -5,9 +5,9 @@ import static org.assertj.core.data.Offset.offset; import io.dropwizard.metrics5.*; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.SummarySnapshot; diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java index f10c61d35..183b50b24 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java @@ -5,9 +5,9 @@ import io.dropwizard.metrics5.MetricFilter; import io.dropwizard.metrics5.MetricRegistry; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.instrumentation.dropwizard5.DropwizardExports; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; 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 4e74a5ea8..34637f4f5 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 @@ -9,6 +9,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.*; diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java index 09923a8be..224f99c51 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java @@ -1,7 +1,7 @@ package io.prometheus.metrics.instrumentation.jvm; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; 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 0d09d7b66..12a9f549d 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 @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java index 35f917d94..d960912a0 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; + @SuppressWarnings("this-escape") public abstract class DataPointSnapshot { private final Labels labels; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java index 24f64a6f3..202508d2a 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java index a9a612c68..acc78d3d3 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; 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 425673cc6..324085c0a 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 @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index c29987030..e2ee44791 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; + /** Immutable container for metric metadata: name, help, unit. */ public final class MetricMetadata { 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 efa327884..048f2259f 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 @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 799bf7a62..5e0770a7d 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -4,6 +4,7 @@ import static java.lang.Character.MAX_LOW_SURROGATE; import static java.lang.Character.MIN_HIGH_SURROGATE; +import io.prometheus.metrics.config.EscapingScheme; import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; @@ -16,17 +17,6 @@ */ public class PrometheusNaming { - /** Default escaping scheme for names when not specified. */ - public static final EscapingScheme DEFAULT_ESCAPING_SCHEME = EscapingScheme.UNDERSCORE_ESCAPING; - - /** - * ESCAPING_KEY is the key in an Accept header that defines how metric and label names that do not - * conform to the legacy character requirements should be escaped when being scraped by a legacy - * Prometheus system. If a system does not explicitly pass an escaping parameter in the Accept - * header, the default escaping scheme will be used. - */ - public static final String ESCAPING_KEY = "escaping"; - private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); /** Legal characters for label names. */ diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 86cad7986..676586dbf 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java index adc0299ac..7b0ac938b 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java index 9d16b6595..1b8dbc2e9 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java index a17f725da..7b41870cb 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java index 7bbd6ed1c..6f514aab0 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import io.prometheus.metrics.config.EscapingScheme; import org.junit.jupiter.api.Test; class EscapingSchemeTest { @@ -30,6 +31,6 @@ void fromAcceptHeader() { assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=values")) .isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING); assertThat(EscapingScheme.fromAcceptHeader("application/json")) - .isEqualTo(PrometheusNaming.DEFAULT_ESCAPING_SCHEME); + .isEqualTo(EscapingScheme.DEFAULT); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index d0c646f6b..40c1f1bde 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -11,6 +11,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import io.prometheus.metrics.config.EscapingScheme; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index 4e32a5caa..acd61dba7 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -4,6 +4,7 @@ import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import java.util.Objects; import java.util.stream.Stream; import org.junit.jupiter.api.Test; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java index 49b40adbf..df423ba97 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; diff --git a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java index 76cb3f67c..8c635f7c2 100644 --- a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java +++ b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java @@ -10,9 +10,9 @@ import io.prometheus.client.Info; import io.prometheus.client.Summary; import io.prometheus.client.exporter.common.TextFormat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.EscapingScheme; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; From c02ac18c2ed8611b5764d09a8efd7faf61e63b6e Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 13 Aug 2025 16:41:54 +0200 Subject: [PATCH 081/106] move EscapingScheme Signed-off-by: Gregor Zeitlinger --- .../java/io/prometheus/metrics/config}/EscapingSchemeTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename {prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots => prometheus-metrics-config/src/test/java/io/prometheus/metrics/config}/EscapingSchemeTest.java (93%) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java similarity index 93% rename from prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java rename to prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java index 6f514aab0..85a23db52 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/EscapingSchemeTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java @@ -1,9 +1,8 @@ -package io.prometheus.metrics.model.snapshots; +package io.prometheus.metrics.config; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import io.prometheus.metrics.config.EscapingScheme; import org.junit.jupiter.api.Test; class EscapingSchemeTest { From 07c54b8758dcbe0b1e9e6ed8192489a40bedae9b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 14 Aug 2025 10:54:56 +0200 Subject: [PATCH 082/106] nullaway Signed-off-by: Gregor Zeitlinger --- pom.xml | 8 +- .../metrics/config/ExemplarsProperties.java | 22 ++- .../config/ExporterFilterProperties.java | 29 +-- .../config/ExporterHttpServerProperties.java | 8 +- .../ExporterOpenTelemetryProperties.java | 57 +++--- .../metrics/config/ExporterProperties.java | 17 +- .../metrics/config/MetricsProperties.java | 165 ++++++++++-------- .../metrics/config/PrometheusProperties.java | 67 ++++--- .../io/prometheus/metrics/config/Util.java | 13 +- .../core/exemplars/ExemplarSampler.java | 3 + .../core/metrics/MetricWithFixedMetadata.java | 3 + .../PrometheusProtobufWriterImpl.java | 3 +- .../OpenMetricsTextFormatWriter.java | 3 +- .../PrometheusProtobufWriter.java | 9 +- .../metrics/model/registry/Collector.java | 4 + .../model/registry/MultiCollector.java | 3 +- .../model/registry/PrometheusRegistry.java | 5 +- .../model/snapshots/CounterSnapshot.java | 12 +- .../metrics/model/snapshots/Exemplar.java | 8 +- .../metrics/model/snapshots/Exemplars.java | 4 + .../model/snapshots/GaugeSnapshot.java | 13 +- .../metrics/model/snapshots/Labels.java | 2 + .../model/snapshots/MetricMetadata.java | 10 +- .../model/snapshots/MetricSnapshot.java | 10 +- .../model/snapshots/PrometheusNaming.java | 3 + .../model/snapshots/UnknownSnapshot.java | 24 ++- .../registry/PrometheusRegistryTest.java | 13 +- .../model/snapshots/ExemplarsTest.java | 4 +- .../metrics/tracer/common/SpanContext.java | 4 + .../initializer/SpanContextSupplier.java | 2 + .../agent/OpenTelemetryAgentSpanContext.java | 3 + .../tracer/otel/OpenTelemetrySpanContext.java | 3 + 32 files changed, 326 insertions(+), 208 deletions(-) diff --git a/pom.xml b/pom.xml index d6a4c58c6..0583149fc 100644 --- a/pom.xml +++ b/pom.xml @@ -267,7 +267,8 @@ -Xep:MissingSummary:OFF -Xep:LongDoubleConversion:OFF -Xep:StringSplitter:OFF - -XepExcludedPaths:.*/generated/.* + -XepExcludedPaths:(.*/generated/.*|.*/src/test/java/.*) + -XepOpt:NullAway:AnnotatedPackages=io.prometheus.metrics @@ -276,6 +277,11 @@ error_prone_core 2.41.0 + + com.uber.nullaway + nullaway + 0.12.8 + | Name | Javadoc | Note | -| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| | io.prometheus.metrics.exemplarsEnabled | [Counter.Builder.withExemplars()]() | (1) (2) | | io.prometheus.metrics.histogramNativeOnly | [Histogram.Builder.nativeOnly()]() | (2) | | io.prometheus.metrics.histogramClassicOnly | [Histogram.Builder.classicOnly()]() | (2) | @@ -71,13 +71,13 @@ metric only by specifying the metric name. Example: Let's say you have a histogram named `latency_seconds`. ```properties -io.prometheus.metrics.histogramClassicUpperBounds = 0.2, 0.4, 0.8, 1.0 +io.prometheus.metrics.histogramClassicUpperBounds=0.2, 0.4, 0.8, 1.0 ``` The line above sets histogram buckets for all histograms. However: ```properties -io.prometheus.metrics.latency_seconds.histogramClassicUpperBounds = 0.2, 0.4, 0.8, 1.0 +io.prometheus.metrics.latency_seconds.histogramClassicUpperBounds=0.2, 0.4, 0.8, 1.0 ``` The line above sets histogram buckets only for the histogram named `latency_seconds`. @@ -89,7 +89,7 @@ This works for all Metrics properties. | Name | Javadoc | Note | -| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|------| | io.prometheus.exemplars.minRetentionPeriodSeconds | [ExemplarsProperties.getMinRetentionPeriodSeconds()]() | | | io.prometheus.exemplars.maxRetentionPeriodSeconds | [ExemplarsProperties.getMaxRetentionPeriodSeconds()]() | | | io.prometheus.exemplars.sampleIntervalMilliseconds | [ExemplarsProperties.getSampleIntervalMilliseconds()]() | | @@ -101,7 +101,7 @@ This works for all Metrics properties. | Name | Javadoc | Note | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|------| | io.prometheus.exporter.includeCreatedTimestamps | [ExporterProperties.getIncludeCreatedTimestamps()]() | (1) | | io.prometheus.exporter.exemplarsOnAllMetricTypes | [ExporterProperties.getExemplarsOnAllMetricTypes()]() | (1) | @@ -114,7 +114,7 @@ This works for all Metrics properties. | Name | Javadoc | Note | -| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| | io.prometheus.exporter.filter.metricNameMustBeEqualTo | [ExporterFilterProperties.getAllowedMetricNames()]() | (1) | | io.prometheus.exporter.filter.metricNameMustNotBeEqualTo | [ExporterFilterProperties.getExcludedMetricNames()]() | (2) | | io.prometheus.exporter.filter.metricNameMustStartWith | [ExporterFilterProperties.getAllowedMetricNamePrefixes()]() | (3) | @@ -133,7 +133,7 @@ Only metrics starting with these prefixes will be exposed.
| Name | Javadoc | Note | -| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---- | +|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|------| | io.prometheus.exporter.httpServer.port | [HTTPServer.Builder.port()]() | | @@ -143,7 +143,7 @@ Only metrics starting with these prefixes will be exposed.
| Name | Javadoc | Note | -| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| | io.prometheus.exporter.opentelemetry.protocol | [OpenTelemetryExporter.Builder.protocol()]() | (1) | | io.prometheus.exporter.opentelemetry.endpoint | [OpenTelemetryExporter.Builder.endpoint()]() | | | io.prometheus.exporter.opentelemetry.headers | [OpenTelemetryExporter.Builder.headers()]() | (2) | @@ -170,10 +170,14 @@ See Javadoc for details. -| Name | Javadoc | Note | -| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exporter.pushgateway.address | [PushGateway.Builder.address()]() | | -| io.prometheus.exporter.pushgateway.scheme | [PushGateway.Builder.scheme()]() | | -| io.prometheus.exporter.pushgateway.job | [PushGateway.Builder.job()]() | | +| Name | Javadoc | Note | +|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| io.prometheus.exporter.pushgateway.address | [PushGateway.Builder.address()]() | | +| io.prometheus.exporter.pushgateway.scheme | [PushGateway.Builder.scheme()]() | | +| io.prometheus.exporter.pushgateway.job | [PushGateway.Builder.job()]() | | +| io.prometheus.exporter.pushgateway.escapingScheme | [PushGateway.Builder.escapingScheme()]() | (1) | + +(1) Escaping scheme can be `allow-utf-8`, `underscores`, `dots`, or `values` as described in +[escaping schemes](https://github.com/prometheus/docs/blob/main/docs/instrumenting/escaping_schemes.md#escaping-schemes) diff --git a/docs/content/exporters/unicode.md b/docs/content/exporters/unicode.md index 8d6acf4f7..cbda09167 100644 --- a/docs/content/exporters/unicode.md +++ b/docs/content/exporters/unicode.md @@ -20,3 +20,10 @@ character set (`a-zA-Z0-9_:`), with underscores by default. When `escaping=allow-utf-8` is passed, add valid UTF-8 characters to the metric and label names without replacing them. This allows you to use dots in metric and label names, as well as other UTF-8 characters, without any replacements. + +## PushGateway + +When using the [Pushgateway](/exporters/pushgateway/), Unicode support has to be enabled +explicitly by setting `io.prometheus.exporter.pushgateway.escapingScheme` to `allow-utf-8` in the +Pushgateway configuration file - see +[Pushgateway configuration]({{< relref "/config/config.md#exporter-pushgateway-properties" >}}) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java index 83cf4f575..d2aceafe6 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.config; +import javax.annotation.Nullable; + public enum EscapingScheme { /** NO_ESCAPING indicates that a name will not be escaped. */ NO_ESCAPING("allow-utf-8"), @@ -40,7 +42,7 @@ public final String getValue() { * contains an escaping=allow-utf-8 term, it will select NO_ESCAPING. If a valid "escaping" term * exists, that will be used. Otherwise, the global default will be returned. */ - public static EscapingScheme fromAcceptHeader(String acceptHeader) { + public static EscapingScheme fromAcceptHeader(@Nullable String acceptHeader) { if (acceptHeader != null) { for (String p : acceptHeader.split(";")) { String[] toks = p.split("="); diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index a257f76d0..c9a58a5f7 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -85,7 +85,7 @@ static ExporterPushgatewayProperties load(Map properties) return null; } switch (scheme) { - case "no-escaping": + case "allow-utf-8": return EscapingScheme.NO_ESCAPING; case "values": return EscapingScheme.VALUE_ENCODING_ESCAPING; @@ -96,7 +96,7 @@ static ExporterPushgatewayProperties load(Map properties) default: throw new PrometheusPropertiesException( String.format( - "%s.%s: Illegal value. Expecting 'no-escaping', 'values', 'underscores', " + "%s.%s: Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', " + "or 'dots'. Found: %s", PREFIX, ESCAPING_SCHEME, scheme)); } @@ -110,6 +110,7 @@ public static class Builder { @Nullable private String address; @Nullable private String job; @Nullable private String scheme; + @Nullable private EscapingScheme escapingScheme; private Builder() {} @@ -128,8 +129,13 @@ public Builder scheme(String scheme) { return this; } + public Builder escapingScheme(EscapingScheme escapingScheme) { + this.escapingScheme = escapingScheme; + return this; + } + public ExporterPushgatewayProperties build() { - return new ExporterPushgatewayProperties(address, job, scheme); + return new ExporterPushgatewayProperties(address, job, scheme, escapingScheme); } } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java index b8e8c8009..945fa83be 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java @@ -40,10 +40,12 @@ void builder() { .address("http://localhost") .job("job") .scheme("http") + .escapingScheme(EscapingScheme.DOTS_ESCAPING) .build(); assertThat(properties.getAddress()).isEqualTo("http://localhost"); assertThat(properties.getJob()).isEqualTo("job"); assertThat(properties.getScheme()).isEqualTo("http"); + assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.DOTS_ESCAPING); } } diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 9789bf103..747d37379 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -439,11 +439,11 @@ private String getJob(@Nullable ExporterPushgatewayProperties properties) { } } - private EscapingScheme getEscapingScheme(ExporterPushgatewayProperties properties) { + private EscapingScheme getEscapingScheme(@Nullable ExporterPushgatewayProperties properties) { if (properties != null && properties.getEscapingScheme() != null) { return properties.getEscapingScheme(); } - return EscapingScheme.NO_ESCAPING; + return EscapingScheme.UNDERSCORE_ESCAPING; } private Format getFormat() { 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 06f901ea3..72a83a879 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 @@ -82,7 +82,7 @@ public CounterDataPointSnapshot( public CounterDataPointSnapshot( double value, Labels labels, - Exemplar exemplar, + @Nullable Exemplar exemplar, long createdTimestampMillis, long scrapeTimestampMillis, boolean internal) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java index 745623518..03474852f 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java @@ -71,7 +71,7 @@ public GaugeDataPointSnapshot( private GaugeDataPointSnapshot( double value, Labels labels, - Exemplar exemplar, + @Nullable Exemplar exemplar, long scrapeTimestampMillis, boolean internal) { super(labels, 0L, scrapeTimestampMillis, internal); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 676586dbf..4280302bf 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -10,13 +10,7 @@ public class SnapshotEscaper { private SnapshotEscaper() {} /** Escapes the given metric names and labels with the given escaping scheme. */ - @Nullable - public static MetricSnapshot escapeMetricSnapshot( - @Nullable MetricSnapshot v, EscapingScheme scheme) { - if (v == null) { - return null; - } - + public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { if (scheme == EscapingScheme.NO_ESCAPING || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { // we re-use the prometheus name for underscore escaping as an optimization return v; @@ -120,6 +114,7 @@ public static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme sche return Exemplars.of(escapedExemplars); } + @Nullable public static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { if (exemplar == null) { return null; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java index ddd6a7548..09574d6cd 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java @@ -72,7 +72,7 @@ public UnknownDataPointSnapshot( private UnknownDataPointSnapshot( double value, Labels labels, - Exemplar exemplar, + @Nullable Exemplar exemplar, long scrapeTimestampMillis, boolean internal) { super(labels, 0L, scrapeTimestampMillis, internal); From ae009cf058c09c98e882caf8d5bf3915a99ea57e Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 14 Aug 2025 16:46:49 +0200 Subject: [PATCH 103/106] rename escaping scheme value for no escaping Signed-off-by: Gregor Zeitlinger --- .../metrics/benchmarks/TextFormatUtilBenchmark.java | 8 ++++---- .../java/io/prometheus/metrics/config/EscapingScheme.java | 4 ++-- .../metrics/config/ExporterPushgatewayProperties.java | 2 +- .../io/prometheus/metrics/config/EscapingSchemeTest.java | 4 ++-- .../io/prometheus/metrics/core/metrics/CounterTest.java | 2 +- .../io/prometheus/metrics/core/metrics/HistogramTest.java | 6 +++--- .../java/io/prometheus/metrics/core/metrics/InfoTest.java | 2 +- .../metrics/exporter/pushgateway/PushGatewayTest.java | 2 +- .../expositionformats/ExpositionFormatWriterTest.java | 4 ++-- .../metrics/expositionformats/ExpositionFormatsTest.java | 2 +- .../expositionformats/PrometheusProtobufWriterTest.java | 4 ++-- .../caffeine/CacheMetricsCollectorTest.java | 2 +- .../instrumentation/guava/CacheMetricsCollectorTest.java | 2 +- .../prometheus/metrics/instrumentation/jvm/TestUtil.java | 2 +- .../metrics/model/snapshots/PrometheusNaming.java | 2 +- .../metrics/model/snapshots/SnapshotEscaper.java | 2 +- .../metrics/model/snapshots/SnapshotEscaperTest.java | 8 ++++---- .../simpleclient/bridge/SimpleclientCollectorTest.java | 4 ++-- 18 files changed, 31 insertions(+), 31 deletions(-) diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java index 09314a328..24fe9cb26 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java @@ -71,14 +71,14 @@ public OutputStream openMetricsWriteToByteArray(WriterState writerState) throws ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); OPEN_METRICS_TEXT_FORMAT_WRITER.write( - byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); + byteArrayOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return byteArrayOutputStream; } @Benchmark public OutputStream openMetricsWriteToNull() throws IOException { OutputStream nullOutputStream = NullOutputStream.INSTANCE; - OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); + OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return nullOutputStream; } @@ -88,14 +88,14 @@ public OutputStream prometheusWriteToByteArray(WriterState writerState) throws I ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); PROMETHEUS_TEXT_FORMAT_WRITER.write( - byteArrayOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); + byteArrayOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return byteArrayOutputStream; } @Benchmark public OutputStream prometheusWriteToNull() throws IOException { OutputStream nullOutputStream = NullOutputStream.INSTANCE; - PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.NO_ESCAPING); + PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return nullOutputStream; } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java index d2aceafe6..1cd037bf3 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java @@ -4,7 +4,7 @@ public enum EscapingScheme { /** NO_ESCAPING indicates that a name will not be escaped. */ - NO_ESCAPING("allow-utf-8"), + ALLOW_UTF8("allow-utf-8"), /** UNDERSCORE_ESCAPING replaces all legacy-invalid characters with underscores. */ UNDERSCORE_ESCAPING("underscores"), @@ -67,7 +67,7 @@ public static EscapingScheme fromAcceptHeader(@Nullable String acceptHeader) { static EscapingScheme forString(String value) { switch (value) { case "allow-utf-8": - return NO_ESCAPING; + return ALLOW_UTF8; case "underscores": return UNDERSCORE_ESCAPING; case "dots": diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index c9a58a5f7..9d7b380d1 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -86,7 +86,7 @@ static ExporterPushgatewayProperties load(Map properties) } switch (scheme) { case "allow-utf-8": - return EscapingScheme.NO_ESCAPING; + return EscapingScheme.ALLOW_UTF8; case "values": return EscapingScheme.VALUE_ENCODING_ESCAPING; case "underscores": diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java index 85a23db52..329644bf5 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java @@ -9,7 +9,7 @@ class EscapingSchemeTest { @Test void forString() { - assertThat(EscapingScheme.forString("allow-utf-8")).isEqualTo(EscapingScheme.NO_ESCAPING); + assertThat(EscapingScheme.forString("allow-utf-8")).isEqualTo(EscapingScheme.ALLOW_UTF8); assertThat(EscapingScheme.forString("underscores")) .isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); assertThat(EscapingScheme.forString("dots")).isEqualTo(EscapingScheme.DOTS_ESCAPING); @@ -22,7 +22,7 @@ void forString() { @Test void fromAcceptHeader() { assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=allow-utf-8")) - .isEqualTo(EscapingScheme.NO_ESCAPING); + .isEqualTo(EscapingScheme.ALLOW_UTF8); assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=underscores")) .isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=dots")) diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java index 7ce62ae17..477bc5dd2 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java @@ -118,7 +118,7 @@ public void testLabels() { public void testTotalStrippedFromName(String name) { Counter counter = Counter.builder().name(name).unit(Unit.SECONDS).build(); Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(counter.collect(), EscapingScheme.NO_ESCAPING); + new PrometheusProtobufWriterImpl().convert(counter.collect(), EscapingScheme.ALLOW_UTF8); assertThat(ProtobufUtil.shortDebugString(protobufData)) .isEqualTo( "name: \"my_counter_seconds_total\" type: COUNTER metric { counter { value: 0.0 } }"); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index 4031f7cbc..9ede0f6e4 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -88,7 +88,7 @@ private void run() throws NoSuchFieldException, IllegalAccessException { } Metrics.MetricFamily protobufData = new PrometheusProtobufWriterImpl() - .convert(histogram.collect(), EscapingScheme.NO_ESCAPING); + .convert(histogram.collect(), EscapingScheme.ALLOW_UTF8); String expectedWithMetadata = "name: \"test\" type: HISTOGRAM metric { histogram { " + expected + " } }"; assertThat(ProtobufUtil.shortDebugString(protobufData)) @@ -944,13 +944,13 @@ public void testDefaults() throws IOException { // protobuf Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(snapshot, EscapingScheme.NO_ESCAPING); + new PrometheusProtobufWriterImpl().convert(snapshot, EscapingScheme.ALLOW_UTF8); assertThat(ProtobufUtil.shortDebugString(protobufData)).isEqualTo(expectedProtobuf); // text ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, true); - writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.NO_ESCAPING); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.ALLOW_UTF8); assertThat(out).hasToString(expectedTextFormat); } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index fc4ef371a..3da6bccc4 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -26,7 +26,7 @@ public void testInfoStrippedFromName(String name) { Info info = Info.builder().name(name).labelNames("my.key").build(); info.addLabelValues("value"); Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(info.collect(), EscapingScheme.NO_ESCAPING); + new PrometheusProtobufWriterImpl().convert(info.collect(), EscapingScheme.ALLOW_UTF8); assertThat(ProtobufUtil.shortDebugString(protobufData)) .isEqualTo( "name: \"jvm.runtime_info\" type: GAUGE metric { label { name: \"my.key\" value:" diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 9eb958ff5..4eba4cb17 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -427,6 +427,6 @@ public void testEscapingSchemeDefaultValue() throws IllegalAccessException, NoSu escapingSchemeField.setAccessible(true); EscapingScheme scheme = (EscapingScheme) escapingSchemeField.get(pg); - assertThat(scheme).isEqualTo(EscapingScheme.NO_ESCAPING); + assertThat(scheme).isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); } } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java index 53af0e8f2..51b4de8bb 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java @@ -16,7 +16,7 @@ class ExpositionFormatWriterTest { void write() throws IOException { MetricSnapshots snapshots = new MetricSnapshots(); ByteArrayOutputStream out = new ByteArrayOutputStream(); - writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); + writer.write(out, snapshots, EscapingScheme.ALLOW_UTF8); assertThat(out).hasToString("# EOF\n"); out.reset(); @@ -26,7 +26,7 @@ void write() throws IOException { @Test void toDebugString() { - assertThat(writer.toDebugString(new MetricSnapshots(), EscapingScheme.NO_ESCAPING)) + assertThat(writer.toDebugString(new MetricSnapshots(), EscapingScheme.ALLOW_UTF8)) .isEqualTo("# EOF\n"); assertThat(writer.toDebugString(new MetricSnapshots())).isEqualTo("# EOF\n"); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index b78dcc67b..92a528480 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -499,7 +499,7 @@ public void testGaugeUTF8() throws IOException { .build(); ByteArrayOutputStream out = new ByteArrayOutputStream(); getPrometheusWriter(PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true)) - .write(out, MetricSnapshots.of((MetricSnapshot) gauge), EscapingScheme.NO_ESCAPING); + .write(out, MetricSnapshots.of((MetricSnapshot) gauge), EscapingScheme.ALLOW_UTF8); assertThat(out).hasToString(prometheusText); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java index da085bdbd..26561bf46 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java @@ -25,13 +25,13 @@ void getContentType() { @Test void write() { - assertThatCode(() -> writer.write(null, null, EscapingScheme.NO_ESCAPING)) + assertThatCode(() -> writer.write(null, null, EscapingScheme.ALLOW_UTF8)) .isInstanceOf(UnsupportedOperationException.class); } @Test void toDebugString() { - assertThatCode(() -> writer.toDebugString(null, EscapingScheme.NO_ESCAPING)) + assertThatCode(() -> writer.toDebugString(null, EscapingScheme.ALLOW_UTF8)) .isInstanceOf(UnsupportedOperationException.class); } } 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 015a8730d..245a071e9 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 @@ -312,7 +312,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, registry.scrape(), EscapingScheme.NO_ESCAPING); + writer.write(out, registry.scrape(), EscapingScheme.ALLOW_UTF8); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new UncheckedIOException(e); 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 34637f4f5..31da91f20 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 @@ -162,7 +162,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, registry.scrape(), EscapingScheme.NO_ESCAPING); + writer.write(out, registry.scrape(), EscapingScheme.ALLOW_UTF8); return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java index 224f99c51..64e15590c 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java @@ -12,7 +12,7 @@ class TestUtil { static String convertToOpenMetricsFormat(MetricSnapshots snapshots) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); - writer.write(out, snapshots, EscapingScheme.NO_ESCAPING); + writer.write(out, snapshots, EscapingScheme.ALLOW_UTF8); return out.toString(StandardCharsets.UTF_8); } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 6a504dc73..e79b0d4d8 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -277,7 +277,7 @@ public static String escapeName(String name, EscapingScheme scheme) { StringBuilder escaped = new StringBuilder(); switch (scheme) { - case NO_ESCAPING: + case ALLOW_UTF8: return name; case UNDERSCORE_ESCAPING: for (int i = 0; i < name.length(); ) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java index 4280302bf..422b36ee0 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -11,7 +11,7 @@ private SnapshotEscaper() {} /** Escapes the given metric names and labels with the given escaping scheme. */ public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { - if (scheme == EscapingScheme.NO_ESCAPING || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + if (scheme == EscapingScheme.ALLOW_UTF8 || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { // we re-use the prometheus name for underscore escaping as an optimization return v; } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java index acd61dba7..f84024e95 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -134,7 +134,7 @@ private MetricSnapshot createTestSnapshot( @MethodSource("emptySnapshots") void escape(MetricSnapshot original) { assertThat(original) - .isSameAs(escapeMetricSnapshot(original, EscapingScheme.NO_ESCAPING)) + .isSameAs(escapeMetricSnapshot(original, EscapingScheme.ALLOW_UTF8)) .isSameAs(escapeMetricSnapshot(original, EscapingScheme.UNDERSCORE_ESCAPING)); assertThat(escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING)) .usingRecursiveComparison() @@ -143,7 +143,7 @@ void escape(MetricSnapshot original) { @Test void escapeNull() { - assertThat(escapeMetricSnapshot(null, EscapingScheme.NO_ESCAPING)).isNull(); + assertThat(escapeMetricSnapshot(null, EscapingScheme.ALLOW_UTF8)).isNull(); } public static Stream emptySnapshots() { @@ -204,7 +204,7 @@ public static Stream emptySnapshots() { @Test void metadataName() { MetricMetadata metadata = new MetricMetadata("test."); - assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.NO_ESCAPING)) + assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.ALLOW_UTF8)) .isEqualTo("test."); assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.UNDERSCORE_ESCAPING)) .isEqualTo("test_"); @@ -213,7 +213,7 @@ void metadataName() { @Test void snapshotLabelName() { Labels labels = Labels.builder().label("test.", "value").build(); - assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.NO_ESCAPING)).isEqualTo("test."); + assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.ALLOW_UTF8)).isEqualTo("test."); assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.UNDERSCORE_ESCAPING)) .isEqualTo("test_"); } diff --git a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java index 8c635f7c2..a17ed8bc4 100644 --- a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java +++ b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java @@ -267,7 +267,7 @@ private String origOpenMetrics() throws IOException { private String newOpenMetrics() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, false); - writer.write(out, newRegistry.scrape(), EscapingScheme.NO_ESCAPING); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, newRegistry.scrape(), EscapingScheme.ALLOW_UTF8); + return out.toString(StandardCharsets.UTF_8); } } From 530d51214b27a8ae79ecba01686bb190e3adfc97 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 14 Aug 2025 19:41:44 +0200 Subject: [PATCH 104/106] format Signed-off-by: Gregor Zeitlinger --- docs/content/config/config.md | 14 +++++++------- docs/content/exporters/unicode.md | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/content/config/config.md b/docs/content/config/config.md index 796e1cbfa..d1774a29e 100644 --- a/docs/content/config/config.md +++ b/docs/content/config/config.md @@ -39,7 +39,7 @@ The properties file is searched in the following locations: | Name | Javadoc | Note | -|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | io.prometheus.metrics.exemplarsEnabled | [Counter.Builder.withExemplars()]() | (1) (2) | | io.prometheus.metrics.histogramNativeOnly | [Histogram.Builder.nativeOnly()]() | (2) | | io.prometheus.metrics.histogramClassicOnly | [Histogram.Builder.classicOnly()]() | (2) | @@ -89,7 +89,7 @@ This works for all Metrics properties. | Name | Javadoc | Note | -|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | | io.prometheus.exemplars.minRetentionPeriodSeconds | [ExemplarsProperties.getMinRetentionPeriodSeconds()]() | | | io.prometheus.exemplars.maxRetentionPeriodSeconds | [ExemplarsProperties.getMaxRetentionPeriodSeconds()]() | | | io.prometheus.exemplars.sampleIntervalMilliseconds | [ExemplarsProperties.getSampleIntervalMilliseconds()]() | | @@ -101,7 +101,7 @@ This works for all Metrics properties. | Name | Javadoc | Note | -|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | | io.prometheus.exporter.includeCreatedTimestamps | [ExporterProperties.getIncludeCreatedTimestamps()]() | (1) | | io.prometheus.exporter.exemplarsOnAllMetricTypes | [ExporterProperties.getExemplarsOnAllMetricTypes()]() | (1) | @@ -114,7 +114,7 @@ This works for all Metrics properties. | Name | Javadoc | Note | -|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | | io.prometheus.exporter.filter.metricNameMustBeEqualTo | [ExporterFilterProperties.getAllowedMetricNames()]() | (1) | | io.prometheus.exporter.filter.metricNameMustNotBeEqualTo | [ExporterFilterProperties.getExcludedMetricNames()]() | (2) | | io.prometheus.exporter.filter.metricNameMustStartWith | [ExporterFilterProperties.getAllowedMetricNamePrefixes()]() | (3) | @@ -133,7 +133,7 @@ Only metrics starting with these prefixes will be exposed.
| Name | Javadoc | Note | -|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|------| +| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---- | | io.prometheus.exporter.httpServer.port | [HTTPServer.Builder.port()]() | | @@ -143,7 +143,7 @@ Only metrics starting with these prefixes will be exposed.
| Name | Javadoc | Note | -|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | | io.prometheus.exporter.opentelemetry.protocol | [OpenTelemetryExporter.Builder.protocol()]() | (1) | | io.prometheus.exporter.opentelemetry.endpoint | [OpenTelemetryExporter.Builder.endpoint()]() | | | io.prometheus.exporter.opentelemetry.headers | [OpenTelemetryExporter.Builder.headers()]() | (2) | @@ -171,7 +171,7 @@ See Javadoc for details. | Name | Javadoc | Note | -|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------| +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | | io.prometheus.exporter.pushgateway.address | [PushGateway.Builder.address()]() | | | io.prometheus.exporter.pushgateway.scheme | [PushGateway.Builder.scheme()]() | | | io.prometheus.exporter.pushgateway.job | [PushGateway.Builder.job()]() | | diff --git a/docs/content/exporters/unicode.md b/docs/content/exporters/unicode.md index cbda09167..80cbdc2ef 100644 --- a/docs/content/exporters/unicode.md +++ b/docs/content/exporters/unicode.md @@ -25,5 +25,5 @@ other UTF-8 characters, without any replacements. When using the [Pushgateway](/exporters/pushgateway/), Unicode support has to be enabled explicitly by setting `io.prometheus.exporter.pushgateway.escapingScheme` to `allow-utf-8` in the -Pushgateway configuration file - see +Pushgateway configuration file - see [Pushgateway configuration]({{< relref "/config/config.md#exporter-pushgateway-properties" >}}) From 2c53f49d211338ddd275f566c622d4f02e2cdd7c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 15 Aug 2025 15:39:43 +0200 Subject: [PATCH 105/106] fix Signed-off-by: Gregor Zeitlinger --- docs/content/config/config.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/config/config.md b/docs/content/config/config.md index d1774a29e..deceae000 100644 --- a/docs/content/config/config.md +++ b/docs/content/config/config.md @@ -180,4 +180,5 @@ See Javadoc for details. (1) Escaping scheme can be `allow-utf-8`, `underscores`, `dots`, or `values` as described in -[escaping schemes](https://github.com/prometheus/docs/blob/main/docs/instrumenting/escaping_schemes.md#escaping-schemes) +[escaping schemes](https://github.com/prometheus/docs/blob/main/docs/instrumenting/escaping_schemes.md#escaping-schemes) +and in the [Unicode documentation]({{< relref "../exporters/unicode.md" >}}). From 8f046957f72023c42bf220271ebf8558a8998dca Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 18 Aug 2025 15:29:07 +0200 Subject: [PATCH 106/106] remove unused file Signed-off-by: Gregor Zeitlinger --- .../src/test/resources/prometheus.properties | 1 - 1 file changed, 1 deletion(-) delete mode 100644 prometheus-metrics-model/src/test/resources/prometheus.properties diff --git a/prometheus-metrics-model/src/test/resources/prometheus.properties b/prometheus-metrics-model/src/test/resources/prometheus.properties deleted file mode 100644 index 4ce7f8487..000000000 --- a/prometheus-metrics-model/src/test/resources/prometheus.properties +++ /dev/null @@ -1 +0,0 @@ -io.prometheus.naming.validationScheme=legacy