diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java index 207c024a5..ca3ca08f9 100644 --- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java @@ -7,7 +7,7 @@ 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.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.PrometheusNames; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -34,7 +34,7 @@ protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeR gaugeBuilder.name("x_load").help("process load"); CounterSnapshot.Builder counterBuilder = CounterSnapshot.builder(); - counterBuilder.name(PrometheusNaming.sanitizeMetricName("x_calls_total")).help("invocations"); + counterBuilder.name(PrometheusNames.sanitizeMetricName("x_calls_total")).help("invocations"); String[] targetNames = scrapeRequest.getParameterValues("target"); String targetName; diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java index a2bac20d2..89ab7b02e 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java @@ -230,7 +230,7 @@ private Builder(PrometheusProperties properties) { * Prometheus. * *

Throws an {@link IllegalArgumentException} if {@link - * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) + * io.prometheus.metrics.model.snapshots.PrometheusNames#isValidMetricName(String) * MetricMetadata.isValidMetricName(name)} is {@code false}. */ @Override diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java index 044644ec5..dc5fa6e9c 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java @@ -88,7 +88,7 @@ private Builder(PrometheusProperties properties) { * Prometheus. * *

Throws an {@link IllegalArgumentException} if {@link - * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) + * io.prometheus.metrics.model.snapshots.PrometheusNames#isValidMetricName(String) * MetricMetadata.isValidMetricName(name)} is {@code false}. */ @Override diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java index d7aa6be70..b2138dafa 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java @@ -135,7 +135,7 @@ private Builder(PrometheusProperties config) { * "runtime_info"} in Prometheus. * *

Throws an {@link IllegalArgumentException} if {@link - * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) + * io.prometheus.metrics.model.snapshots.PrometheusNames#isValidMetricName(String) * MetricMetadata.isValidMetricName(name)} is {@code false}. */ @Override diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java index 6f6afa482..003febe66 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java @@ -3,7 +3,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.PrometheusNames; import io.prometheus.metrics.model.snapshots.Unit; import java.util.Arrays; import java.util.List; @@ -61,7 +61,7 @@ protected Builder(List illegalLabelNames, PrometheusProperties propertie } public B name(String name) { - String error = PrometheusNaming.validateMetricName(name); + String error = PrometheusNames.validateMetricName(name); if (error != null) { throw new IllegalArgumentException("'" + name + "': Illegal metric name: " + error); } @@ -81,7 +81,7 @@ public B help(String help) { public B labelNames(String... labelNames) { for (String labelName : labelNames) { - if (!PrometheusNaming.isValidLabelName(labelName)) { + if (!PrometheusNames.isValidLabelName(labelName)) { throw new IllegalArgumentException(labelName + ": illegal label name"); } if (illegalLabelNames.contains(labelName)) { diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java index 4dbaf8ad5..6de7cef89 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.core.metrics; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.prometheusName; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.StateSetDataPoint; 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 643e0aeca..f30cab10b 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.NameEscaper.escapeName; import static java.util.Objects.requireNonNull; import io.prometheus.metrics.config.EscapingScheme; 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 1ba1c627d..2699d133e 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,7 +23,7 @@ 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.PrometheusNames; import io.prometheus.metrics.model.snapshots.Quantile; import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; @@ -396,7 +396,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.isValidLegacyMetricName(name)) { + if (!PrometheusNames.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 5dc3f629b..4bb0e4220 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 @@ -21,7 +21,7 @@ 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.PrometheusNames; import io.prometheus.metrics.model.snapshots.Quantile; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; @@ -396,7 +396,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.isValidLegacyLabelName(name)) { + if (!PrometheusNames.isValidLegacyLabelName(name)) { metricInsideBraces = true; 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 42909ee25..1ca1402dd 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 @@ -2,7 +2,7 @@ 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.PrometheusNames; import io.prometheus.metrics.model.snapshots.SnapshotEscaper; import java.io.IOException; import java.io.Writer; @@ -137,13 +137,13 @@ static void writeLabels( static void writeName(Writer writer, String name, NameType nameType) throws IOException { switch (nameType) { case Metric: - if (PrometheusNaming.isValidLegacyMetricName(name)) { + if (PrometheusNames.isValidLegacyMetricName(name)) { writer.write(name); return; } break; case Label: - if (PrometheusNaming.isValidLegacyLabelName(name)) { + if (PrometheusNames.isValidLegacyLabelName(name)) { 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 09acff58c..c7d5d6435 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 @@ -17,7 +17,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.PrometheusNames; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; @@ -2707,7 +2707,7 @@ public void testUnknownWithDots() throws IOException { // @formatter:on UnknownSnapshot unknown = UnknownSnapshot.builder() - .name(PrometheusNaming.sanitizeMetricName("some.unknown.metric", Unit.BYTES)) + .name(PrometheusNames.sanitizeMetricName("some.unknown.metric", Unit.BYTES)) .help("help message") .unit(Unit.BYTES) .dataPoint( diff --git a/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java b/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java index 4ab03341e..3942aec90 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java @@ -18,7 +18,7 @@ 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.PrometheusNames; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import java.util.Collections; @@ -101,7 +101,7 @@ private static String getHelpMessage(String metricName, Metric metric) { private MetricMetadata getMetricMetaData(String metricName, Metric metric) { String name = labelMapper != null ? labelMapper.getName(metricName) : metricName; return new MetricMetadata( - PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric)); + PrometheusNames.sanitizeMetricName(name), getHelpMessage(metricName, metric)); } /** @@ -134,7 +134,7 @@ MetricSnapshot fromGauge(String dropwizardName, Gauge gauge) { Level.FINE, String.format( "Invalid type for Gauge %s: %s", - PrometheusNaming.sanitizeMetricName(dropwizardName), + PrometheusNames.sanitizeMetricName(dropwizardName), obj == null ? "null" : obj.getClass().getName())); return null; } @@ -170,7 +170,7 @@ MetricSnapshot fromSnapshotAndCount( String name = labelMapper != null ? labelMapper.getName(dropwizardName) : dropwizardName; MetricMetadata metadata = - new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage); + new MetricMetadata(PrometheusNames.sanitizeMetricName(name), helpMessage); SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder = SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count); if (labelMapper != null) { diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java index c68d26f49..04b005abc 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java @@ -18,7 +18,7 @@ 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.PrometheusNames; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import java.util.Collections; @@ -101,7 +101,7 @@ private static String getHelpMessage(String metricName, Metric metric) { private MetricMetadata getMetricMetaData(String metricName, Metric metric) { String name = labelMapper != null ? labelMapper.getName(metricName) : metricName; return new MetricMetadata( - PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric)); + PrometheusNames.sanitizeMetricName(name), getHelpMessage(metricName, metric)); } /** @@ -134,7 +134,7 @@ MetricSnapshot fromGauge(String dropwizardName, Gauge gauge) { Level.FINE, String.format( "Invalid type for Gauge %s: %s", - PrometheusNaming.sanitizeMetricName(dropwizardName), + PrometheusNames.sanitizeMetricName(dropwizardName), obj == null ? "null" : obj.getClass().getName())); return null; } @@ -170,7 +170,7 @@ MetricSnapshot fromSnapshotAndCount( String name = labelMapper != null ? labelMapper.getName(dropwizardName) : dropwizardName; MetricMetadata metadata = - new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage); + new MetricMetadata(PrometheusNames.sanitizeMetricName(name), helpMessage); SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder = SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count); if (labelMapper != null) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java index 7db568d95..4ebbef8e6 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.model.registry; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.prometheusName; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; 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 31c6ab485..1e762123d 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 @@ -1,7 +1,7 @@ package io.prometheus.metrics.model.snapshots; -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.PrometheusNames.isValidLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.prometheusName; import java.util.ArrayList; import java.util.Arrays; @@ -48,8 +48,8 @@ public boolean isEmpty() { * Labels.of(...)} methods, or you can use the {@link Labels#builder()}. * * @param keyValuePairs as in {@code {name1, value1, name2, value2}}. Length must be even. {@link - * PrometheusNaming#isValidLabelName(String)} must be true for each name. Use {@link - * PrometheusNaming#sanitizeLabelName(String)} to convert arbitrary strings to valid label + * PrometheusNames#isValidLabelName(String)} must be true for each name. Use {@link + * PrometheusNames#sanitizeLabelName(String)} to convert arbitrary strings to valid label * names. Label names must be unique (no duplicate label names). */ public static Labels of(String... keyValuePairs) { @@ -75,8 +75,8 @@ public static Labels of(String... keyValuePairs) { * Create a new Labels instance. You can either create Labels with one of the static {@code * Labels.of(...)} methods, or you can use the {@link Labels#builder()}. * - * @param names label names. {@link PrometheusNaming#isValidLabelName(String)} must be true for - * each name. Use {@link PrometheusNaming#sanitizeLabelName(String)} to convert arbitrary + * @param names label names. {@link PrometheusNames#isValidLabelName(String)} must be true for + * each name. Use {@link PrometheusNames#sanitizeLabelName(String)} to convert arbitrary * strings to valid label names. Label names must be unique (no duplicate label names). * @param values label values. {@code names.size()} must be equal to {@code values.size()}. */ @@ -98,8 +98,8 @@ public static Labels of(List names, List values) { * Create a new Labels instance. You can either create Labels with one of the static {@code * Labels.of(...)} methods, or you can use the {@link Labels#builder()}. * - * @param names label names. {@link PrometheusNaming#isValidLabelName(String)} must be true for - * each name. Use {@link PrometheusNaming#sanitizeLabelName(String)} to convert arbitrary + * @param names label names. {@link PrometheusNames#isValidLabelName(String)} must be true for + * each name. Use {@link PrometheusNames#sanitizeLabelName(String)} to convert arbitrary * strings to valid label names. Label names must be unique (no duplicate label names). * @param values label values. {@code names.length} must be equal to {@code values.length}. */ @@ -121,11 +121,11 @@ static String[] makePrometheusNames(String[] names) { String[] prometheusNames = names; for (int i = 0; i < names.length; i++) { String name = names[i]; - if (!PrometheusNaming.isValidLegacyLabelName(name)) { + if (!PrometheusNames.isValidLegacyLabelName(name)) { if (prometheusNames == names) { prometheusNames = Arrays.copyOf(names, names.length); } - prometheusNames[i] = PrometheusNaming.prometheusName(name); + prometheusNames[i] = PrometheusNames.prometheusName(name); } } return prometheusNames; 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 9c54f96d5..b29f36f35 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 @@ -45,9 +45,9 @@ public MetricMetadata(String name, String help) { /** * Constructor. * - * @param name must not be {@code null}. {@link PrometheusNaming#isValidMetricName(String) + * @param name must not be {@code null}. {@link PrometheusNames#isValidMetricName(String) * isValidMetricName(name)} must be {@code true}. Use {@link - * PrometheusNaming#sanitizeMetricName(String)} to convert arbitrary strings into valid names. + * PrometheusNames#sanitizeMetricName(String)} to convert arbitrary strings into valid names. * @param help optional. May be {@code null}. * @param unit optional. May be {@code null}. */ @@ -56,7 +56,7 @@ public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) { this.help = help; this.unit = unit; validate(); - this.prometheusName = PrometheusNaming.prometheusName(name); + this.prometheusName = PrometheusNames.prometheusName(name); } /** @@ -97,7 +97,7 @@ private void validate() { if (name == null) { throw new IllegalArgumentException("Missing required field: name is null"); } - String error = PrometheusNaming.validateMetricName(name); + String error = PrometheusNames.validateMetricName(name); if (error != null) { throw new IllegalArgumentException( "'" @@ -105,7 +105,7 @@ private void validate() { + "': Illegal metric name. " + error + " Call " - + PrometheusNaming.class.getSimpleName() + + PrometheusNames.class.getSimpleName() + ".sanitizeMetricName(name) to avoid this error."); } if (hasUnit()) { @@ -118,13 +118,13 @@ private void validate() { + unit + "." + " Call " - + PrometheusNaming.class.getSimpleName() + + PrometheusNames.class.getSimpleName() + ".sanitizeMetricName(name, unit) to avoid this error."); } } } MetricMetadata escape(EscapingScheme escapingScheme) { - return new MetricMetadata(PrometheusNaming.escapeName(name, escapingScheme), help, unit); + return new MetricMetadata(NameEscaper.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 4dac2e30e..04f435475 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 @@ -61,7 +61,7 @@ public abstract static class Builder> { /** * The name is required. If the name is missing or invalid, {@code build()} will throw an {@link - * IllegalArgumentException}. See {@link PrometheusNaming#isValidMetricName(String)} for info on + * IllegalArgumentException}. See {@link PrometheusNames#isValidMetricName(String)} for info on * valid metric names. */ public T name(String name) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java index ecee897e4..48a9ccd6a 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.prometheusName; import static java.util.Collections.unmodifiableList; import static java.util.Comparator.comparing; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/NameEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/NameEscaper.java new file mode 100644 index 000000000..715f88951 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/NameEscaper.java @@ -0,0 +1,74 @@ +package io.prometheus.metrics.model.snapshots; + +import io.prometheus.metrics.config.EscapingScheme; + +public class NameEscaper { + /** + * 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 ALLOW_UTF8: + return name; + case UNDERSCORE_ESCAPING: + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (PrometheusNames.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 (PrometheusNames.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 (PrometheusNames.isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else if (!PrometheusNames.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); + } + } + + static boolean needsEscaping(String name, EscapingScheme scheme) { + return !PrometheusNames.isValidLegacyMetricName(name) + || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); + } +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNames.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNames.java new file mode 100644 index 000000000..1cb74f735 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNames.java @@ -0,0 +1,287 @@ +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.EscapingScheme; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +/** + * Utility for Prometheus Metric and Label naming. + * + *

Note that this library allows dots in metric and label names. Dots will automatically be + * replaced with underscores in Prometheus exposition formats. However, if metrics are exposed in + * OpenTelemetry format the dots are retained. + */ +public class PrometheusNames { + + static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); + + /** Legal characters for label names. */ + 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_.:]+$"); + + /** + * According to OpenMetrics {@code _count} and {@code _sum} (and {@code _gcount}, {@code _gsum}) + * should also be reserved metric name suffixes. However, popular instrumentation libraries have + * Gauges with names ending in {@code _count}. Examples: + * + *

+ * + * We do not treat {@code _count} and {@code _sum} as reserved suffixes here for compatibility + * with these libraries. However, there is a risk of name conflict if someone creates a gauge + * named {@code my_data_count} and a histogram or summary named {@code my_data}, because the + * histogram or summary will implicitly have a sample named {@code my_data_count}. + */ + static final String[] RESERVED_METRIC_NAME_SUFFIXES = { + "_total", "_created", "_bucket", "_info", + ".total", ".created", ".bucket", ".info" + }; + + /** + * Test if a metric name is valid. Rules: + * + * + * + * If a metric has a {@link Unit}, the metric name SHOULD end with the unit as a suffix. Note that + * OpenMetrics requires metric names to have their unit as + * suffix, and we implement this in {@code prometheus-metrics-core}. However, {@code + * prometheus-metrics-model} does not enforce Unit suffixes. + * + *

Example: If you create a Counter for a processing time with Unit {@link Unit#SECONDS + * SECONDS}, the name should be {@code processing_time_seconds}. When exposed in OpenMetrics Text + * format, this will be represented as two values: {@code processing_time_seconds_total} for the + * counter value, and the optional {@code processing_time_seconds_created} timestamp. + * + *

Use {@link #sanitizeMetricName(String)} to convert arbitrary Strings to valid metric names. + */ + public static boolean isValidMetricName(String name) { + return validateMetricName(name) == null; + } + + /** + * Same as {@link #isValidMetricName(String)}, but produces an error message. + * + *

The name is valid if the error message is {@code null}. + */ + @Nullable + public static String validateMetricName(String name) { + String reservedSuffix = findReservedSuffix(name); + if (reservedSuffix != null) { + return reservedSuffix; + } + if (isValidUtf8(name)) { + return null; + } + return "The metric name contains unsupported characters"; + } + + @Nullable + static String findReservedSuffix(String name) { + for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { + if (name.endsWith(reservedSuffix)) { + return "The metric name must not include the '" + reservedSuffix + "' suffix."; + } + } + return null; + } + + public static boolean isValidLegacyMetricName(String name) { + return METRIC_NAME_PATTERN.matcher(name).matches(); + } + + public static boolean isValidLabelName(String name) { + return isValidUtf8(name) && !hasInvalidLabelPrefix(name); + } + + static boolean hasInvalidLabelPrefix(String name) { + return name.startsWith("__") + || name.startsWith("._") + || name.startsWith("..") + || name.startsWith("_."); + } + + 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(); + } + + /** + * 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. */ + @Nullable + public static String validateUnitName(String name) { + if (name.isEmpty()) { + return "The unit name must not be empty."; + } + for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { + String suffixName = reservedSuffix.substring(1); + if (name.endsWith(suffixName)) { + return suffixName + " is a reserved suffix in Prometheus"; + } + } + if (!UNIT_NAME_PATTERN.matcher(name).matches()) { + return "The unit name contains unsupported characters"; + } + return null; + } + + /** + * Get the metric or label name that is used in Prometheus exposition format. + * + * @param name must be a valid metric or label name, i.e. {@link #isValidMetricName(String) + * isValidMetricName(name)} or {@link #isValidLabelName(String) isValidLabelName(name)} must + * be true. + * @return the name with dots replaced by underscores. + */ + public static String prometheusName(String name) { + return NameEscaper.escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); + } + + /** + * Convert an arbitrary string to a name where {@link #isValidMetricName(String) + * isValidMetricName(name)} is true. + */ + public static String sanitizeMetricName(String metricName) { + if (metricName.isEmpty()) { + throw new IllegalArgumentException("Cannot convert an empty string to a valid metric name."); + } + String sanitizedName = metricName; + boolean modified = true; + while (modified) { + modified = false; + for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { + if (sanitizedName.equals(reservedSuffix)) { + // This is for the corner case when you call sanitizeMetricName("_total"). + // In that case the result will be "total". + return reservedSuffix.substring(1); + } + if (sanitizedName.endsWith(reservedSuffix)) { + sanitizedName = + sanitizedName.substring(0, sanitizedName.length() - reservedSuffix.length()); + modified = true; + } + } + } + return sanitizedName; + } + + /** + * Like {@link #sanitizeMetricName(String)}, but also makes sure that the unit is appended as a + * suffix if the unit is not {@code null}. + */ + public static String sanitizeMetricName(String metricName, Unit unit) { + String result = sanitizeMetricName(metricName); + if (unit != null) { + if (!result.endsWith("_" + unit) && !result.endsWith("." + unit)) { + result += "_" + unit; + } + } + return result; + } + + /** + * Convert an arbitrary string to a name where {@link #isValidLabelName(String) + * isValidLabelName(name)} is true. + */ + public static String sanitizeLabelName(String labelName) { + if (labelName.isEmpty()) { + throw new IllegalArgumentException("Cannot convert an empty string to a valid label name."); + } + String sanitizedName = labelName; + while (hasInvalidLabelPrefix(sanitizedName)) { + sanitizedName = sanitizedName.substring(1); + } + return sanitizedName; + } + + /** + * 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("")}. + * @throws NullPointerException if {@code unitName} is null. + */ + public static String sanitizeUnitName(String unitName) { + if (unitName.isEmpty()) { + throw new IllegalArgumentException("Cannot convert an empty string to a valid unit name."); + } + String sanitizedName = replaceIllegalCharsInUnitName(unitName); + boolean modified = true; + while (modified) { + modified = false; + while (sanitizedName.startsWith("_") || sanitizedName.startsWith(".")) { + sanitizedName = sanitizedName.substring(1); + modified = true; + } + while (sanitizedName.endsWith(".") || sanitizedName.endsWith("_")) { + sanitizedName = sanitizedName.substring(0, sanitizedName.length() - 1); + modified = true; + } + for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { + String suffixName = reservedSuffix.substring(1); + if (sanitizedName.endsWith(suffixName)) { + sanitizedName = sanitizedName.substring(0, sanitizedName.length() - suffixName.length()); + modified = true; + } + } + } + if (sanitizedName.isEmpty()) { + throw new IllegalArgumentException( + "Cannot convert '" + unitName + "' into a valid unit name."); + } + return sanitizedName; + } + + /** Returns a string that matches {@link #UNIT_NAME_PATTERN}. */ + private static String replaceIllegalCharsInUnitName(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 == '.' + || (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9')) { + sanitized[i] = ch; + } else { + sanitized[i] = '_'; + } + } + return new String(sanitized); + } + + static boolean isValidLegacyChar(int c, int i) { + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || (c >= '0' && c <= '9' && i > 0); + } + + 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/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index e79b0d4d8..e399adc55 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,12 +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 io.prometheus.metrics.config.EscapingScheme; -import java.nio.charset.StandardCharsets; -import java.util.regex.Pattern; import javax.annotation.Nullable; /** @@ -15,44 +8,20 @@ *

Note that this library allows dots in metric and label names. Dots will automatically be * replaced with underscores in Prometheus exposition formats. However, if metrics are exposed in * OpenTelemetry format the dots are retained. + * + * @deprecated use {@link PrometheusNames} instead. */ +@Deprecated +@SuppressWarnings("InlineMeSuggester") public class PrometheusNaming { - private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_:][a-zA-Z0-9_:]*$"); - - /** Legal characters for label names. */ - 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_.:]+$"); - - /** - * According to OpenMetrics {@code _count} and {@code _sum} (and {@code _gcount}, {@code _gsum}) - * should also be reserved metric name suffixes. However, popular instrumentation libraries have - * Gauges with names ending in {@code _count}. Examples: - * - *

- * - * We do not treat {@code _count} and {@code _sum} as reserved suffixes here for compatibility - * with these libraries. However, there is a risk of name conflict if someone creates a gauge - * named {@code my_data_count} and a histogram or summary named {@code my_data}, because the - * histogram or summary will implicitly have a sample named {@code my_data_count}. - */ - private static final String[] RESERVED_METRIC_NAME_SUFFIXES = { - "_total", "_created", "_bucket", "_info", - ".total", ".created", ".bucket", ".info" - }; - /** * Test if a metric name is valid. Rules: * * * * If a metric has a {@link Unit}, the metric name SHOULD end with the unit as a suffix. Note that @@ -66,7 +35,10 @@ public class PrometheusNaming { * counter value, and the optional {@code processing_time_seconds_created} timestamp. * *

Use {@link #sanitizeMetricName(String)} to convert arbitrary Strings to valid metric names. + * + * @deprecated use {@link PrometheusNames#isValidMetricName(String)} instead. */ + @Deprecated public static boolean isValidMetricName(String name) { return validateMetricName(name) == null; } @@ -75,64 +47,60 @@ public static boolean isValidMetricName(String name) { * Same as {@link #isValidMetricName(String)}, but produces an error message. * *

The name is valid if the error message is {@code null}. + * + * @deprecated use {@link PrometheusNames#validateMetricName(String)} instead. */ + @Deprecated @Nullable 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."; - } + String reservedSuffix = PrometheusNames.findReservedSuffix(name); + if (reservedSuffix != null) { + return reservedSuffix; } - if (isValidUtf8(name)) { - return null; + if (!PrometheusNames.isValidLegacyMetricName(name)) { + return "The metric name contains unsupported characters"; } - return "The metric name contains unsupported characters"; - } - - public static boolean isValidLegacyMetricName(String name) { - return METRIC_NAME_PATTERN.matcher(name).matches(); + return null; } + /** + * Test if a label name is valid. Rules: + * + *

+ * + * @deprecated use {@link PrometheusNames#isValidLabelName(String)} instead. + */ + @Deprecated public static boolean isValidLabelName(String name) { - return isValidUtf8(name) - && !(name.startsWith("__") - || name.startsWith("._") - || name.startsWith("..") - || name.startsWith("_.")); - } - - 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(); + return PrometheusNames.isValidLegacyLabelName(name) + && !PrometheusNames.hasInvalidLabelPrefix(name); } /** * Units may not have illegal characters, and they may not end with a reserved suffix like * 'total'. + * + * @deprecated use {@link PrometheusNames#isValidUnitName(String)} instead. */ + @Deprecated public static boolean isValidUnitName(String name) { - return validateUnitName(name) == null; + // no Unicode support for unit names + return PrometheusNames.isValidUnitName(name); } - /** Same as {@link #isValidUnitName(String)} but returns an error message. */ + /** + * Same as {@link #isValidUnitName(String)} but returns an error message. + * + * @deprecated use {@link PrometheusNames#validateUnitName(String)} instead. + */ + @Deprecated @Nullable public static String validateUnitName(String name) { - if (name.isEmpty()) { - return "The unit name must not be empty."; - } - for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { - String suffixName = reservedSuffix.substring(1); - if (name.endsWith(suffixName)) { - return suffixName + " is a reserved suffix in Prometheus"; - } - } - if (!UNIT_NAME_PATTERN.matcher(name).matches()) { - return "The unit name contains unsupported characters"; - } - return null; + // no Unicode support for unit names + return PrometheusNames.validateUnitName(name); } /** @@ -142,121 +110,77 @@ public static String validateUnitName(String name) { * isValidMetricName(name)} or {@link #isValidLabelName(String) isValidLabelName(name)} must * be true. * @return the name with dots replaced by underscores. + * @deprecated use {@link PrometheusNames#prometheusName(String)} instead. */ + @Deprecated public static String prometheusName(String name) { - return escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); + return name.replace(".", "_"); } /** * Convert an arbitrary string to a name where {@link #isValidMetricName(String) * isValidMetricName(name)} is true. + * + * @deprecated use {@link PrometheusNames#sanitizeMetricName(String)} instead. */ + @Deprecated public static String sanitizeMetricName(String metricName) { if (metricName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid metric name."); } - String sanitizedName = metricName; - boolean modified = true; - while (modified) { - modified = false; - for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { - if (sanitizedName.equals(reservedSuffix)) { - // This is for the corner case when you call sanitizeMetricName("_total"). - // In that case the result will be "total". - return reservedSuffix.substring(1); - } - if (sanitizedName.endsWith(reservedSuffix)) { - sanitizedName = - sanitizedName.substring(0, sanitizedName.length() - reservedSuffix.length()); - modified = true; - } - } - } - return sanitizedName; + return PrometheusNames.sanitizeMetricName(replaceIllegalCharsInMetricName(metricName)); } /** * Like {@link #sanitizeMetricName(String)}, but also makes sure that the unit is appended as a * suffix if the unit is not {@code null}. + * + * @deprecated use {@link PrometheusNames#sanitizeMetricName(String, Unit)} instead. */ + @Deprecated public static String sanitizeMetricName(String metricName, Unit unit) { - String result = sanitizeMetricName(metricName); - if (unit != null) { - if (!result.endsWith("_" + unit) && !result.endsWith("." + unit)) { - result += "_" + unit; - } - } - return result; + return PrometheusNames.sanitizeMetricName(replaceIllegalCharsInMetricName(metricName), unit); } /** * Convert an arbitrary string to a name where {@link #isValidLabelName(String) * isValidLabelName(name)} is true. + * + * @deprecated use {@link PrometheusNames#sanitizeLabelName(String)} instead. */ + @Deprecated public static String sanitizeLabelName(String labelName) { if (labelName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid label name."); } - String sanitizedName = labelName; - while (sanitizedName.startsWith("__") - || sanitizedName.startsWith("_.") - || sanitizedName.startsWith("._") - || sanitizedName.startsWith("..")) { - sanitizedName = sanitizedName.substring(1); - } - return sanitizedName; + return PrometheusNames.sanitizeLabelName(replaceIllegalCharsInLabelName(labelName)); } /** - * Convert an arbitrary string to a name where {@link #validateUnitName(String)} is {@code null} - * (i.e. the name is valid). + * Convert an arbitrary string to a name where {@link #isValidUnitName(String) + * isValidUnitName(name)} is true. * * @throws IllegalArgumentException if the {@code unitName} cannot be converted, for example if * you call {@code sanitizeUnitName("total")} or {@code sanitizeUnitName("")}. * @throws NullPointerException if {@code unitName} is null. + * @deprecated use {@link PrometheusNames#sanitizeUnitName(String)} instead. */ + @Deprecated public static String sanitizeUnitName(String unitName) { - if (unitName.isEmpty()) { - throw new IllegalArgumentException("Cannot convert an empty string to a valid unit name."); - } - String sanitizedName = replaceIllegalCharsInUnitName(unitName); - boolean modified = true; - while (modified) { - modified = false; - while (sanitizedName.startsWith("_") || sanitizedName.startsWith(".")) { - sanitizedName = sanitizedName.substring(1); - modified = true; - } - while (sanitizedName.endsWith(".") || sanitizedName.endsWith("_")) { - sanitizedName = sanitizedName.substring(0, sanitizedName.length() - 1); - modified = true; - } - for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { - String suffixName = reservedSuffix.substring(1); - if (sanitizedName.endsWith(suffixName)) { - sanitizedName = sanitizedName.substring(0, sanitizedName.length() - suffixName.length()); - modified = true; - } - } - } - if (sanitizedName.isEmpty()) { - throw new IllegalArgumentException( - "Cannot convert '" + unitName + "' into a valid unit name."); - } - return sanitizedName; + // no Unicode support for unit names + return PrometheusNames.sanitizeUnitName(unitName); } - /** Returns a string that matches {@link #UNIT_NAME_PATTERN}. */ - private static String replaceIllegalCharsInUnitName(String name) { + /** Returns a string that matches {@link PrometheusNames#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 == '.' + if (ch == '.' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9')) { + || (i > 0 && ch >= '0' && ch <= '9')) { sanitized[i] = ch; } else { sanitized[i] = '_'; @@ -265,84 +189,21 @@ 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 ALLOW_UTF8: - 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); + /** Returns a string that matches {@link PrometheusNames#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] = '_'; + } } - } - - 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); + return new String(sanitized); } } 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 422b36ee0..df8c2e862 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 @@ -60,7 +60,7 @@ static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { for (Label l : labels) { - if (PrometheusNaming.needsEscaping(l.getName(), scheme)) { + if (NameEscaper.needsEscaping(l.getName(), scheme)) { return true; } } @@ -100,7 +100,7 @@ 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()); + outLabelsBuilder.label(NameEscaper.escapeName(l.getName(), scheme), l.getValue()); } return outLabelsBuilder.build(); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java index 31a9524e7..5fca57dc1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java @@ -31,7 +31,7 @@ public Unit(String name) { throw new NullPointerException("Unit name cannot be null."); } name = name.trim(); - String error = PrometheusNaming.validateUnitName(name); + String error = PrometheusNames.validateUnitName(name); if (error != null) { throw new IllegalArgumentException(name + ": Illegal unit name: " + error); } 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 f2d6a6ba4..9607a8adb 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 @@ -1,6 +1,6 @@ package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.sanitizeMetricName; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NameEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NameEscaperTest.java new file mode 100644 index 000000000..95199c6be --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NameEscaperTest.java @@ -0,0 +1,77 @@ +package io.prometheus.metrics.model.snapshots; + +import static io.prometheus.metrics.model.snapshots.NameEscaper.escapeName; +import static org.assertj.core.api.Assertions.assertThat; + +import io.prometheus.metrics.config.EscapingScheme; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class NameEscaperTest { + @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/PrometheusNamesTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamesTest.java new file mode 100644 index 000000000..38fbc1d5c --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamesTest.java @@ -0,0 +1,154 @@ +package io.prometheus.metrics.model.snapshots; + +import static io.prometheus.metrics.model.snapshots.PrometheusNames.isValidLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.prometheusName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.sanitizeLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.sanitizeMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.sanitizeUnitName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.validateMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.validateUnitName; +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.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PrometheusNamesTest { + + @Test + void testSanitizeMetricName() { + 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"); + assertThat(sanitizeMetricName("a.b")).isEqualTo("a.b"); + assertThat(sanitizeMetricName("_total")).isEqualTo("total"); + assertThat(sanitizeMetricName("total")).isEqualTo("total"); + } + + @Test + public void testSanitizeMetricNameWithUnit() { + 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("_total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); + assertThat(sanitizeMetricName("total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); + } + + @Test + public void testSanitizeLabelName() { + assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); + assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); + assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); + assertThat(prometheusName(sanitizeLabelName("___abc"))).isEqualTo("_abc"); + assertThat(prometheusName(sanitizeLabelName("_.abc"))).isEqualTo("_abc"); + assertThat(sanitizeLabelName("abc.def")).isEqualTo("abc.def"); + assertThat(sanitizeLabelName("abc.def2")).isEqualTo("abc.def2"); + } + + @Test + public void testValidateUnitName() { + assertThat(validateUnitName("secondstotal")).isNotNull(); + assertThat(validateUnitName("total")).isNotNull(); + assertThat(validateUnitName("seconds_total")).isNotNull(); + assertThat(validateUnitName("_total")).isNotNull(); + assertThat(validateUnitName("")).isNotNull(); + + assertThat(validateUnitName("seconds")).isNull(); + assertThat(validateUnitName("2")).isNull(); + } + + @Test + public void testSanitizeUnitName() { + assertThat(sanitizeUnitName("seconds")).isEqualTo("seconds"); + assertThat(sanitizeUnitName("seconds_total")).isEqualTo("seconds"); + assertThat(sanitizeUnitName("seconds_total_total")).isEqualTo("seconds"); + assertThat(sanitizeUnitName("m/s")).isEqualTo("m_s"); + assertThat(sanitizeUnitName("secondstotal")).isEqualTo("seconds"); + assertThat(sanitizeUnitName("2")).isEqualTo("2"); + } + + @Test + public void testInvalidUnitName1() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> sanitizeUnitName("total")); + } + + @Test + public void testInvalidUnitName2() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> sanitizeUnitName("_total")); + } + + @Test + public void testInvalidUnitName3() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> sanitizeUnitName("%")); + } + + @Test + public void testEmptyUnitName() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> sanitizeUnitName("")); + } + + @ParameterizedTest + @MethodSource("nameIsValid") + public void testLabelNameIsValidUtf8(String labelName, boolean utf8Valid) { + assertMetricName(labelName, utf8Valid); + assertLabelName(labelName, utf8Valid); + } + + 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"); + } + + static Stream nameIsValid() { + return Stream.of( + 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)); + } + + @Test + void testValidMetricName() { + assertThat(PrometheusNames.isValidMetricName("valid_metric_name")).isTrue(); + assertThat(PrometheusNames.isValidMetricName("invalid_metric_name_total")).isFalse(); + assertThat(PrometheusNames.isValidMetricName("0abc.def")).isTrue(); + } + + @Test + void testValidLabelName() { + assertThat(PrometheusNames.isValidLabelName("valid_label_name")).isTrue(); + assertThat(PrometheusNames.isValidLabelName("0invalid_label_name")).isTrue(); + assertThat(PrometheusNames.isValidLabelName("invalid-label-name")).isTrue(); + } + + @Test + void testValidUnitName() { + assertThat(PrometheusNames.isValidUnitName("seconds")).isTrue(); + assertThat(PrometheusNames.isValidUnitName("seconds_total")).isFalse(); + assertThat(PrometheusNames.isValidUnitName("2")).isTrue(); + } +} 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 40c1f1bde..3c60ece29 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,200 +1,143 @@ 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; -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.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 io.prometheus.metrics.config.EscapingScheme; -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; +@SuppressWarnings("deprecation") class PrometheusNamingTest { @Test public void testSanitizeMetricName() { - 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"); - assertThat(sanitizeMetricName("a.b")).isEqualTo("a.b"); - assertThat(sanitizeMetricName("_total")).isEqualTo("total"); - assertThat(sanitizeMetricName("total")).isEqualTo("total"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeMetricName("0abc.def"))) + .isEqualTo("_abc_def"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeMetricName("___ab.:c0"))) + .isEqualTo("___ab__c0"); + assertThat(PrometheusNaming.sanitizeMetricName("my_prefix/my_metric")) + .isEqualTo("my_prefix_my_metric"); + assertThat( + PrometheusNaming.prometheusName( + PrometheusNaming.sanitizeMetricName("my_counter_total"))) + .isEqualTo("my_counter"); + assertThat(PrometheusNaming.sanitizeMetricName("jvm.info")).isEqualTo("jvm"); + assertThat(PrometheusNaming.sanitizeMetricName("jvm_info")).isEqualTo("jvm"); + assertThat(PrometheusNaming.sanitizeMetricName("jvm.info")).isEqualTo("jvm"); + assertThat(PrometheusNaming.sanitizeMetricName("a.b")).isEqualTo("a.b"); + assertThat(PrometheusNaming.sanitizeMetricName("_total")).isEqualTo("total"); + assertThat(PrometheusNaming.sanitizeMetricName("total")).isEqualTo("total"); } @Test public void testSanitizeMetricNameWithUnit() { - assertThat(prometheusName(sanitizeMetricName("def", Unit.RATIO))) - .isEqualTo("def_" + Unit.RATIO); - assertThat(prometheusName(sanitizeMetricName("my_counter_total", Unit.RATIO))) + assertThat( + PrometheusNaming.prometheusName( + PrometheusNaming.sanitizeMetricName("0abc.def", Unit.RATIO))) + .isEqualTo("_abc_def_" + Unit.RATIO); + assertThat( + PrometheusNaming.prometheusName( + PrometheusNaming.sanitizeMetricName("___ab.:c0", Unit.RATIO))) + .isEqualTo("___ab__c0_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("my_prefix/my_metric", Unit.RATIO)) + .isEqualTo("my_prefix_my_metric_" + Unit.RATIO); + assertThat( + PrometheusNaming.prometheusName( + PrometheusNaming.sanitizeMetricName("my_counter_total", Unit.RATIO))) .isEqualTo("my_counter_" + Unit.RATIO); - assertThat(sanitizeMetricName("jvm.info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("_total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); - assertThat(sanitizeMetricName("total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("jvm.info", Unit.RATIO)) + .isEqualTo("jvm_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("jvm_info", Unit.RATIO)) + .isEqualTo("jvm_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("jvm.info", Unit.RATIO)) + .isEqualTo("jvm_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("a.b", Unit.RATIO)) + .isEqualTo("a.b_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("_total", Unit.RATIO)) + .isEqualTo("total_" + Unit.RATIO); + assertThat(PrometheusNaming.sanitizeMetricName("total", Unit.RATIO)) + .isEqualTo("total_" + Unit.RATIO); } @Test public void testSanitizeLabelName() { - assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); - assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); - assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); - assertThat(prometheusName(sanitizeLabelName("___abc"))).isEqualTo("_abc"); - assertThat(prometheusName(sanitizeLabelName("_.abc"))).isEqualTo("_abc"); - assertThat(sanitizeLabelName("abc.def")).isEqualTo("abc.def"); - assertThat(sanitizeLabelName("abc.def2")).isEqualTo("abc.def2"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeLabelName("0abc.def"))) + .isEqualTo("_abc_def"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeLabelName("_abc"))) + .isEqualTo("_abc"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeLabelName("__abc"))) + .isEqualTo("_abc"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeLabelName("___abc"))) + .isEqualTo("_abc"); + assertThat(PrometheusNaming.prometheusName(PrometheusNaming.sanitizeLabelName("_.abc"))) + .isEqualTo("_abc"); + assertThat(PrometheusNaming.sanitizeLabelName("abc.def")).isEqualTo("abc.def"); + assertThat(PrometheusNaming.sanitizeLabelName("abc.def2")).isEqualTo("abc.def2"); } @Test public void testValidateUnitName() { - assertThat(validateUnitName("secondstotal")).isNotNull(); - assertThat(validateUnitName("total")).isNotNull(); - assertThat(validateUnitName("seconds_total")).isNotNull(); - assertThat(validateUnitName("_total")).isNotNull(); - assertThat(validateUnitName("")).isNotNull(); - - assertThat(validateUnitName("seconds")).isNull(); - assertThat(validateUnitName("2")).isNull(); + assertThat(PrometheusNaming.validateUnitName("secondstotal")).isNotNull(); + assertThat(PrometheusNaming.validateUnitName("total")).isNotNull(); + assertThat(PrometheusNaming.validateUnitName("seconds_total")).isNotNull(); + assertThat(PrometheusNaming.validateUnitName("_total")).isNotNull(); + assertThat(PrometheusNaming.validateUnitName("")).isNotNull(); + + assertThat(PrometheusNaming.validateUnitName("seconds")).isNull(); + assertThat(PrometheusNaming.validateUnitName("2")).isNull(); } @Test public void testSanitizeUnitName() { - assertThat(sanitizeUnitName("seconds")).isEqualTo("seconds"); - assertThat(sanitizeUnitName("seconds_total")).isEqualTo("seconds"); - assertThat(sanitizeUnitName("seconds_total_total")).isEqualTo("seconds"); - assertThat(sanitizeUnitName("m/s")).isEqualTo("m_s"); - assertThat(sanitizeUnitName("secondstotal")).isEqualTo("seconds"); - assertThat(sanitizeUnitName("2")).isEqualTo("2"); + assertThat(PrometheusNaming.sanitizeUnitName("seconds")).isEqualTo("seconds"); + assertThat(PrometheusNaming.sanitizeUnitName("seconds_total")).isEqualTo("seconds"); + assertThat(PrometheusNaming.sanitizeUnitName("seconds_total_total")).isEqualTo("seconds"); + assertThat(PrometheusNaming.sanitizeUnitName("m/s")).isEqualTo("m_s"); + assertThat(PrometheusNaming.sanitizeUnitName("secondstotal")).isEqualTo("seconds"); + assertThat(PrometheusNaming.sanitizeUnitName("2")).isEqualTo("2"); } @Test public void testInvalidUnitName1() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> sanitizeUnitName("total")); + .isThrownBy(() -> PrometheusNaming.sanitizeUnitName("total")); } @Test public void testInvalidUnitName2() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> sanitizeUnitName("_total")); + .isThrownBy(() -> PrometheusNaming.sanitizeUnitName("_total")); } @Test public void testInvalidUnitName3() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> sanitizeUnitName("%")); + .isThrownBy(() -> PrometheusNaming.sanitizeUnitName("%")); } @Test public void testEmptyUnitName() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> sanitizeUnitName("")); + .isThrownBy(() -> PrometheusNaming.sanitizeUnitName("")); } - @ParameterizedTest - @MethodSource("nameIsValid") - public void testLabelNameIsValidUtf8(String labelName, boolean utf8Valid) { - assertMetricName(labelName, utf8Valid); - assertLabelName(labelName, utf8Valid); - } - - 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"); - } - - static Stream nameIsValid() { - return Stream.of( - 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)); + @Test + void testValidMetricName() { + assertThat(PrometheusNaming.isValidMetricName("valid_metric_name")).isTrue(); + assertThat(PrometheusNaming.isValidMetricName("invalid_metric_name_total")).isFalse(); + assertThat(PrometheusNaming.isValidMetricName("0abc.def")).isFalse(); } - @ParameterizedTest - @MethodSource("escapeNameLegacyTestCases") - public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) { - assertThat(escapeName(input, escapingScheme)).isEqualTo(expected); + @Test + void testValidLabelName() { + assertThat(PrometheusNaming.isValidLabelName("valid_label_name")).isTrue(); + assertThat(PrometheusNaming.isValidLabelName("0invalid_label_name")).isFalse(); + assertThat(PrometheusNaming.isValidLabelName("invalid-label-name")).isFalse(); } - 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 + void testValidUnitName() { + assertThat(PrometheusNaming.isValidUnitName("seconds")).isTrue(); + assertThat(PrometheusNaming.isValidUnitName("seconds_total")).isFalse(); + assertThat(PrometheusNaming.isValidUnitName("2")).isTrue(); } } diff --git a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java index 3a96453e7..61f13d602 100644 --- a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java +++ b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java @@ -1,6 +1,6 @@ package io.prometheus.metrics.simpleclient.bridge; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNames.sanitizeMetricName; import static java.util.Objects.requireNonNull; import io.prometheus.client.Collector;