From 5b28758c2bc9881759c5cd9debb1a07fceda2bc7 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 24 Apr 2023 21:06:43 +0000 Subject: [PATCH 01/23] Append units to Prometheus metric names --- .../prometheus/PrometheusUnitsHelper.java | 154 ++++++++++++++++++ .../exporter/prometheus/Serializer.java | 13 +- .../exporter/prometheus/SerializerTest.java | 16 +- 3 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java new file mode 100644 index 00000000000..f6aa694e432 --- /dev/null +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -0,0 +1,154 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricDataType; +import java.util.Collections; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * A utility class that is used to maintain mappings between OTLP unit and Prometheus units. The + * list of mappings is adopted from OpenTelemetry + * Collector Contrib. + * + * @see OpenMetrics + * specification for units + * @see Prometheus best practices + * for units + */ +public final class PrometheusUnitsHelper { + + private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); + private static final String CHARACTERS_BETWEEN_BRACES_REGEX = + "\\{(.*?)}"; // matches all characters between {} + + private static final Map PROMETHEUS_UNIT_MAP = + Stream.of( + new String[][] { + // Time + {"d", "days"}, + {"h", "hours"}, + {"min", "minutes"}, + {"s", "seconds"}, + {"ms", "milliseconds"}, + {"us", "microseconds"}, + {"ns", "nanoseconds"}, + // Bytes + {"By", "bytes"}, + {"KiBy", "kibibytes"}, + {"MiBy", "mebibytes"}, + {"GiBy", "gibibytes"}, + {"TiBy", "tibibytes"}, + {"KBy", "kilobytes"}, + {"MBy", "megabytes"}, + {"GBy", "gigabytes"}, + {"TBy", "terabytes"}, + {"B", "bytes"}, + {"KB", "kilobytes"}, + {"MB", "megabytes"}, + {"GB", "gigabytes"}, + {"TB", "terabytes"}, + // SI + {"m", "meters"}, + {"V", "volts"}, + {"A", "amperes"}, + {"J", "joules"}, + {"W", "watts"}, + {"g", "grams"}, + // Misc + {"Cel", "celsius"}, + {"Hz", "hertz"}, + {"1", ""}, + {"%", "percent"}, + {"$", "dollars"} + }) + .collect( + Collectors.collectingAndThen( + Collectors.toMap( + keyValuePair -> keyValuePair[0], keyValuePair -> keyValuePair[1]), + Collections::unmodifiableMap)); + + // The map that translates the "per" unit + // Example: s => per second (singular) + private static final Map PROMETHEUS_PER_UNIT_MAP = + Stream.of( + new String[][] { + {"s", "second"}, + {"m", "minute"}, + {"h", "hour"}, + {"d", "day"}, + {"w", "week"}, + {"mo", "month"}, + {"y", "year"} + }) + .collect( + Collectors.collectingAndThen( + Collectors.toMap( + keyValuePair -> keyValuePair[0], keyValuePair -> keyValuePair[1]), + Collections::unmodifiableMap)); + + private PrometheusUnitsHelper() { + // Prevent object creation for utility classes + } + + /** + * A utility function that returns the equivalent Prometheus name for the provided metric's unit. + * + * @param metric The raw {@link MetricData} for which Prometheus metric unit needs to be computed. + * @return the computed Prometheus metric unit equivalent. + */ + public static String getEquivalentPrometheusUnit(MetricData metric) { + String unit = metric.getUnit(); + if (StringUtils.isNullOrEmpty(unit)) { + return unit; + } + unit = INVALID_CHARACTERS_PATTERN.matcher(unit).replaceAll("_").replaceAll("[_]{2,}", "_"); + + // special case + if (unit.equals("1") + && (metric.getType() == MetricDataType.DOUBLE_GAUGE + || metric.getType() == MetricDataType.LONG_GAUGE)) { + return "ratio"; + } + + // Drop units specified between curly braces + if (unit.matches(CHARACTERS_BETWEEN_BRACES_REGEX)) { + return removeUnitPortionInBrackets(unit); + } + + // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar + if (unit.contains("/")) { + return convertRateExpressedToPrometheusUnit(unit); + } + + // Converting abbreviated unit names to full names + return PROMETHEUS_UNIT_MAP.getOrDefault(unit, unit); + } + + private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { + String[] rateEntities = rateExpressedUnit.split("/", 2); + // Only convert rate expressed units if it's a valid expression + if (rateEntities[1].equals("")) { + return rateExpressedUnit; + } + return PROMETHEUS_PER_UNIT_MAP.getOrDefault(rateEntities[0], rateEntities[0]) + + "_per_" + + PROMETHEUS_PER_UNIT_MAP.getOrDefault(rateEntities[1], rateEntities[1]); + } + + private static String removeUnitPortionInBrackets(@Nonnull String unit) { + // This does not handle nested braces + return unit.replaceAll(CHARACTERS_BETWEEN_BRACES_REGEX, ""); + } +} diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java index 5053f9aedbd..a8a82a894cc 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java @@ -23,6 +23,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.internal.StringUtils; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -118,7 +119,7 @@ final Set write(Collection metrics, OutputStream output) thr continue; } PrometheusType prometheusType = PrometheusType.forMetric(metric); - String metricName = metricName(metric.getName(), prometheusType); + String metricName = metricName(metric, prometheusType); // Skip metrics which do not pass metricNameFilter if (!metricNameFilter.test(metricName)) { continue; @@ -650,8 +651,14 @@ static Collection getPoints(MetricData metricData) { return Collections.emptyList(); } - private static String metricName(String rawMetricName, PrometheusType type) { - String name = NameSanitizer.INSTANCE.apply(rawMetricName); + private static String metricName(MetricData rawMetric, PrometheusType type) { + String name = NameSanitizer.INSTANCE.apply(rawMetric.getName()); + String prometheusEquivalentUnit = PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric); + if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit) + && !name.contains(prometheusEquivalentUnit)) { + name = name + "_" + prometheusEquivalentUnit; + } + if (type == PrometheusType.COUNTER) { name = name + "_total"; } diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index 07db0ae02ae..2fe8b4406ea 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -372,10 +372,10 @@ void prometheus004() { + "non_monotonic_cumulative_long_sum{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672000\n" + "# TYPE double_gauge gauge\n" + "# HELP double_gauge unused\n" - + "double_gauge{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672000\n" + + "double_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672000\n" + "# TYPE long_gauge gauge\n" + "# HELP long_gauge unused\n" - + "long_gauge{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"lg\"} 5.0 1633950672000\n" + + "long_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"lg\"} 5.0 1633950672000\n" + "# TYPE summary summary\n" + "# HELP summary unused\n" + "summary_count{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"s\"} 5.0 1633950672000\n" @@ -394,10 +394,10 @@ void prometheus004() { + "cumulative_histogram_single_attribute_bucket{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"hs\",le=\"+Inf\"} 2.0 1633950672000\n" + "# TYPE double_gauge_no_attributes gauge\n" + "# HELP double_gauge_no_attributes unused\n" - + "double_gauge_no_attributes{otel_scope_name=\"full\",otel_scope_version=\"version\"} 7.0 1633950672000\n" + + "double_gauge_no_attributes_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\"} 7.0 1633950672000\n" + "# TYPE double_gauge_multiple_attributes gauge\n" + "# HELP double_gauge_multiple_attributes unused\n" - + "double_gauge_multiple_attributes{otel_scope_name=\"full\",otel_scope_version=\"version\",animal=\"bear\",type=\"dgma\"} 8.0 1633950672000\n"); + + "double_gauge_multiple_attributes_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",animal=\"bear\",type=\"dgma\"} 8.0 1633950672000\n"); } @Test @@ -439,10 +439,10 @@ void openMetrics() { + "non_monotonic_cumulative_long_sum{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672.000\n" + "# TYPE double_gauge gauge\n" + "# HELP double_gauge unused\n" - + "double_gauge{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672.000\n" + + "double_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672.000\n" + "# TYPE long_gauge gauge\n" + "# HELP long_gauge unused\n" - + "long_gauge{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"lg\"} 5.0 1633950672.000\n" + + "long_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"lg\"} 5.0 1633950672.000\n" + "# TYPE summary summary\n" + "# HELP summary unused\n" + "summary_count{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"s\"} 5.0 1633950672.000\n" @@ -461,10 +461,10 @@ void openMetrics() { + "cumulative_histogram_single_attribute_bucket{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"hs\",le=\"+Inf\"} 2.0 1633950672.000 # {span_id=\"0000000000000002\",trace_id=\"00000000000000000000000000000001\"} 4.0 0.001\n" + "# TYPE double_gauge_no_attributes gauge\n" + "# HELP double_gauge_no_attributes unused\n" - + "double_gauge_no_attributes{otel_scope_name=\"full\",otel_scope_version=\"version\"} 7.0 1633950672.000\n" + + "double_gauge_no_attributes_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\"} 7.0 1633950672.000\n" + "# TYPE double_gauge_multiple_attributes gauge\n" + "# HELP double_gauge_multiple_attributes unused\n" - + "double_gauge_multiple_attributes{otel_scope_name=\"full\",otel_scope_version=\"version\",animal=\"bear\",type=\"dgma\"} 8.0 1633950672.000\n" + + "double_gauge_multiple_attributes_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",animal=\"bear\",type=\"dgma\"} 8.0 1633950672.000\n" + "# EOF\n"); } From 5e5661f239ce7699a056c4ff5ce05f34bdc19b81 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 24 Apr 2023 23:14:29 +0000 Subject: [PATCH 02/23] Fix failing tests due to metric names update --- .../prometheus/PrometheusHttpServerTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java index 724fb67c5bd..7cbbcfc3e3a 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java @@ -236,7 +236,7 @@ void fetch_DuplicateMetrics() { InstrumentationScopeInfo.create("scope1"), "foo", "description1", - "unit1", + "unit", ImmutableSumData.create( /* isMonotonic= */ true, AggregationTemporality.CUMULATIVE, @@ -248,7 +248,7 @@ void fetch_DuplicateMetrics() { InstrumentationScopeInfo.create("scope2"), "foo", "description2", - "unit2", + "unit", ImmutableSumData.create( /* isMonotonic= */ true, AggregationTemporality.CUMULATIVE, @@ -259,9 +259,9 @@ void fetch_DuplicateMetrics() { ImmutableMetricData.createLongGauge( resource, InstrumentationScopeInfo.create("scope3"), - "foo_total", - "unused", + "foo_unit_total", "unused", + "unit", ImmutableGaugeData.create( Collections.singletonList( ImmutableLongPointData.create(123, 456, Attributes.empty(), 3)))))); @@ -283,13 +283,13 @@ void fetch_DuplicateMetrics() { + "otel_scope_info{otel_scope_name=\"scope2\"} 1\n" + "# TYPE foo_total counter\n" + "# HELP foo_total description1\n" - + "foo_total{otel_scope_name=\"scope1\"} 1.0 0\n" - + "foo_total{otel_scope_name=\"scope2\"} 2.0 0\n"); + + "foo_unit_total{otel_scope_name=\"scope1\"} 1.0 0\n" + + "foo_unit_total{otel_scope_name=\"scope2\"} 2.0 0\n"); // Validate conflict warning message assertThat(logs.getEvents()).hasSize(1); logs.assertContains( - "Metric conflict(s) detected. Multiple metrics with same name but different type: [foo_total]"); + "Metric conflict(s) detected. Multiple metrics with same name but different type: [foo_unit_total]"); // Make another request and confirm warning is only logged once client.get("/").aggregate().join(); From bccc0443e5474fc37fb02d9f4ca3565e22555fbe Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 24 Apr 2023 23:16:22 +0000 Subject: [PATCH 03/23] Refactor unit conversion function The unit conversion function that converts OTLP units to Prometheus now takes in the rawUnitName and PrometheusType instead of OTLP metric data. This leads to more accurate conversion of OTLP units to Prometheus units since the unit type representative of types recognized by Prometheus --- .../prometheus/PrometheusUnitsHelper.java | 39 ++++++++++--------- .../exporter/prometheus/Serializer.java | 3 +- .../prometheus/PrometheusUnitsHelperTest.java | 8 ++++ .../exporter/prometheus/SerializerTest.java | 8 ++-- 4 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index f6aa694e432..183d0a5e342 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -6,8 +6,6 @@ package io.opentelemetry.exporter.prometheus; import io.opentelemetry.api.internal.StringUtils; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.data.MetricDataType; import java.util.Collections; import java.util.Map; import java.util.regex.Pattern; @@ -103,37 +101,42 @@ private PrometheusUnitsHelper() { } /** - * A utility function that returns the equivalent Prometheus name for the provided metric's unit. + * A utility function that returns the equivalent Prometheus name for the provided OTLP metric + * unit. * - * @param metric The raw {@link MetricData} for which Prometheus metric unit needs to be computed. - * @return the computed Prometheus metric unit equivalent. + * @param rawMetricUnitName The raw metric unit for which Prometheus metric unit needs to be + * computed. + * @param metricType The {@link PrometheusType} of the metric whose unit is to be converted. + * @return the computed Prometheus metric unit equivalent of the OTLP metric unit. */ - public static String getEquivalentPrometheusUnit(MetricData metric) { - String unit = metric.getUnit(); - if (StringUtils.isNullOrEmpty(unit)) { - return unit; + public static String getEquivalentPrometheusUnit( + String rawMetricUnitName, PrometheusType metricType) { + if (StringUtils.isNullOrEmpty(rawMetricUnitName)) { + return rawMetricUnitName; } - unit = INVALID_CHARACTERS_PATTERN.matcher(unit).replaceAll("_").replaceAll("[_]{2,}", "_"); + rawMetricUnitName = + INVALID_CHARACTERS_PATTERN + .matcher(rawMetricUnitName) + .replaceAll("_") + .replaceAll("[_]{2,}", "_"); // special case - if (unit.equals("1") - && (metric.getType() == MetricDataType.DOUBLE_GAUGE - || metric.getType() == MetricDataType.LONG_GAUGE)) { + if (rawMetricUnitName.equals("1") && metricType == PrometheusType.GAUGE) { return "ratio"; } // Drop units specified between curly braces - if (unit.matches(CHARACTERS_BETWEEN_BRACES_REGEX)) { - return removeUnitPortionInBrackets(unit); + if (rawMetricUnitName.matches(CHARACTERS_BETWEEN_BRACES_REGEX)) { + return removeUnitPortionInBrackets(rawMetricUnitName); } // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar - if (unit.contains("/")) { - return convertRateExpressedToPrometheusUnit(unit); + if (rawMetricUnitName.contains("/")) { + return convertRateExpressedToPrometheusUnit(rawMetricUnitName); } // Converting abbreviated unit names to full names - return PROMETHEUS_UNIT_MAP.getOrDefault(unit, unit); + return PROMETHEUS_UNIT_MAP.getOrDefault(rawMetricUnitName, rawMetricUnitName); } private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java index a8a82a894cc..90fc67281c2 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java @@ -653,7 +653,8 @@ static Collection getPoints(MetricData metricData) { private static String metricName(MetricData rawMetric, PrometheusType type) { String name = NameSanitizer.INSTANCE.apply(rawMetric.getName()); - String prometheusEquivalentUnit = PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric); + String prometheusEquivalentUnit = + PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit(), type); if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit) && !name.contains(prometheusEquivalentUnit)) { name = name + "_" + prometheusEquivalentUnit; diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java new file mode 100644 index 00000000000..c4cfbe12a91 --- /dev/null +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -0,0 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +class PrometheusUnitsHelperTest {} diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index 2fe8b4406ea..e0b17f055ff 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -363,13 +363,13 @@ void prometheus004() { + "monotonic_cumulative_double_sum_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcds\"} 5.0 1633950672000\n" + "# TYPE non_monotonic_cumulative_double_sum gauge\n" + "# HELP non_monotonic_cumulative_double_sum description\n" - + "non_monotonic_cumulative_double_sum{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672000\n" + + "non_monotonic_cumulative_double_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672000\n" + "# TYPE monotonic_cumulative_long_sum_total counter\n" + "# HELP monotonic_cumulative_long_sum_total unused\n" + "monotonic_cumulative_long_sum_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcls\"} 5.0 1633950672000\n" + "# TYPE non_monotonic_cumulative_long_sum gauge\n" + "# HELP non_monotonic_cumulative_long_sum unused\n" - + "non_monotonic_cumulative_long_sum{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672000\n" + + "non_monotonic_cumulative_long_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672000\n" + "# TYPE double_gauge gauge\n" + "# HELP double_gauge unused\n" + "double_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672000\n" @@ -430,13 +430,13 @@ void openMetrics() { + "monotonic_cumulative_double_sum_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcds\"} 5.0 1633950672.000\n" + "# TYPE non_monotonic_cumulative_double_sum gauge\n" + "# HELP non_monotonic_cumulative_double_sum description\n" - + "non_monotonic_cumulative_double_sum{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672.000\n" + + "non_monotonic_cumulative_double_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672.000\n" + "# TYPE monotonic_cumulative_long_sum counter\n" + "# HELP monotonic_cumulative_long_sum unused\n" + "monotonic_cumulative_long_sum_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcls\"} 5.0 1633950672.000\n" + "# TYPE non_monotonic_cumulative_long_sum gauge\n" + "# HELP non_monotonic_cumulative_long_sum unused\n" - + "non_monotonic_cumulative_long_sum{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672.000\n" + + "non_monotonic_cumulative_long_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672.000\n" + "# TYPE double_gauge gauge\n" + "# HELP double_gauge unused\n" + "double_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672.000\n" From cb3956b778fd62e1786cdffaf4235b592134a639 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 25 Apr 2023 20:05:35 +0000 Subject: [PATCH 04/23] Fix edge cases for unit conversion --- .../prometheus/PrometheusUnitsHelper.java | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 183d0a5e342..fbce6be24ae 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -11,7 +11,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.Nonnull; /** * A utility class that is used to maintain mappings between OTLP unit and Prometheus units. The @@ -27,7 +26,7 @@ */ public final class PrometheusUnitsHelper { - private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); + private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); private static final String CHARACTERS_BETWEEN_BRACES_REGEX = "\\{(.*?)}"; // matches all characters between {} @@ -114,44 +113,57 @@ public static String getEquivalentPrometheusUnit( if (StringUtils.isNullOrEmpty(rawMetricUnitName)) { return rawMetricUnitName; } - rawMetricUnitName = - INVALID_CHARACTERS_PATTERN - .matcher(rawMetricUnitName) - .replaceAll("_") - .replaceAll("[_]{2,}", "_"); // special case if (rawMetricUnitName.equals("1") && metricType == PrometheusType.GAUGE) { return "ratio"; } + String convertedMetricUnitName = rawMetricUnitName; // Drop units specified between curly braces - if (rawMetricUnitName.matches(CHARACTERS_BETWEEN_BRACES_REGEX)) { - return removeUnitPortionInBrackets(rawMetricUnitName); - } + convertedMetricUnitName = removeUnitPortionInBrackets(convertedMetricUnitName); // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar - if (rawMetricUnitName.contains("/")) { - return convertRateExpressedToPrometheusUnit(rawMetricUnitName); + if (convertedMetricUnitName.contains("/")) { + convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); } // Converting abbreviated unit names to full names - return PROMETHEUS_UNIT_MAP.getOrDefault(rawMetricUnitName, rawMetricUnitName); + return cleanUpString( + PROMETHEUS_UNIT_MAP.getOrDefault(convertedMetricUnitName, convertedMetricUnitName)); } private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { String[] rateEntities = rateExpressedUnit.split("/", 2); + if (rateEntities.length < 1) { + return rateExpressedUnit; + } // Only convert rate expressed units if it's a valid expression if (rateEntities[1].equals("")) { return rateExpressedUnit; } - return PROMETHEUS_PER_UNIT_MAP.getOrDefault(rateEntities[0], rateEntities[0]) + return PROMETHEUS_UNIT_MAP.getOrDefault(rateEntities[0], rateEntities[0]) + "_per_" + PROMETHEUS_PER_UNIT_MAP.getOrDefault(rateEntities[1], rateEntities[1]); } - private static String removeUnitPortionInBrackets(@Nonnull String unit) { + private static String removeUnitPortionInBrackets(String unit) { // This does not handle nested braces return unit.replaceAll(CHARACTERS_BETWEEN_BRACES_REGEX, ""); } + + /** + * Replaces all characters that are not a letter or a digit with '_' to make the resulting string + * Prometheus compliant. This method also removes leading and trailing underscores. + * + * @param string The string input that needs to be made Prometheus compliant. + * @return the cleaned-up Prometheus compliant string. + */ + private static String cleanUpString(String string) { + String prometheusCompliant = + INVALID_CHARACTERS_PATTERN.matcher(string).replaceAll("_").replaceAll("[_]{2,}", "_"); + prometheusCompliant = prometheusCompliant.replaceAll("_+$", ""); // remove trailing underscore + prometheusCompliant = prometheusCompliant.replaceAll("^_+", ""); // remove leading underscore + return prometheusCompliant; + } } From d5e7c40ad28ab64cccedc6fdd2900e31214bb549 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 25 Apr 2023 20:06:11 +0000 Subject: [PATCH 05/23] Add tests for unit conversion --- .../prometheus/PrometheusUnitsHelperTest.java | 116 +++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java index c4cfbe12a91..c9574ccc8be 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -5,4 +5,118 @@ package io.opentelemetry.exporter.prometheus; -class PrometheusUnitsHelperTest {} +import static org.junit.jupiter.api.Assertions.assertEquals; + +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 PrometheusUnitsHelperTest { + + @ParameterizedTest + @MethodSource("providePrometheusOTLPUnitEquivalentPairs") + public void testPrometheusUnitEquivalency( + String otlpUnit, String prometheusUnit, PrometheusType metricType) { + assertEquals( + prometheusUnit, PrometheusUnitsHelper.getEquivalentPrometheusUnit(otlpUnit, metricType)); + } + + private static Stream providePrometheusOTLPUnitEquivalentPairs() { + return Stream.of( + // Simple expansion - storage Bytes + Arguments.of("By", "bytes", PrometheusType.GAUGE), + Arguments.of("By", "bytes", PrometheusType.COUNTER), + Arguments.of("By", "bytes", PrometheusType.SUMMARY), + Arguments.of("By", "bytes", PrometheusType.HISTOGRAM), + Arguments.of("B", "bytes", PrometheusType.GAUGE), + Arguments.of("B", "bytes", PrometheusType.COUNTER), + Arguments.of("B", "bytes", PrometheusType.SUMMARY), + Arguments.of("B", "bytes", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit + Arguments.of("s", "seconds", PrometheusType.GAUGE), + Arguments.of("s", "seconds", PrometheusType.COUNTER), + Arguments.of("s", "seconds", PrometheusType.SUMMARY), + Arguments.of("s", "seconds", PrometheusType.HISTOGRAM), + // Unit not found - Case sensitive + Arguments.of("S", "S", PrometheusType.GAUGE), + Arguments.of("S", "S", PrometheusType.COUNTER), + Arguments.of("S", "S", PrometheusType.SUMMARY), + Arguments.of("S", "S", PrometheusType.HISTOGRAM), + // Special case - 1 + Arguments.of("1", "ratio", PrometheusType.GAUGE), + Arguments.of("1", "", PrometheusType.COUNTER), + Arguments.of("1", "", PrometheusType.SUMMARY), + Arguments.of("1", "", PrometheusType.HISTOGRAM), + // Special Case - Drop metric units in {} + Arguments.of("{packets}", "", PrometheusType.GAUGE), + Arguments.of("{packets}", "", PrometheusType.COUNTER), + Arguments.of("{packets}", "", PrometheusType.SUMMARY), + Arguments.of("{packets}", "", PrometheusType.HISTOGRAM), + // Special Case - Dropped metric units only in {} + Arguments.of("{packets}m", "meters", PrometheusType.GAUGE), + Arguments.of("{packets}m", "meters", PrometheusType.COUNTER), + Arguments.of("{packets}m", "meters", PrometheusType.SUMMARY), + Arguments.of("{packets}m", "meters", PrometheusType.HISTOGRAM), + // Special Case - Dropped metric units with 'per' unit handling applicable + Arguments.of("{scanned}/{returned}", "", PrometheusType.GAUGE), + Arguments.of("{scanned}/{returned}", "", PrometheusType.COUNTER), + Arguments.of("{scanned}/{returned}", "", PrometheusType.SUMMARY), + Arguments.of("{scanned}/{returned}", "", PrometheusType.HISTOGRAM), + // Special Case - Dropped metric units with 'per' unit handling applicable + Arguments.of("{objects}/s", "per_second", PrometheusType.GAUGE), + Arguments.of("{objects}/s", "per_second", PrometheusType.COUNTER), + Arguments.of("{objects}/s", "per_second", PrometheusType.SUMMARY), + Arguments.of("{objects}/s", "per_second", PrometheusType.HISTOGRAM), + // Units expressing rate - 'per' units + Arguments.of("km/h", "km_per_hour", PrometheusType.GAUGE), + Arguments.of("km/h", "km_per_hour", PrometheusType.COUNTER), + Arguments.of("km/h", "km_per_hour", PrometheusType.SUMMARY), + Arguments.of("km/h", "km_per_hour", PrometheusType.HISTOGRAM), + // Units expressing rate - 'per' units, both units expanded + Arguments.of("m/s", "meters_per_second", PrometheusType.GAUGE), + Arguments.of("m/s", "meters_per_second", PrometheusType.COUNTER), + Arguments.of("m/s", "meters_per_second", PrometheusType.SUMMARY), + Arguments.of("m/s", "meters_per_second", PrometheusType.HISTOGRAM), + // Misc - unsupported symbols are replaced with _ + Arguments.of("°F", "F", PrometheusType.GAUGE), + Arguments.of("°F", "F", PrometheusType.COUNTER), + Arguments.of("°F", "F", PrometheusType.SUMMARY), + Arguments.of("°F", "F", PrometheusType.HISTOGRAM), + // Misc - multiple unsupported symbols are replaced with single _ + Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.GAUGE), + Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.COUNTER), + Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.SUMMARY), + Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.HISTOGRAM), + // Misc - unsupported runes in 'per' units + Arguments.of("__test $/°C", "test_per_C", PrometheusType.GAUGE), + Arguments.of("__test $/°C", "test_per_C", PrometheusType.COUNTER), + Arguments.of("__test $/°C", "test_per_C", PrometheusType.SUMMARY), + Arguments.of("__test $/°C", "test_per_C", PrometheusType.HISTOGRAM), + // Misc - Special supported symbols + Arguments.of("$", "dollars", PrometheusType.GAUGE), + Arguments.of("$", "dollars", PrometheusType.COUNTER), + Arguments.of("$", "dollars", PrometheusType.SUMMARY), + Arguments.of("$", "dollars", PrometheusType.HISTOGRAM), + // Empty Units - whitespace + Arguments.of("\t", "", PrometheusType.GAUGE), + Arguments.of("\t", "", PrometheusType.COUNTER), + Arguments.of("\t", "", PrometheusType.SUMMARY), + Arguments.of("\t", "", PrometheusType.HISTOGRAM), + // Null unit + Arguments.of(null, null, PrometheusType.GAUGE), + Arguments.of(null, null, PrometheusType.COUNTER), + Arguments.of(null, null, PrometheusType.SUMMARY), + Arguments.of(null, null, PrometheusType.HISTOGRAM), + // Misc - unit cleanup - no case match special char + Arguments.of("$1000", "1000", PrometheusType.GAUGE), + Arguments.of("$1000", "1000", PrometheusType.COUNTER), + Arguments.of("$1000", "1000", PrometheusType.SUMMARY), + Arguments.of("$1000", "1000", PrometheusType.HISTOGRAM), + // Misc - unit cleanup - no case match whitespace + Arguments.of("a b !!", "a_b", PrometheusType.GAUGE), + Arguments.of("a b !!", "a_b", PrometheusType.COUNTER), + Arguments.of("a b !!", "a_b", PrometheusType.SUMMARY), + Arguments.of("a b !!", "a_b", PrometheusType.HISTOGRAM)); + } +} From 6e7b6012f825b4ff4b431cd611eb233c5d8c2d27 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 25 Apr 2023 20:18:46 +0000 Subject: [PATCH 06/23] Fix naming issues to be compliant with the stylecheck --- .../exporter/prometheus/PrometheusUnitsHelperTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java index c9574ccc8be..bfe442e2f14 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -15,14 +15,14 @@ class PrometheusUnitsHelperTest { @ParameterizedTest - @MethodSource("providePrometheusOTLPUnitEquivalentPairs") + @MethodSource("providePrometheusOTelUnitEquivalentPairs") public void testPrometheusUnitEquivalency( String otlpUnit, String prometheusUnit, PrometheusType metricType) { assertEquals( prometheusUnit, PrometheusUnitsHelper.getEquivalentPrometheusUnit(otlpUnit, metricType)); } - private static Stream providePrometheusOTLPUnitEquivalentPairs() { + private static Stream providePrometheusOTelUnitEquivalentPairs() { return Stream.of( // Simple expansion - storage Bytes Arguments.of("By", "bytes", PrometheusType.GAUGE), From 6c1734489d3ee5bd40546a014850f0bbeeeb43d8 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 25 Apr 2023 20:46:34 +0000 Subject: [PATCH 07/23] Remove redundant validity check --- .../exporter/prometheus/PrometheusUnitsHelper.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index fbce6be24ae..908aa344d3f 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -135,9 +135,6 @@ public static String getEquivalentPrometheusUnit( private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { String[] rateEntities = rateExpressedUnit.split("/", 2); - if (rateEntities.length < 1) { - return rateExpressedUnit; - } // Only convert rate expressed units if it's a valid expression if (rateEntities[1].equals("")) { return rateExpressedUnit; From 05259695fe958b71f306980003f5571b4e6901df Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Wed, 26 Apr 2023 15:20:15 +0000 Subject: [PATCH 08/23] Move 'per' unit check inside the coversion method --- .../exporter/prometheus/PrometheusUnitsHelper.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 908aa344d3f..f774e5d9e7e 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -124,9 +124,7 @@ public static String getEquivalentPrometheusUnit( convertedMetricUnitName = removeUnitPortionInBrackets(convertedMetricUnitName); // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar - if (convertedMetricUnitName.contains("/")) { - convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); - } + convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); // Converting abbreviated unit names to full names return cleanUpString( @@ -134,6 +132,9 @@ public static String getEquivalentPrometheusUnit( } private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { + if (!rateExpressedUnit.contains("/")) { + return rateExpressedUnit; + } String[] rateEntities = rateExpressedUnit.split("/", 2); // Only convert rate expressed units if it's a valid expression if (rateEntities[1].equals("")) { From 6f20800f536e96ddd44bc8fa7c1830dd83c4be05 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Wed, 26 Apr 2023 15:47:17 +0000 Subject: [PATCH 09/23] Move abbreviated unit mappings: Map to Switch-Case The static Map would be too memory intensive compared to a switch-case and does not give any performance boost in its comparison. --- .../prometheus/PrometheusUnitsHelper.java | 187 +++++++++++------- 1 file changed, 113 insertions(+), 74 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index f774e5d9e7e..1e8914786de 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -6,11 +6,7 @@ package io.opentelemetry.exporter.prometheus; import io.opentelemetry.api.internal.StringUtils; -import java.util.Collections; -import java.util.Map; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * A utility class that is used to maintain mappings between OTLP unit and Prometheus units. The @@ -30,71 +26,6 @@ public final class PrometheusUnitsHelper { private static final String CHARACTERS_BETWEEN_BRACES_REGEX = "\\{(.*?)}"; // matches all characters between {} - private static final Map PROMETHEUS_UNIT_MAP = - Stream.of( - new String[][] { - // Time - {"d", "days"}, - {"h", "hours"}, - {"min", "minutes"}, - {"s", "seconds"}, - {"ms", "milliseconds"}, - {"us", "microseconds"}, - {"ns", "nanoseconds"}, - // Bytes - {"By", "bytes"}, - {"KiBy", "kibibytes"}, - {"MiBy", "mebibytes"}, - {"GiBy", "gibibytes"}, - {"TiBy", "tibibytes"}, - {"KBy", "kilobytes"}, - {"MBy", "megabytes"}, - {"GBy", "gigabytes"}, - {"TBy", "terabytes"}, - {"B", "bytes"}, - {"KB", "kilobytes"}, - {"MB", "megabytes"}, - {"GB", "gigabytes"}, - {"TB", "terabytes"}, - // SI - {"m", "meters"}, - {"V", "volts"}, - {"A", "amperes"}, - {"J", "joules"}, - {"W", "watts"}, - {"g", "grams"}, - // Misc - {"Cel", "celsius"}, - {"Hz", "hertz"}, - {"1", ""}, - {"%", "percent"}, - {"$", "dollars"} - }) - .collect( - Collectors.collectingAndThen( - Collectors.toMap( - keyValuePair -> keyValuePair[0], keyValuePair -> keyValuePair[1]), - Collections::unmodifiableMap)); - - // The map that translates the "per" unit - // Example: s => per second (singular) - private static final Map PROMETHEUS_PER_UNIT_MAP = - Stream.of( - new String[][] { - {"s", "second"}, - {"m", "minute"}, - {"h", "hour"}, - {"d", "day"}, - {"w", "week"}, - {"mo", "month"}, - {"y", "year"} - }) - .collect( - Collectors.collectingAndThen( - Collectors.toMap( - keyValuePair -> keyValuePair[0], keyValuePair -> keyValuePair[1]), - Collections::unmodifiableMap)); - private PrometheusUnitsHelper() { // Prevent object creation for utility classes } @@ -127,8 +58,7 @@ public static String getEquivalentPrometheusUnit( convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); // Converting abbreviated unit names to full names - return cleanUpString( - PROMETHEUS_UNIT_MAP.getOrDefault(convertedMetricUnitName, convertedMetricUnitName)); + return cleanUpString(getPrometheusUnit(convertedMetricUnitName)); } private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { @@ -140,9 +70,7 @@ private static String convertRateExpressedToPrometheusUnit(String rateExpressedU if (rateEntities[1].equals("")) { return rateExpressedUnit; } - return PROMETHEUS_UNIT_MAP.getOrDefault(rateEntities[0], rateEntities[0]) - + "_per_" - + PROMETHEUS_PER_UNIT_MAP.getOrDefault(rateEntities[1], rateEntities[1]); + return getPrometheusUnit(rateEntities[0]) + "_per_" + getPrometheusPerUnit(rateEntities[1]); } private static String removeUnitPortionInBrackets(String unit) { @@ -164,4 +92,115 @@ private static String cleanUpString(String string) { prometheusCompliant = prometheusCompliant.replaceAll("^_+", ""); // remove leading underscore return prometheusCompliant; } + + /** + * This method retrieves the expanded Prometheus unit name for known abbreviations. + * + * @param unitAbbreviation The unit that name that needs to be expanded/converted to Prometheus + * units. + * @return The expanded/converted unit name if known, otherwise returns the input unit name as-is. + */ + private static String getPrometheusUnit(String unitAbbreviation) { + switch (unitAbbreviation) { + // Time + case "d": + return "days"; + case "h": + return "hours"; + case "min": + return "minutes"; + case "s": + return "seconds"; + case "ms": + return "milliseconds"; + case "us": + return "microseconds"; + case "ns": + return "nanoseconds"; + // Bytes + case "By": + return "bytes"; + case "KiBy": + return "kibibytes"; + case "MiBy": + return "mebibytes"; + case "GiBy": + return "gibibytes"; + case "TiBy": + return "tibibytes"; + case "KBy": + return "kilobytes"; + case "MBy": + return "megabytes"; + case "GBy": + return "gigabytes"; + case "TBy": + return "terabytes"; + case "B": + return "bytes"; + case "KB": + return "kilobytes"; + case "MB": + return "megabytes"; + case "GB": + return "gigabytes"; + case "TB": + return "terabytes"; + // SI + case "m": + return "meters"; + case "V": + return "volts"; + case "A": + return "amperes"; + case "J": + return "joules"; + case "W": + return "watts"; + case "g": + return "grams"; + // Misc + case "Cel": + return "celsius"; + case "Hz": + return "hertz"; + case "1": + return ""; + case "%": + return "percent"; + case "$": + return "dollars"; + default: + return unitAbbreviation; + } + } + + /** + * This method retrieves the expanded Prometheus unit name to be used with "per" units for known + * units. For example: s => per second (singular) + * + * @param perUnitAbbreviation The unit abbreviation used in a 'per' unit. + * @return The expanded unit equivalent to be used in 'per' unit if the input is a known unit, + * otherwise returns the input as-is. + */ + private static String getPrometheusPerUnit(String perUnitAbbreviation) { + switch (perUnitAbbreviation) { + case "s": + return "second"; + case "m": + return "minute"; + case "h": + return "hour"; + case "d": + return "day"; + case "w": + return "week"; + case "mo": + return "month"; + case "y": + return "year"; + default: + return perUnitAbbreviation; + } + } } From f2fea06a0a49826438a5d3b1c4eec757ce8e4ba7 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Wed, 26 Apr 2023 17:08:19 +0000 Subject: [PATCH 10/23] Add unit tests to cover all expansion cases --- .../prometheus/PrometheusUnitsHelperTest.java | 166 ++++++++++++++++-- 1 file changed, 151 insertions(+), 15 deletions(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java index bfe442e2f14..b95f7d52b56 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -33,11 +33,112 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { Arguments.of("B", "bytes", PrometheusType.COUNTER), Arguments.of("B", "bytes", PrometheusType.SUMMARY), Arguments.of("B", "bytes", PrometheusType.HISTOGRAM), - // Simple expansion - Time unit + // Simple expansion - storage KB + Arguments.of("KB", "kilobytes", PrometheusType.GAUGE), + Arguments.of("KB", "kilobytes", PrometheusType.COUNTER), + Arguments.of("KB", "kilobytes", PrometheusType.SUMMARY), + Arguments.of("KB", "kilobytes", PrometheusType.HISTOGRAM), + Arguments.of("KBy", "kilobytes", PrometheusType.GAUGE), + Arguments.of("KBy", "kilobytes", PrometheusType.COUNTER), + Arguments.of("KBy", "kilobytes", PrometheusType.SUMMARY), + Arguments.of("KBy", "kilobytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage MB + Arguments.of("MB", "megabytes", PrometheusType.GAUGE), + Arguments.of("MB", "megabytes", PrometheusType.COUNTER), + Arguments.of("MB", "megabytes", PrometheusType.SUMMARY), + Arguments.of("MB", "megabytes", PrometheusType.HISTOGRAM), + Arguments.of("MBy", "megabytes", PrometheusType.GAUGE), + Arguments.of("MBy", "megabytes", PrometheusType.COUNTER), + Arguments.of("MBy", "megabytes", PrometheusType.SUMMARY), + Arguments.of("MBy", "megabytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage GB + Arguments.of("GB", "gigabytes", PrometheusType.GAUGE), + Arguments.of("GB", "gigabytes", PrometheusType.COUNTER), + Arguments.of("GB", "gigabytes", PrometheusType.SUMMARY), + Arguments.of("GB", "gigabytes", PrometheusType.HISTOGRAM), + Arguments.of("GBy", "gigabytes", PrometheusType.GAUGE), + Arguments.of("GBy", "gigabytes", PrometheusType.COUNTER), + Arguments.of("GBy", "gigabytes", PrometheusType.SUMMARY), + Arguments.of("GBy", "gigabytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage TB + Arguments.of("TB", "terabytes", PrometheusType.GAUGE), + Arguments.of("TB", "terabytes", PrometheusType.COUNTER), + Arguments.of("TB", "terabytes", PrometheusType.SUMMARY), + Arguments.of("TB", "terabytes", PrometheusType.HISTOGRAM), + Arguments.of("TBy", "terabytes", PrometheusType.GAUGE), + Arguments.of("TBy", "terabytes", PrometheusType.COUNTER), + Arguments.of("TBy", "terabytes", PrometheusType.SUMMARY), + Arguments.of("TBy", "terabytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage KiBy + Arguments.of("KiBy", "kibibytes", PrometheusType.GAUGE), + Arguments.of("KiBy", "kibibytes", PrometheusType.COUNTER), + Arguments.of("KiBy", "kibibytes", PrometheusType.SUMMARY), + Arguments.of("KiBy", "kibibytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage MiBy + Arguments.of("MiBy", "mebibytes", PrometheusType.GAUGE), + Arguments.of("MiBy", "mebibytes", PrometheusType.COUNTER), + Arguments.of("MiBy", "mebibytes", PrometheusType.SUMMARY), + Arguments.of("MiBy", "mebibytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage GiBy + Arguments.of("GiBy", "gibibytes", PrometheusType.GAUGE), + Arguments.of("GiBy", "gibibytes", PrometheusType.COUNTER), + Arguments.of("GiBy", "gibibytes", PrometheusType.SUMMARY), + Arguments.of("GiBy", "gibibytes", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit d + Arguments.of("d", "days", PrometheusType.GAUGE), + Arguments.of("d", "days", PrometheusType.COUNTER), + Arguments.of("d", "days", PrometheusType.SUMMARY), + Arguments.of("d", "days", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit h + Arguments.of("h", "hours", PrometheusType.GAUGE), + Arguments.of("h", "hours", PrometheusType.COUNTER), + Arguments.of("h", "hours", PrometheusType.SUMMARY), + Arguments.of("h", "hours", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit s Arguments.of("s", "seconds", PrometheusType.GAUGE), Arguments.of("s", "seconds", PrometheusType.COUNTER), Arguments.of("s", "seconds", PrometheusType.SUMMARY), Arguments.of("s", "seconds", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit ms + Arguments.of("ms", "milliseconds", PrometheusType.GAUGE), + Arguments.of("ms", "milliseconds", PrometheusType.COUNTER), + Arguments.of("ms", "milliseconds", PrometheusType.SUMMARY), + Arguments.of("ms", "milliseconds", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit us + Arguments.of("us", "microseconds", PrometheusType.GAUGE), + Arguments.of("us", "microseconds", PrometheusType.COUNTER), + Arguments.of("us", "microseconds", PrometheusType.SUMMARY), + Arguments.of("us", "microseconds", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit ns + Arguments.of("ns", "nanoseconds", PrometheusType.GAUGE), + Arguments.of("ns", "nanoseconds", PrometheusType.COUNTER), + Arguments.of("ns", "nanoseconds", PrometheusType.SUMMARY), + Arguments.of("ns", "nanoseconds", PrometheusType.HISTOGRAM), + // Simple expansion - Time unit min + Arguments.of("min", "minutes", PrometheusType.GAUGE), + Arguments.of("min", "minutes", PrometheusType.COUNTER), + Arguments.of("min", "minutes", PrometheusType.SUMMARY), + Arguments.of("min", "minutes", PrometheusType.HISTOGRAM), + // Simple expansion - special symbol - % + Arguments.of("%", "percent", PrometheusType.GAUGE), + Arguments.of("%", "percent", PrometheusType.COUNTER), + Arguments.of("%", "percent", PrometheusType.SUMMARY), + Arguments.of("%", "percent", PrometheusType.HISTOGRAM), + // Simple expansion - special symbols - $ + Arguments.of("$", "dollars", PrometheusType.GAUGE), + Arguments.of("$", "dollars", PrometheusType.COUNTER), + Arguments.of("$", "dollars", PrometheusType.SUMMARY), + Arguments.of("$", "dollars", PrometheusType.HISTOGRAM), + // Simple expansion - frequency + Arguments.of("Hz", "hertz", PrometheusType.GAUGE), + Arguments.of("Hz", "hertz", PrometheusType.COUNTER), + Arguments.of("Hz", "hertz", PrometheusType.SUMMARY), + Arguments.of("Hz", "hertz", PrometheusType.HISTOGRAM), + // Simple expansion - temperature + Arguments.of("Cel", "celsius", PrometheusType.GAUGE), + Arguments.of("Cel", "celsius", PrometheusType.COUNTER), + Arguments.of("Cel", "celsius", PrometheusType.SUMMARY), + Arguments.of("Cel", "celsius", PrometheusType.HISTOGRAM), // Unit not found - Case sensitive Arguments.of("S", "S", PrometheusType.GAUGE), Arguments.of("S", "S", PrometheusType.COUNTER), @@ -54,10 +155,10 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { Arguments.of("{packets}", "", PrometheusType.SUMMARY), Arguments.of("{packets}", "", PrometheusType.HISTOGRAM), // Special Case - Dropped metric units only in {} - Arguments.of("{packets}m", "meters", PrometheusType.GAUGE), - Arguments.of("{packets}m", "meters", PrometheusType.COUNTER), - Arguments.of("{packets}m", "meters", PrometheusType.SUMMARY), - Arguments.of("{packets}m", "meters", PrometheusType.HISTOGRAM), + Arguments.of("{packets}V", "volts", PrometheusType.GAUGE), + Arguments.of("{packets}V", "volts", PrometheusType.COUNTER), + Arguments.of("{packets}V", "volts", PrometheusType.SUMMARY), + Arguments.of("{packets}V", "volts", PrometheusType.HISTOGRAM), // Special Case - Dropped metric units with 'per' unit handling applicable Arguments.of("{scanned}/{returned}", "", PrometheusType.GAUGE), Arguments.of("{scanned}/{returned}", "", PrometheusType.COUNTER), @@ -68,16 +169,56 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { Arguments.of("{objects}/s", "per_second", PrometheusType.COUNTER), Arguments.of("{objects}/s", "per_second", PrometheusType.SUMMARY), Arguments.of("{objects}/s", "per_second", PrometheusType.HISTOGRAM), - // Units expressing rate - 'per' units - Arguments.of("km/h", "km_per_hour", PrometheusType.GAUGE), - Arguments.of("km/h", "km_per_hour", PrometheusType.COUNTER), - Arguments.of("km/h", "km_per_hour", PrometheusType.SUMMARY), - Arguments.of("km/h", "km_per_hour", PrometheusType.HISTOGRAM), // Units expressing rate - 'per' units, both units expanded Arguments.of("m/s", "meters_per_second", PrometheusType.GAUGE), Arguments.of("m/s", "meters_per_second", PrometheusType.COUNTER), Arguments.of("m/s", "meters_per_second", PrometheusType.SUMMARY), Arguments.of("m/s", "meters_per_second", PrometheusType.HISTOGRAM), + // Units expressing rate - per minute + Arguments.of("m/m", "meters_per_minute", PrometheusType.GAUGE), + Arguments.of("m/m", "meters_per_minute", PrometheusType.COUNTER), + Arguments.of("m/m", "meters_per_minute", PrometheusType.SUMMARY), + Arguments.of("m/m", "meters_per_minute", PrometheusType.HISTOGRAM), + // Units expressing rate - per day + Arguments.of("A/d", "amperes_per_day", PrometheusType.GAUGE), + Arguments.of("A/d", "amperes_per_day", PrometheusType.COUNTER), + Arguments.of("A/d", "amperes_per_day", PrometheusType.SUMMARY), + Arguments.of("A/d", "amperes_per_day", PrometheusType.HISTOGRAM), + // Units expressing rate - per week + Arguments.of("W/w", "watts_per_week", PrometheusType.GAUGE), + Arguments.of("W/w", "watts_per_week", PrometheusType.COUNTER), + Arguments.of("W/w", "watts_per_week", PrometheusType.SUMMARY), + Arguments.of("W/w", "watts_per_week", PrometheusType.HISTOGRAM), + // Units expressing rate - per month + Arguments.of("J/mo", "joules_per_month", PrometheusType.GAUGE), + Arguments.of("J/mo", "joules_per_month", PrometheusType.COUNTER), + Arguments.of("J/mo", "joules_per_month", PrometheusType.SUMMARY), + Arguments.of("J/mo", "joules_per_month", PrometheusType.HISTOGRAM), + // Units expressing rate - per year + Arguments.of("TB/y", "terabytes_per_year", PrometheusType.GAUGE), + Arguments.of("TB/y", "terabytes_per_year", PrometheusType.COUNTER), + Arguments.of("TB/y", "terabytes_per_year", PrometheusType.SUMMARY), + Arguments.of("TB/y", "terabytes_per_year", PrometheusType.HISTOGRAM), + // Units expressing rate - 'per' units, both units unknown + Arguments.of("v/v", "v_per_v", PrometheusType.GAUGE), + Arguments.of("v/v", "v_per_v", PrometheusType.COUNTER), + Arguments.of("v/v", "v_per_v", PrometheusType.SUMMARY), + Arguments.of("v/v", "v_per_v", PrometheusType.HISTOGRAM), + // Units expressing rate - 'per' units, first unit unknown + Arguments.of("km/h", "km_per_hour", PrometheusType.GAUGE), + Arguments.of("km/h", "km_per_hour", PrometheusType.COUNTER), + Arguments.of("km/h", "km_per_hour", PrometheusType.SUMMARY), + Arguments.of("km/h", "km_per_hour", PrometheusType.HISTOGRAM), + // Units expressing rate - 'per' units, 'per' unit unknown + Arguments.of("g/g", "grams_per_g", PrometheusType.GAUGE), + Arguments.of("g/g", "grams_per_g", PrometheusType.COUNTER), + Arguments.of("g/g", "grams_per_g", PrometheusType.SUMMARY), + Arguments.of("g/g", "grams_per_g", PrometheusType.HISTOGRAM), + // Misc - unit containing known abbreviations improperly formatted + Arguments.of("watts_W", "watts_W", PrometheusType.GAUGE), + Arguments.of("watts_W", "watts_W", PrometheusType.COUNTER), + Arguments.of("watts_W", "watts_W", PrometheusType.SUMMARY), + Arguments.of("watts_W", "watts_W", PrometheusType.HISTOGRAM), // Misc - unsupported symbols are replaced with _ Arguments.of("°F", "F", PrometheusType.GAUGE), Arguments.of("°F", "F", PrometheusType.COUNTER), @@ -93,11 +234,6 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { Arguments.of("__test $/°C", "test_per_C", PrometheusType.COUNTER), Arguments.of("__test $/°C", "test_per_C", PrometheusType.SUMMARY), Arguments.of("__test $/°C", "test_per_C", PrometheusType.HISTOGRAM), - // Misc - Special supported symbols - Arguments.of("$", "dollars", PrometheusType.GAUGE), - Arguments.of("$", "dollars", PrometheusType.COUNTER), - Arguments.of("$", "dollars", PrometheusType.SUMMARY), - Arguments.of("$", "dollars", PrometheusType.HISTOGRAM), // Empty Units - whitespace Arguments.of("\t", "", PrometheusType.GAUGE), Arguments.of("\t", "", PrometheusType.COUNTER), From 7568167317d2fbd37342633236d8a9ed2cb85a4f Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Wed, 26 Apr 2023 17:25:14 +0000 Subject: [PATCH 11/23] Add missing test case to increase coverage --- .../exporter/prometheus/PrometheusUnitsHelperTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java index b95f7d52b56..15673da2090 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -84,6 +84,11 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { Arguments.of("GiBy", "gibibytes", PrometheusType.COUNTER), Arguments.of("GiBy", "gibibytes", PrometheusType.SUMMARY), Arguments.of("GiBy", "gibibytes", PrometheusType.HISTOGRAM), + // Simple expansion - storage TiBy + Arguments.of("TiBy", "tibibytes", PrometheusType.GAUGE), + Arguments.of("TiBy", "tibibytes", PrometheusType.COUNTER), + Arguments.of("TiBy", "tibibytes", PrometheusType.SUMMARY), + Arguments.of("TiBy", "tibibytes", PrometheusType.HISTOGRAM), // Simple expansion - Time unit d Arguments.of("d", "days", PrometheusType.GAUGE), Arguments.of("d", "days", PrometheusType.COUNTER), From bab0e57de03d4e133102ee917fb69235a1835ebb Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Thu, 27 Apr 2023 15:16:57 +0000 Subject: [PATCH 12/23] Add missing documentation --- .../prometheus/PrometheusUnitsHelper.java | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 1e8914786de..5aad02eb3ba 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -9,10 +9,8 @@ import java.util.regex.Pattern; /** - * A utility class that is used to maintain mappings between OTLP unit and Prometheus units. The - * list of mappings is adopted from OpenTelemetry - * Collector Contrib. + * A utility class that contains helper function(s) to perform conversion from OTLP to Prometheus + * units. * * @see OpenMetrics @@ -50,17 +48,25 @@ public static String getEquivalentPrometheusUnit( return "ratio"; } - String convertedMetricUnitName = rawMetricUnitName; // Drop units specified between curly braces - convertedMetricUnitName = removeUnitPortionInBrackets(convertedMetricUnitName); - + String convertedMetricUnitName = removeUnitPortionInBraces(rawMetricUnitName); // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); - // Converting abbreviated unit names to full names return cleanUpString(getPrometheusUnit(convertedMetricUnitName)); } + /** + * This method is used to convert the units expressed as a rate via '/' symbol in their name to + * their expanded text equivalent. For instance, km/h => km_per_hour. The method operates on the + * input by splitting it in 2 parts - before and after '/' symbol and will attempt to expand any + * known unit abbreviation in both parts. Unknown abbreviation will remain unchanged in the final + * output. + * + * @param rateExpressedUnit The rate unit input that needs to be converted to its text equivalent. + * @return The text equivalent of unit expressed as rate. If the input does not contain '/', the + * function returns it as-is. + */ private static String convertRateExpressedToPrometheusUnit(String rateExpressedUnit) { if (!rateExpressedUnit.contains("/")) { return rateExpressedUnit; @@ -73,8 +79,17 @@ private static String convertRateExpressedToPrometheusUnit(String rateExpressedU return getPrometheusUnit(rateEntities[0]) + "_per_" + getPrometheusPerUnit(rateEntities[1]); } - private static String removeUnitPortionInBrackets(String unit) { - // This does not handle nested braces + /** + * This method drops all characters enclosed within '{}' (including the curly braces) by replacing + * them with an empty string. Note that this method will not produce the intended effect if there + * are nested curly braces within the outer enclosure of '{}'. + * + *

For instance, {packet{s}s} => s}. + * + * @param unit The input unit from which text within curly braces needs to be removed. + * @return The resulting unit after removing the text within '{}'. + */ + private static String removeUnitPortionInBraces(String unit) { return unit.replaceAll(CHARACTERS_BETWEEN_BRACES_REGEX, ""); } @@ -94,7 +109,11 @@ private static String cleanUpString(String string) { } /** - * This method retrieves the expanded Prometheus unit name for known abbreviations. + * This method retrieves the expanded Prometheus unit name for known abbreviations. OTLP metrics + * use the c/s notation as specified at UCUM. The list of + * mappings is adopted from OpenTelemetry + * Collector Contrib. * * @param unitAbbreviation The unit that name that needs to be expanded/converted to Prometheus * units. From 19a93fea19a9b16ec1d667242d0fe37f341285a1 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 28 Apr 2023 16:18:27 +0000 Subject: [PATCH 13/23] Make PrometheusUnitsHelper class internal --- .../exporter/prometheus/PrometheusUnitsHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 5aad02eb3ba..9672d5528ca 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -18,7 +18,7 @@ * @see Prometheus best practices * for units */ -public final class PrometheusUnitsHelper { +final class PrometheusUnitsHelper { private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); private static final String CHARACTERS_BETWEEN_BRACES_REGEX = From 2937d84989d74bda1dabbfdd54660a2a0dc73a0c Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 28 Apr 2023 16:46:51 +0000 Subject: [PATCH 14/23] Replace string replace with pattern matching --- .../exporter/prometheus/PrometheusUnitsHelper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 9672d5528ca..234119c7c26 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -21,8 +21,7 @@ final class PrometheusUnitsHelper { private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); - private static final String CHARACTERS_BETWEEN_BRACES_REGEX = - "\\{(.*?)}"; // matches all characters between {} + private static final Pattern CHARACTERS_BETWEEN_BRACES_PATTERN = Pattern.compile("\\{(.*?)}"); private PrometheusUnitsHelper() { // Prevent object creation for utility classes @@ -90,7 +89,7 @@ private static String convertRateExpressedToPrometheusUnit(String rateExpressedU * @return The resulting unit after removing the text within '{}'. */ private static String removeUnitPortionInBraces(String unit) { - return unit.replaceAll(CHARACTERS_BETWEEN_BRACES_REGEX, ""); + return CHARACTERS_BETWEEN_BRACES_PATTERN.matcher(unit).replaceAll(""); } /** From ac96ab072677ad98900d2bfe4c6608798e9c1cde Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 28 Apr 2023 21:08:47 +0000 Subject: [PATCH 15/23] Refactor: unit name cleanup logic moved to Serializer --- .../exporter/prometheus/NameSanitizer.java | 8 +- .../prometheus/PrometheusUnitsHelper.java | 41 +-- .../exporter/prometheus/Serializer.java | 16 +- .../prometheus/NameSanitizerTest.java | 33 +++ .../prometheus/PrometheusUnitsHelperTest.java | 254 ++++-------------- 5 files changed, 113 insertions(+), 239 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java index 40465d30b07..466d9ccbfa6 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java @@ -17,6 +17,7 @@ class NameSanitizer implements Function { private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_:]"); private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); + private static final Pattern SANITIZE_CONSECUTIVE_UNDERSCORES = Pattern.compile("[_]{2,}"); private final Function delegate; private final Map cache = new ConcurrentHashMap<>(); @@ -36,8 +37,11 @@ public String apply(String labelName) { } private static String sanitizeMetricName(String metricName) { - return SANITIZE_BODY_PATTERN - .matcher(SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_")) + return SANITIZE_CONSECUTIVE_UNDERSCORES + .matcher( + SANITIZE_BODY_PATTERN + .matcher(SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_")) + .replaceAll("_")) .replaceAll("_"); } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 234119c7c26..50fb4e6d252 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -9,8 +9,7 @@ import java.util.regex.Pattern; /** - * A utility class that contains helper function(s) to perform conversion from OTLP to Prometheus - * units. + * A utility class that contains helper function(s) to aid conversion from OTLP to Prometheus units. * * @see OpenMetrics @@ -20,7 +19,6 @@ */ final class PrometheusUnitsHelper { - private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); private static final Pattern CHARACTERS_BETWEEN_BRACES_PATTERN = Pattern.compile("\\{(.*?)}"); private PrometheusUnitsHelper() { @@ -29,38 +27,34 @@ private PrometheusUnitsHelper() { /** * A utility function that returns the equivalent Prometheus name for the provided OTLP metric - * unit. + * unit. This function does not handle the unsupported characters that it may find within the + * string. * * @param rawMetricUnitName The raw metric unit for which Prometheus metric unit needs to be * computed. - * @param metricType The {@link PrometheusType} of the metric whose unit is to be converted. * @return the computed Prometheus metric unit equivalent of the OTLP metric unit. + * @see Prometheus + * metric names and labels for supported characters. */ - public static String getEquivalentPrometheusUnit( - String rawMetricUnitName, PrometheusType metricType) { + public static String getEquivalentPrometheusUnit(String rawMetricUnitName) { if (StringUtils.isNullOrEmpty(rawMetricUnitName)) { return rawMetricUnitName; } - - // special case - if (rawMetricUnitName.equals("1") && metricType == PrometheusType.GAUGE) { - return "ratio"; - } - // Drop units specified between curly braces String convertedMetricUnitName = removeUnitPortionInBraces(rawMetricUnitName); // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); // Converting abbreviated unit names to full names - return cleanUpString(getPrometheusUnit(convertedMetricUnitName)); + return getPrometheusUnit(convertedMetricUnitName); } /** * This method is used to convert the units expressed as a rate via '/' symbol in their name to * their expanded text equivalent. For instance, km/h => km_per_hour. The method operates on the * input by splitting it in 2 parts - before and after '/' symbol and will attempt to expand any - * known unit abbreviation in both parts. Unknown abbreviation will remain unchanged in the final - * output. + * known unit abbreviation in both parts. Unknown abbreviations & unsupported characters will + * remain unchanged in the final output of this function. * * @param rateExpressedUnit The rate unit input that needs to be converted to its text equivalent. * @return The text equivalent of unit expressed as rate. If the input does not contain '/', the @@ -92,21 +86,6 @@ private static String removeUnitPortionInBraces(String unit) { return CHARACTERS_BETWEEN_BRACES_PATTERN.matcher(unit).replaceAll(""); } - /** - * Replaces all characters that are not a letter or a digit with '_' to make the resulting string - * Prometheus compliant. This method also removes leading and trailing underscores. - * - * @param string The string input that needs to be made Prometheus compliant. - * @return the cleaned-up Prometheus compliant string. - */ - private static String cleanUpString(String string) { - String prometheusCompliant = - INVALID_CHARACTERS_PATTERN.matcher(string).replaceAll("_").replaceAll("[_]{2,}", "_"); - prometheusCompliant = prometheusCompliant.replaceAll("_+$", ""); // remove trailing underscore - prometheusCompliant = prometheusCompliant.replaceAll("^_+", ""); // remove leading underscore - return prometheusCompliant; - } - /** * This method retrieves the expanded Prometheus unit name for known abbreviations. OTLP metrics * use the c/s notation as specified at UCUM. The list of diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java index 90fc67281c2..66fd2bcb5ca 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java @@ -652,18 +652,24 @@ static Collection getPoints(MetricData metricData) { } private static String metricName(MetricData rawMetric, PrometheusType type) { - String name = NameSanitizer.INSTANCE.apply(rawMetric.getName()); + String name = rawMetric.getName(); String prometheusEquivalentUnit = - PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit(), type); - if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit) - && !name.contains(prometheusEquivalentUnit)) { + PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit()); + // append prometheus unit if not null or empty. + if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit)) { name = name + "_" + prometheusEquivalentUnit; } + // special case - counter if (type == PrometheusType.COUNTER) { name = name + "_total"; } - return name; + // special case - gauge + if (rawMetric.getUnit().equals("1") && type == PrometheusType.GAUGE) { + name = name + "_ratio"; + } + + return NameSanitizer.INSTANCE.apply(name); } private static double getExemplarValue(ExemplarData exemplar) { diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/NameSanitizerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/NameSanitizerTest.java index e1dc0f1926e..56eb36f085b 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/NameSanitizerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/NameSanitizerTest.java @@ -9,7 +9,12 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; 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 NameSanitizerTest { @@ -27,4 +32,32 @@ void testSanitizerCaching() { assertThat(sanitizer.apply(labelName)).isEqualTo("http.name1"); assertThat(count).hasValue(1); } + + @ParameterizedTest + @MethodSource("provideMetricNamesForTest") + void testSanitizerCleansing(String unsanitizedName, String sanitizedName) { + Assertions.assertEquals(sanitizedName, NameSanitizer.INSTANCE.apply(unsanitizedName)); + } + + private static Stream provideMetricNamesForTest() { + return Stream.of( + // valid name - already sanitized + Arguments.of( + "active_directory_ds_replication_network_io", + "active_directory_ds_replication_network_io"), + // consecutive underscores + Arguments.of("cpu_sp__d_hertz", "cpu_sp_d_hertz"), + // leading and trailing underscores - should be fine + Arguments.of("_cpu_speed_hertz_", "_cpu_speed_hertz_"), + // unsupported characters replaced + Arguments.of("metric_unit_$1000", "metric_unit_1000"), + // multiple unsupported characters - whitespace + Arguments.of("sample_me%%$$$_count_ !!@unit include", "sample_me_count_unit_include"), + // metric names cannot start with a number + Arguments.of("1_some_metric_name", "_some_metric_name"), + // metric names can have : + Arguments.of("sample_metric_name__:_per_meter", "sample_metric_name_:_per_meter"), + // Illegal characters + Arguments.of("cpu_sp$$d_hertz", "cpu_sp_d_hertz")); + } } diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java index 15673da2090..61e53111d78 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -16,248 +16,100 @@ class PrometheusUnitsHelperTest { @ParameterizedTest @MethodSource("providePrometheusOTelUnitEquivalentPairs") - public void testPrometheusUnitEquivalency( - String otlpUnit, String prometheusUnit, PrometheusType metricType) { - assertEquals( - prometheusUnit, PrometheusUnitsHelper.getEquivalentPrometheusUnit(otlpUnit, metricType)); + public void testPrometheusUnitEquivalency(String otlpUnit, String prometheusUnit) { + assertEquals(prometheusUnit, PrometheusUnitsHelper.getEquivalentPrometheusUnit(otlpUnit)); } private static Stream providePrometheusOTelUnitEquivalentPairs() { return Stream.of( // Simple expansion - storage Bytes - Arguments.of("By", "bytes", PrometheusType.GAUGE), - Arguments.of("By", "bytes", PrometheusType.COUNTER), - Arguments.of("By", "bytes", PrometheusType.SUMMARY), - Arguments.of("By", "bytes", PrometheusType.HISTOGRAM), - Arguments.of("B", "bytes", PrometheusType.GAUGE), - Arguments.of("B", "bytes", PrometheusType.COUNTER), - Arguments.of("B", "bytes", PrometheusType.SUMMARY), - Arguments.of("B", "bytes", PrometheusType.HISTOGRAM), + Arguments.of("By", "bytes"), + Arguments.of("B", "bytes"), // Simple expansion - storage KB - Arguments.of("KB", "kilobytes", PrometheusType.GAUGE), - Arguments.of("KB", "kilobytes", PrometheusType.COUNTER), - Arguments.of("KB", "kilobytes", PrometheusType.SUMMARY), - Arguments.of("KB", "kilobytes", PrometheusType.HISTOGRAM), - Arguments.of("KBy", "kilobytes", PrometheusType.GAUGE), - Arguments.of("KBy", "kilobytes", PrometheusType.COUNTER), - Arguments.of("KBy", "kilobytes", PrometheusType.SUMMARY), - Arguments.of("KBy", "kilobytes", PrometheusType.HISTOGRAM), + Arguments.of("KB", "kilobytes"), + Arguments.of("KBy", "kilobytes"), // Simple expansion - storage MB - Arguments.of("MB", "megabytes", PrometheusType.GAUGE), - Arguments.of("MB", "megabytes", PrometheusType.COUNTER), - Arguments.of("MB", "megabytes", PrometheusType.SUMMARY), - Arguments.of("MB", "megabytes", PrometheusType.HISTOGRAM), - Arguments.of("MBy", "megabytes", PrometheusType.GAUGE), - Arguments.of("MBy", "megabytes", PrometheusType.COUNTER), - Arguments.of("MBy", "megabytes", PrometheusType.SUMMARY), - Arguments.of("MBy", "megabytes", PrometheusType.HISTOGRAM), + Arguments.of("MB", "megabytes"), + Arguments.of("MBy", "megabytes"), // Simple expansion - storage GB - Arguments.of("GB", "gigabytes", PrometheusType.GAUGE), - Arguments.of("GB", "gigabytes", PrometheusType.COUNTER), - Arguments.of("GB", "gigabytes", PrometheusType.SUMMARY), - Arguments.of("GB", "gigabytes", PrometheusType.HISTOGRAM), - Arguments.of("GBy", "gigabytes", PrometheusType.GAUGE), - Arguments.of("GBy", "gigabytes", PrometheusType.COUNTER), - Arguments.of("GBy", "gigabytes", PrometheusType.SUMMARY), - Arguments.of("GBy", "gigabytes", PrometheusType.HISTOGRAM), + Arguments.of("GB", "gigabytes"), + Arguments.of("GBy", "gigabytes"), // Simple expansion - storage TB - Arguments.of("TB", "terabytes", PrometheusType.GAUGE), - Arguments.of("TB", "terabytes", PrometheusType.COUNTER), - Arguments.of("TB", "terabytes", PrometheusType.SUMMARY), - Arguments.of("TB", "terabytes", PrometheusType.HISTOGRAM), - Arguments.of("TBy", "terabytes", PrometheusType.GAUGE), - Arguments.of("TBy", "terabytes", PrometheusType.COUNTER), - Arguments.of("TBy", "terabytes", PrometheusType.SUMMARY), - Arguments.of("TBy", "terabytes", PrometheusType.HISTOGRAM), + Arguments.of("TB", "terabytes"), + Arguments.of("TBy", "terabytes"), // Simple expansion - storage KiBy - Arguments.of("KiBy", "kibibytes", PrometheusType.GAUGE), - Arguments.of("KiBy", "kibibytes", PrometheusType.COUNTER), - Arguments.of("KiBy", "kibibytes", PrometheusType.SUMMARY), - Arguments.of("KiBy", "kibibytes", PrometheusType.HISTOGRAM), + Arguments.of("KiBy", "kibibytes"), // Simple expansion - storage MiBy - Arguments.of("MiBy", "mebibytes", PrometheusType.GAUGE), - Arguments.of("MiBy", "mebibytes", PrometheusType.COUNTER), - Arguments.of("MiBy", "mebibytes", PrometheusType.SUMMARY), - Arguments.of("MiBy", "mebibytes", PrometheusType.HISTOGRAM), + Arguments.of("MiBy", "mebibytes"), // Simple expansion - storage GiBy - Arguments.of("GiBy", "gibibytes", PrometheusType.GAUGE), - Arguments.of("GiBy", "gibibytes", PrometheusType.COUNTER), - Arguments.of("GiBy", "gibibytes", PrometheusType.SUMMARY), - Arguments.of("GiBy", "gibibytes", PrometheusType.HISTOGRAM), + Arguments.of("GiBy", "gibibytes"), // Simple expansion - storage TiBy - Arguments.of("TiBy", "tibibytes", PrometheusType.GAUGE), - Arguments.of("TiBy", "tibibytes", PrometheusType.COUNTER), - Arguments.of("TiBy", "tibibytes", PrometheusType.SUMMARY), - Arguments.of("TiBy", "tibibytes", PrometheusType.HISTOGRAM), + Arguments.of("TiBy", "tibibytes"), // Simple expansion - Time unit d - Arguments.of("d", "days", PrometheusType.GAUGE), - Arguments.of("d", "days", PrometheusType.COUNTER), - Arguments.of("d", "days", PrometheusType.SUMMARY), - Arguments.of("d", "days", PrometheusType.HISTOGRAM), + Arguments.of("d", "days"), // Simple expansion - Time unit h - Arguments.of("h", "hours", PrometheusType.GAUGE), - Arguments.of("h", "hours", PrometheusType.COUNTER), - Arguments.of("h", "hours", PrometheusType.SUMMARY), - Arguments.of("h", "hours", PrometheusType.HISTOGRAM), + Arguments.of("h", "hours"), // Simple expansion - Time unit s - Arguments.of("s", "seconds", PrometheusType.GAUGE), - Arguments.of("s", "seconds", PrometheusType.COUNTER), - Arguments.of("s", "seconds", PrometheusType.SUMMARY), - Arguments.of("s", "seconds", PrometheusType.HISTOGRAM), + Arguments.of("s", "seconds"), // Simple expansion - Time unit ms - Arguments.of("ms", "milliseconds", PrometheusType.GAUGE), - Arguments.of("ms", "milliseconds", PrometheusType.COUNTER), - Arguments.of("ms", "milliseconds", PrometheusType.SUMMARY), - Arguments.of("ms", "milliseconds", PrometheusType.HISTOGRAM), + Arguments.of("ms", "milliseconds"), // Simple expansion - Time unit us - Arguments.of("us", "microseconds", PrometheusType.GAUGE), - Arguments.of("us", "microseconds", PrometheusType.COUNTER), - Arguments.of("us", "microseconds", PrometheusType.SUMMARY), - Arguments.of("us", "microseconds", PrometheusType.HISTOGRAM), + Arguments.of("us", "microseconds"), // Simple expansion - Time unit ns - Arguments.of("ns", "nanoseconds", PrometheusType.GAUGE), - Arguments.of("ns", "nanoseconds", PrometheusType.COUNTER), - Arguments.of("ns", "nanoseconds", PrometheusType.SUMMARY), - Arguments.of("ns", "nanoseconds", PrometheusType.HISTOGRAM), + Arguments.of("ns", "nanoseconds"), // Simple expansion - Time unit min - Arguments.of("min", "minutes", PrometheusType.GAUGE), - Arguments.of("min", "minutes", PrometheusType.COUNTER), - Arguments.of("min", "minutes", PrometheusType.SUMMARY), - Arguments.of("min", "minutes", PrometheusType.HISTOGRAM), + Arguments.of("min", "minutes"), // Simple expansion - special symbol - % - Arguments.of("%", "percent", PrometheusType.GAUGE), - Arguments.of("%", "percent", PrometheusType.COUNTER), - Arguments.of("%", "percent", PrometheusType.SUMMARY), - Arguments.of("%", "percent", PrometheusType.HISTOGRAM), + Arguments.of("%", "percent"), // Simple expansion - special symbols - $ - Arguments.of("$", "dollars", PrometheusType.GAUGE), - Arguments.of("$", "dollars", PrometheusType.COUNTER), - Arguments.of("$", "dollars", PrometheusType.SUMMARY), - Arguments.of("$", "dollars", PrometheusType.HISTOGRAM), + Arguments.of("$", "dollars"), // Simple expansion - frequency - Arguments.of("Hz", "hertz", PrometheusType.GAUGE), - Arguments.of("Hz", "hertz", PrometheusType.COUNTER), - Arguments.of("Hz", "hertz", PrometheusType.SUMMARY), - Arguments.of("Hz", "hertz", PrometheusType.HISTOGRAM), + Arguments.of("Hz", "hertz"), // Simple expansion - temperature - Arguments.of("Cel", "celsius", PrometheusType.GAUGE), - Arguments.of("Cel", "celsius", PrometheusType.COUNTER), - Arguments.of("Cel", "celsius", PrometheusType.SUMMARY), - Arguments.of("Cel", "celsius", PrometheusType.HISTOGRAM), + Arguments.of("Cel", "celsius"), // Unit not found - Case sensitive - Arguments.of("S", "S", PrometheusType.GAUGE), - Arguments.of("S", "S", PrometheusType.COUNTER), - Arguments.of("S", "S", PrometheusType.SUMMARY), - Arguments.of("S", "S", PrometheusType.HISTOGRAM), + Arguments.of("S", "S"), // Special case - 1 - Arguments.of("1", "ratio", PrometheusType.GAUGE), - Arguments.of("1", "", PrometheusType.COUNTER), - Arguments.of("1", "", PrometheusType.SUMMARY), - Arguments.of("1", "", PrometheusType.HISTOGRAM), + Arguments.of("1", ""), // Special Case - Drop metric units in {} - Arguments.of("{packets}", "", PrometheusType.GAUGE), - Arguments.of("{packets}", "", PrometheusType.COUNTER), - Arguments.of("{packets}", "", PrometheusType.SUMMARY), - Arguments.of("{packets}", "", PrometheusType.HISTOGRAM), + Arguments.of("{packets}", ""), // Special Case - Dropped metric units only in {} - Arguments.of("{packets}V", "volts", PrometheusType.GAUGE), - Arguments.of("{packets}V", "volts", PrometheusType.COUNTER), - Arguments.of("{packets}V", "volts", PrometheusType.SUMMARY), - Arguments.of("{packets}V", "volts", PrometheusType.HISTOGRAM), + Arguments.of("{packets}V", "volts"), // Special Case - Dropped metric units with 'per' unit handling applicable - Arguments.of("{scanned}/{returned}", "", PrometheusType.GAUGE), - Arguments.of("{scanned}/{returned}", "", PrometheusType.COUNTER), - Arguments.of("{scanned}/{returned}", "", PrometheusType.SUMMARY), - Arguments.of("{scanned}/{returned}", "", PrometheusType.HISTOGRAM), + Arguments.of("{scanned}/{returned}", "/"), // Special Case - Dropped metric units with 'per' unit handling applicable - Arguments.of("{objects}/s", "per_second", PrometheusType.GAUGE), - Arguments.of("{objects}/s", "per_second", PrometheusType.COUNTER), - Arguments.of("{objects}/s", "per_second", PrometheusType.SUMMARY), - Arguments.of("{objects}/s", "per_second", PrometheusType.HISTOGRAM), + Arguments.of("{objects}/s", "_per_second"), // Units expressing rate - 'per' units, both units expanded - Arguments.of("m/s", "meters_per_second", PrometheusType.GAUGE), - Arguments.of("m/s", "meters_per_second", PrometheusType.COUNTER), - Arguments.of("m/s", "meters_per_second", PrometheusType.SUMMARY), - Arguments.of("m/s", "meters_per_second", PrometheusType.HISTOGRAM), + Arguments.of("m/s", "meters_per_second"), // Units expressing rate - per minute - Arguments.of("m/m", "meters_per_minute", PrometheusType.GAUGE), - Arguments.of("m/m", "meters_per_minute", PrometheusType.COUNTER), - Arguments.of("m/m", "meters_per_minute", PrometheusType.SUMMARY), - Arguments.of("m/m", "meters_per_minute", PrometheusType.HISTOGRAM), + Arguments.of("m/m", "meters_per_minute"), // Units expressing rate - per day - Arguments.of("A/d", "amperes_per_day", PrometheusType.GAUGE), - Arguments.of("A/d", "amperes_per_day", PrometheusType.COUNTER), - Arguments.of("A/d", "amperes_per_day", PrometheusType.SUMMARY), - Arguments.of("A/d", "amperes_per_day", PrometheusType.HISTOGRAM), + Arguments.of("A/d", "amperes_per_day"), // Units expressing rate - per week - Arguments.of("W/w", "watts_per_week", PrometheusType.GAUGE), - Arguments.of("W/w", "watts_per_week", PrometheusType.COUNTER), - Arguments.of("W/w", "watts_per_week", PrometheusType.SUMMARY), - Arguments.of("W/w", "watts_per_week", PrometheusType.HISTOGRAM), + Arguments.of("W/w", "watts_per_week"), // Units expressing rate - per month - Arguments.of("J/mo", "joules_per_month", PrometheusType.GAUGE), - Arguments.of("J/mo", "joules_per_month", PrometheusType.COUNTER), - Arguments.of("J/mo", "joules_per_month", PrometheusType.SUMMARY), - Arguments.of("J/mo", "joules_per_month", PrometheusType.HISTOGRAM), + Arguments.of("J/mo", "joules_per_month"), // Units expressing rate - per year - Arguments.of("TB/y", "terabytes_per_year", PrometheusType.GAUGE), - Arguments.of("TB/y", "terabytes_per_year", PrometheusType.COUNTER), - Arguments.of("TB/y", "terabytes_per_year", PrometheusType.SUMMARY), - Arguments.of("TB/y", "terabytes_per_year", PrometheusType.HISTOGRAM), + Arguments.of("TB/y", "terabytes_per_year"), // Units expressing rate - 'per' units, both units unknown - Arguments.of("v/v", "v_per_v", PrometheusType.GAUGE), - Arguments.of("v/v", "v_per_v", PrometheusType.COUNTER), - Arguments.of("v/v", "v_per_v", PrometheusType.SUMMARY), - Arguments.of("v/v", "v_per_v", PrometheusType.HISTOGRAM), + Arguments.of("v/v", "v_per_v"), // Units expressing rate - 'per' units, first unit unknown - Arguments.of("km/h", "km_per_hour", PrometheusType.GAUGE), - Arguments.of("km/h", "km_per_hour", PrometheusType.COUNTER), - Arguments.of("km/h", "km_per_hour", PrometheusType.SUMMARY), - Arguments.of("km/h", "km_per_hour", PrometheusType.HISTOGRAM), + Arguments.of("km/h", "km_per_hour"), // Units expressing rate - 'per' units, 'per' unit unknown - Arguments.of("g/g", "grams_per_g", PrometheusType.GAUGE), - Arguments.of("g/g", "grams_per_g", PrometheusType.COUNTER), - Arguments.of("g/g", "grams_per_g", PrometheusType.SUMMARY), - Arguments.of("g/g", "grams_per_g", PrometheusType.HISTOGRAM), + Arguments.of("g/g", "grams_per_g"), // Misc - unit containing known abbreviations improperly formatted - Arguments.of("watts_W", "watts_W", PrometheusType.GAUGE), - Arguments.of("watts_W", "watts_W", PrometheusType.COUNTER), - Arguments.of("watts_W", "watts_W", PrometheusType.SUMMARY), - Arguments.of("watts_W", "watts_W", PrometheusType.HISTOGRAM), - // Misc - unsupported symbols are replaced with _ - Arguments.of("°F", "F", PrometheusType.GAUGE), - Arguments.of("°F", "F", PrometheusType.COUNTER), - Arguments.of("°F", "F", PrometheusType.SUMMARY), - Arguments.of("°F", "F", PrometheusType.HISTOGRAM), - // Misc - multiple unsupported symbols are replaced with single _ - Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.GAUGE), - Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.COUNTER), - Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.SUMMARY), - Arguments.of("unit+=.:,!* & #unused", "unit_unused", PrometheusType.HISTOGRAM), - // Misc - unsupported runes in 'per' units - Arguments.of("__test $/°C", "test_per_C", PrometheusType.GAUGE), - Arguments.of("__test $/°C", "test_per_C", PrometheusType.COUNTER), - Arguments.of("__test $/°C", "test_per_C", PrometheusType.SUMMARY), - Arguments.of("__test $/°C", "test_per_C", PrometheusType.HISTOGRAM), - // Empty Units - whitespace - Arguments.of("\t", "", PrometheusType.GAUGE), - Arguments.of("\t", "", PrometheusType.COUNTER), - Arguments.of("\t", "", PrometheusType.SUMMARY), - Arguments.of("\t", "", PrometheusType.HISTOGRAM), + Arguments.of("watts_W", "watts_W"), + // Unsupported symbols + Arguments.of("°F", "°F"), + // Unsupported symbols - multiple + Arguments.of("unit+=.:,!* & #unused", "unit+=.:,!* & #unused"), + // Unsupported symbols - 'per' units + Arguments.of("__test $/°C", "__test $_per_°C"), + // Unsupported symbols - whitespace + Arguments.of("\t", "\t"), // Null unit - Arguments.of(null, null, PrometheusType.GAUGE), - Arguments.of(null, null, PrometheusType.COUNTER), - Arguments.of(null, null, PrometheusType.SUMMARY), - Arguments.of(null, null, PrometheusType.HISTOGRAM), + Arguments.of(null, null), // Misc - unit cleanup - no case match special char - Arguments.of("$1000", "1000", PrometheusType.GAUGE), - Arguments.of("$1000", "1000", PrometheusType.COUNTER), - Arguments.of("$1000", "1000", PrometheusType.SUMMARY), - Arguments.of("$1000", "1000", PrometheusType.HISTOGRAM), - // Misc - unit cleanup - no case match whitespace - Arguments.of("a b !!", "a_b", PrometheusType.GAUGE), - Arguments.of("a b !!", "a_b", PrometheusType.COUNTER), - Arguments.of("a b !!", "a_b", PrometheusType.SUMMARY), - Arguments.of("a b !!", "a_b", PrometheusType.HISTOGRAM)); + Arguments.of("$1000", "$1000")); } } From 5d3488fe51cfafc7c66256bf34fae21dbcfee06a Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Sat, 29 Apr 2023 02:27:05 +0000 Subject: [PATCH 16/23] Add tests for metricName serialization --- .../exporter/prometheus/Serializer.java | 3 +- .../exporter/prometheus/SerializerTest.java | 87 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java index 66fd2bcb5ca..fde8cdbd597 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java @@ -651,7 +651,8 @@ static Collection getPoints(MetricData metricData) { return Collections.emptyList(); } - private static String metricName(MetricData rawMetric, PrometheusType type) { + // Visible for testing + static String metricName(MetricData rawMetric, PrometheusType type) { String name = rawMetric.getName(); String prometheusEquivalentUnit = PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit()); diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index e0b17f055ff..15e004735ed 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -7,6 +7,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -34,7 +35,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; +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 SerializerTest { @@ -468,6 +473,49 @@ void openMetrics() { + "# EOF\n"); } + @ParameterizedTest + @MethodSource("provideRawMetricDataForTest") + void metricNameSerializationTest(MetricData metricData, String expectedSerializedName) { + assertEquals( + expectedSerializedName, + Serializer.metricName(metricData, PrometheusType.forMetric(metricData))); + } + + private static Stream provideRawMetricDataForTest() { + return Stream.of( + // special case for gauge + Arguments.of( + sampleMetricDataGenerator("sample", "1", PrometheusType.GAUGE), "sample_ratio"), + // special case for gauge with drop eligible unit + Arguments.of( + sampleMetricDataGenerator("sample", "1{dropped}", PrometheusType.GAUGE), "sample"), + // Gauge without "1" as unit + Arguments.of( + sampleMetricDataGenerator("sample", "unit", PrometheusType.GAUGE), "sample_unit"), + // special case with counter + Arguments.of( + sampleMetricDataGenerator("sample", "unit", PrometheusType.COUNTER), + "sample_unit_total"), + // special case unit "1", but no gauge + Arguments.of( + sampleMetricDataGenerator("sample", "1", PrometheusType.COUNTER), "sample_total"), + // metric name with unsupported characters + Arguments.of( + sampleMetricDataGenerator("s%%ple", "%/m", PrometheusType.SUMMARY), + "s_ple_percent_per_minute"), + // metric name with dropped portions + Arguments.of( + sampleMetricDataGenerator("s%%ple", "%/m", PrometheusType.SUMMARY), + "s_ple_percent_per_minute"), + // metric unit as a number other than 1 is not treated specially + Arguments.of( + sampleMetricDataGenerator("metric_name", "2", PrometheusType.SUMMARY), "metric_name_2"), + // metric name cannot start with a number + Arguments.of( + sampleMetricDataGenerator("2_metric_name", "By", PrometheusType.SUMMARY), + "_metric_name_bytes")); + } + private static String serialize004(MetricData... metrics) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { @@ -487,4 +535,43 @@ private static String serializeOpenMetrics(MetricData... metrics) { throw new UncheckedIOException(e); } } + + private static MetricData sampleMetricDataGenerator( + String metricName, String metricUnit, PrometheusType prometheusType) { + switch (prometheusType) { + case SUMMARY: + return ImmutableMetricData.createDoubleSummary( + SUMMARY.getResource(), + SUMMARY.getInstrumentationScopeInfo(), + metricName, + SUMMARY.getDescription(), + metricUnit, + SUMMARY.getSummaryData()); + case COUNTER: + return ImmutableMetricData.createLongSum( + MONOTONIC_CUMULATIVE_LONG_SUM.getResource(), + MONOTONIC_CUMULATIVE_LONG_SUM.getInstrumentationScopeInfo(), + metricName, + MONOTONIC_CUMULATIVE_LONG_SUM.getDescription(), + metricUnit, + MONOTONIC_CUMULATIVE_LONG_SUM.getLongSumData()); + case GAUGE: + return ImmutableMetricData.createDoubleGauge( + DOUBLE_GAUGE.getResource(), + DOUBLE_GAUGE.getInstrumentationScopeInfo(), + metricName, + DOUBLE_GAUGE.getDescription(), + metricUnit, + DOUBLE_GAUGE.getDoubleGaugeData()); + case HISTOGRAM: + return ImmutableMetricData.createDoubleHistogram( + DELTA_HISTOGRAM.getResource(), + DELTA_HISTOGRAM.getInstrumentationScopeInfo(), + metricName, + DELTA_HISTOGRAM.getDescription(), + metricUnit, + DELTA_HISTOGRAM.getHistogramData()); + } + throw new IllegalArgumentException(); + } } From bf33ae02ab175b73385d94aab9f68681399a5a4b Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Sat, 29 Apr 2023 16:16:13 +0000 Subject: [PATCH 17/23] Cleanup units before returning --- .../exporter/prometheus/NameSanitizer.java | 3 +- .../prometheus/PrometheusUnitsHelper.java | 35 +++++++++++++++---- .../prometheus/PrometheusUnitsHelperTest.java | 14 ++++---- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java index 466d9ccbfa6..9e8c62c4ede 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java @@ -15,9 +15,10 @@ class NameSanitizer implements Function { static final NameSanitizer INSTANCE = new NameSanitizer(); + static final Pattern SANITIZE_CONSECUTIVE_UNDERSCORES = Pattern.compile("[_]{2,}"); + private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_:]"); private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); - private static final Pattern SANITIZE_CONSECUTIVE_UNDERSCORES = Pattern.compile("[_]{2,}"); private final Function delegate; private final Map cache = new ConcurrentHashMap<>(); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index 50fb4e6d252..e72216ba474 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -5,6 +5,8 @@ package io.opentelemetry.exporter.prometheus; +import static io.opentelemetry.exporter.prometheus.NameSanitizer.SANITIZE_CONSECUTIVE_UNDERSCORES; + import io.opentelemetry.api.internal.StringUtils; import java.util.regex.Pattern; @@ -19,7 +21,10 @@ */ final class PrometheusUnitsHelper { + private static final Pattern INVALID_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); private static final Pattern CHARACTERS_BETWEEN_BRACES_PATTERN = Pattern.compile("\\{(.*?)}"); + private static final Pattern SANITIZE_LEADING_UNDERSCORES = Pattern.compile("^_+"); + private static final Pattern SANITIZE_TRAILING_UNDERSCORES = Pattern.compile("_+$"); private PrometheusUnitsHelper() { // Prevent object creation for utility classes @@ -27,15 +32,11 @@ private PrometheusUnitsHelper() { /** * A utility function that returns the equivalent Prometheus name for the provided OTLP metric - * unit. This function does not handle the unsupported characters that it may find within the - * string. + * unit. * * @param rawMetricUnitName The raw metric unit for which Prometheus metric unit needs to be * computed. - * @return the computed Prometheus metric unit equivalent of the OTLP metric unit. - * @see Prometheus - * metric names and labels for supported characters. + * @return the computed Prometheus metric unit equivalent of the OTLP metric un */ public static String getEquivalentPrometheusUnit(String rawMetricUnitName) { if (StringUtils.isNullOrEmpty(rawMetricUnitName)) { @@ -46,7 +47,7 @@ public static String getEquivalentPrometheusUnit(String rawMetricUnitName) { // Handling for the "per" unit(s), e.g. foo/bar -> foo_per_bar convertedMetricUnitName = convertRateExpressedToPrometheusUnit(convertedMetricUnitName); // Converting abbreviated unit names to full names - return getPrometheusUnit(convertedMetricUnitName); + return cleanUpString(getPrometheusUnit(convertedMetricUnitName)); } /** @@ -86,6 +87,26 @@ private static String removeUnitPortionInBraces(String unit) { return CHARACTERS_BETWEEN_BRACES_PATTERN.matcher(unit).replaceAll(""); } + /** + * Replaces all characters that are not a letter or a digit with '_' to make the resulting string + * Prometheus compliant. This method also removes leading and trailing underscores - this is done + * to keep the resulting unit similar to what is produced from the collector's implementation. + * + * @param string The string input that needs to be made Prometheus compliant. + * @return the cleaned-up Prometheus compliant string. + */ + private static String cleanUpString(String string) { + return SANITIZE_LEADING_UNDERSCORES + .matcher( + SANITIZE_TRAILING_UNDERSCORES + .matcher( + SANITIZE_CONSECUTIVE_UNDERSCORES + .matcher(INVALID_CHARACTERS_PATTERN.matcher(string).replaceAll("_")) + .replaceAll("_")) + .replaceAll("")) + .replaceAll(""); + } + /** * This method retrieves the expanded Prometheus unit name for known abbreviations. OTLP metrics * use the c/s notation as specified at UCUM. The list of diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java index 61e53111d78..a510e726215 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -76,9 +76,9 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { // Special Case - Dropped metric units only in {} Arguments.of("{packets}V", "volts"), // Special Case - Dropped metric units with 'per' unit handling applicable - Arguments.of("{scanned}/{returned}", "/"), + Arguments.of("{scanned}/{returned}", ""), // Special Case - Dropped metric units with 'per' unit handling applicable - Arguments.of("{objects}/s", "_per_second"), + Arguments.of("{objects}/s", "per_second"), // Units expressing rate - 'per' units, both units expanded Arguments.of("m/s", "meters_per_second"), // Units expressing rate - per minute @@ -100,16 +100,16 @@ private static Stream providePrometheusOTelUnitEquivalentPairs() { // Misc - unit containing known abbreviations improperly formatted Arguments.of("watts_W", "watts_W"), // Unsupported symbols - Arguments.of("°F", "°F"), + Arguments.of("°F", "F"), // Unsupported symbols - multiple - Arguments.of("unit+=.:,!* & #unused", "unit+=.:,!* & #unused"), + Arguments.of("unit+=.:,!* & #unused", "unit_unused"), // Unsupported symbols - 'per' units - Arguments.of("__test $/°C", "__test $_per_°C"), + Arguments.of("__test $/°C", "test_per_C"), // Unsupported symbols - whitespace - Arguments.of("\t", "\t"), + Arguments.of("\t", ""), // Null unit Arguments.of(null, null), // Misc - unit cleanup - no case match special char - Arguments.of("$1000", "$1000")); + Arguments.of("$1000", "1000")); } } From 0cd3aab9b1beb58c5f40343f66902aa5e7f1abfd Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Sat, 29 Apr 2023 16:17:26 +0000 Subject: [PATCH 18/23] Appends units if not present in metric name --- .../exporter/prometheus/Serializer.java | 13 ++++--- .../exporter/prometheus/SerializerTest.java | 37 ++++++++++++++++++- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java index fde8cdbd597..dca13da5f52 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java @@ -653,24 +653,27 @@ static Collection getPoints(MetricData metricData) { // Visible for testing static String metricName(MetricData rawMetric, PrometheusType type) { - String name = rawMetric.getName(); + String name = NameSanitizer.INSTANCE.apply(rawMetric.getName()); String prometheusEquivalentUnit = PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit()); // append prometheus unit if not null or empty. - if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit)) { + if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit) + && !name.contains(prometheusEquivalentUnit)) { name = name + "_" + prometheusEquivalentUnit; } // special case - counter - if (type == PrometheusType.COUNTER) { + if (type == PrometheusType.COUNTER && !name.contains("total")) { name = name + "_total"; } // special case - gauge - if (rawMetric.getUnit().equals("1") && type == PrometheusType.GAUGE) { + if (rawMetric.getUnit().equals("1") + && type == PrometheusType.GAUGE + && !name.contains("ratio")) { name = name + "_ratio"; } - return NameSanitizer.INSTANCE.apply(name); + return name; } private static double getExemplarValue(ExemplarData exemplar) { diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index 15e004735ed..e9776a3edb8 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -486,7 +486,7 @@ private static Stream provideRawMetricDataForTest() { // special case for gauge Arguments.of( sampleMetricDataGenerator("sample", "1", PrometheusType.GAUGE), "sample_ratio"), - // special case for gauge with drop eligible unit + // special case for gauge with drop - metric unit should match "1" Arguments.of( sampleMetricDataGenerator("sample", "1{dropped}", PrometheusType.GAUGE), "sample"), // Gauge without "1" as unit @@ -496,9 +496,12 @@ private static Stream provideRawMetricDataForTest() { Arguments.of( sampleMetricDataGenerator("sample", "unit", PrometheusType.COUNTER), "sample_unit_total"), - // special case unit "1", but no gauge + // special case unit "1", but no gauge - "1" is dropped Arguments.of( sampleMetricDataGenerator("sample", "1", PrometheusType.COUNTER), "sample_total"), + // units expressed as numbers other than 1 are retained + Arguments.of( + sampleMetricDataGenerator("sample", "2", PrometheusType.COUNTER), "sample_2_total"), // metric name with unsupported characters Arguments.of( sampleMetricDataGenerator("s%%ple", "%/m", PrometheusType.SUMMARY), @@ -510,6 +513,36 @@ private static Stream provideRawMetricDataForTest() { // metric unit as a number other than 1 is not treated specially Arguments.of( sampleMetricDataGenerator("metric_name", "2", PrometheusType.SUMMARY), "metric_name_2"), + // metric unit is not appended if the name already contains the unit + Arguments.of( + sampleMetricDataGenerator("metric_name_total", "total", PrometheusType.COUNTER), + "metric_name_total"), + // metric unit is not appended if the name already contains the unit - special case for + // total with non-counter type + Arguments.of( + sampleMetricDataGenerator("metric_name_total", "total", PrometheusType.SUMMARY), + "metric_name_total"), + // metric unit not appended if present in metric name - special case for ratio + Arguments.of( + sampleMetricDataGenerator("metric_name_ratio", "1", PrometheusType.GAUGE), + "metric_name_ratio"), + // metric unit not appended if present in metric name - special case for ratio - unit not + // gauge + Arguments.of( + sampleMetricDataGenerator("metric_name_ratio", "1", PrometheusType.SUMMARY), + "metric_name_ratio"), + // metric unit is not appended if the name already contains the unit - unit can be anywhere + Arguments.of( + sampleMetricDataGenerator("metric_hertz", "hertz", PrometheusType.GAUGE), + "metric_hertz"), + // metric unit is not appended if the name already contains the unit - applies to every unit + Arguments.of( + sampleMetricDataGenerator("metric_hertz_total", "hertz_total", PrometheusType.COUNTER), + "metric_hertz_total"), + // metric unit is not appended if the name already contains the unit - order matters + Arguments.of( + sampleMetricDataGenerator("metric_total_hertz", "hertz_total", PrometheusType.COUNTER), + "metric_total_hertz_hertz_total"), // metric name cannot start with a number Arguments.of( sampleMetricDataGenerator("2_metric_name", "By", PrometheusType.SUMMARY), From 7bf71a2ba2f294ae7346bda9547a1bec035493f7 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 2 Jun 2023 18:58:46 +0000 Subject: [PATCH 19/23] Convert public method to package-private --- .../exporter/prometheus/PrometheusUnitsHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index e72216ba474..b4ba94d4709 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -38,7 +38,7 @@ private PrometheusUnitsHelper() { * computed. * @return the computed Prometheus metric unit equivalent of the OTLP metric un */ - public static String getEquivalentPrometheusUnit(String rawMetricUnitName) { + static String getEquivalentPrometheusUnit(String rawMetricUnitName) { if (StringUtils.isNullOrEmpty(rawMetricUnitName)) { return rawMetricUnitName; } From 0f2c3c107e2dc63231634e84cc4dc3b30144c9f5 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 2 Jun 2023 20:35:40 +0000 Subject: [PATCH 20/23] Rename method sampleMetricDataGenerator -> createSampleMetricData --- .../exporter/prometheus/SerializerTest.java | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index 3c299e6ff65..86c166528e8 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -512,68 +512,63 @@ void metricNameSerializationTest(MetricData metricData, String expectedSerialize private static Stream provideRawMetricDataForTest() { return Stream.of( // special case for gauge - Arguments.of( - sampleMetricDataGenerator("sample", "1", PrometheusType.GAUGE), "sample_ratio"), + Arguments.of(createSampleMetricData("sample", "1", PrometheusType.GAUGE), "sample_ratio"), // special case for gauge with drop - metric unit should match "1" Arguments.of( - sampleMetricDataGenerator("sample", "1{dropped}", PrometheusType.GAUGE), "sample"), + createSampleMetricData("sample", "1{dropped}", PrometheusType.GAUGE), "sample"), // Gauge without "1" as unit - Arguments.of( - sampleMetricDataGenerator("sample", "unit", PrometheusType.GAUGE), "sample_unit"), + Arguments.of(createSampleMetricData("sample", "unit", PrometheusType.GAUGE), "sample_unit"), // special case with counter Arguments.of( - sampleMetricDataGenerator("sample", "unit", PrometheusType.COUNTER), - "sample_unit_total"), + createSampleMetricData("sample", "unit", PrometheusType.COUNTER), "sample_unit_total"), // special case unit "1", but no gauge - "1" is dropped - Arguments.of( - sampleMetricDataGenerator("sample", "1", PrometheusType.COUNTER), "sample_total"), + Arguments.of(createSampleMetricData("sample", "1", PrometheusType.COUNTER), "sample_total"), // units expressed as numbers other than 1 are retained Arguments.of( - sampleMetricDataGenerator("sample", "2", PrometheusType.COUNTER), "sample_2_total"), + createSampleMetricData("sample", "2", PrometheusType.COUNTER), "sample_2_total"), // metric name with unsupported characters Arguments.of( - sampleMetricDataGenerator("s%%ple", "%/m", PrometheusType.SUMMARY), + createSampleMetricData("s%%ple", "%/m", PrometheusType.SUMMARY), "s_ple_percent_per_minute"), // metric name with dropped portions Arguments.of( - sampleMetricDataGenerator("s%%ple", "%/m", PrometheusType.SUMMARY), + createSampleMetricData("s%%ple", "%/m", PrometheusType.SUMMARY), "s_ple_percent_per_minute"), // metric unit as a number other than 1 is not treated specially Arguments.of( - sampleMetricDataGenerator("metric_name", "2", PrometheusType.SUMMARY), "metric_name_2"), + createSampleMetricData("metric_name", "2", PrometheusType.SUMMARY), "metric_name_2"), // metric unit is not appended if the name already contains the unit Arguments.of( - sampleMetricDataGenerator("metric_name_total", "total", PrometheusType.COUNTER), + createSampleMetricData("metric_name_total", "total", PrometheusType.COUNTER), "metric_name_total"), // metric unit is not appended if the name already contains the unit - special case for // total with non-counter type Arguments.of( - sampleMetricDataGenerator("metric_name_total", "total", PrometheusType.SUMMARY), + createSampleMetricData("metric_name_total", "total", PrometheusType.SUMMARY), "metric_name_total"), // metric unit not appended if present in metric name - special case for ratio Arguments.of( - sampleMetricDataGenerator("metric_name_ratio", "1", PrometheusType.GAUGE), + createSampleMetricData("metric_name_ratio", "1", PrometheusType.GAUGE), "metric_name_ratio"), // metric unit not appended if present in metric name - special case for ratio - unit not // gauge Arguments.of( - sampleMetricDataGenerator("metric_name_ratio", "1", PrometheusType.SUMMARY), + createSampleMetricData("metric_name_ratio", "1", PrometheusType.SUMMARY), "metric_name_ratio"), // metric unit is not appended if the name already contains the unit - unit can be anywhere Arguments.of( - sampleMetricDataGenerator("metric_hertz", "hertz", PrometheusType.GAUGE), - "metric_hertz"), + createSampleMetricData("metric_hertz", "hertz", PrometheusType.GAUGE), "metric_hertz"), // metric unit is not appended if the name already contains the unit - applies to every unit Arguments.of( - sampleMetricDataGenerator("metric_hertz_total", "hertz_total", PrometheusType.COUNTER), + createSampleMetricData("metric_hertz_total", "hertz_total", PrometheusType.COUNTER), "metric_hertz_total"), // metric unit is not appended if the name already contains the unit - order matters Arguments.of( - sampleMetricDataGenerator("metric_total_hertz", "hertz_total", PrometheusType.COUNTER), + createSampleMetricData("metric_total_hertz", "hertz_total", PrometheusType.COUNTER), "metric_total_hertz_hertz_total"), // metric name cannot start with a number Arguments.of( - sampleMetricDataGenerator("2_metric_name", "By", PrometheusType.SUMMARY), + createSampleMetricData("2_metric_name", "By", PrometheusType.SUMMARY), "_metric_name_bytes")); } @@ -597,7 +592,7 @@ private static String serializeOpenMetrics(MetricData... metrics) { } } - private static MetricData sampleMetricDataGenerator( + private static MetricData createSampleMetricData( String metricName, String metricUnit, PrometheusType prometheusType) { switch (prometheusType) { case SUMMARY: From 38ee5a8eaea0e33bc10480d0ec91fd3453fdbd74 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Sat, 3 Jun 2023 19:38:58 +0000 Subject: [PATCH 21/23] Apply caching mechanism to prometheus metric name mapping --- .../PrometheusMetricNameMapper.java | 75 +++ .../exporter/prometheus/Serializer.java | 28 +- .../PrometheusMetricNameMapperTest.java | 163 ++++++ .../exporter/prometheus/SerializerTest.java | 463 +----------------- .../exporter/prometheus/TestConstants.java | 360 ++++++++++++++ 5 files changed, 614 insertions(+), 475 deletions(-) create mode 100644 exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java create mode 100644 exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapperTest.java create mode 100644 exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java new file mode 100644 index 00000000000..a9f2defea01 --- /dev/null +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.sdk.metrics.data.MetricData; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; + +/** A class that maps a raw metric name to Prometheus equivalent name. */ +class PrometheusMetricNameMapper implements BiFunction { + + static final PrometheusMetricNameMapper INSTANCE = new PrometheusMetricNameMapper(); + + private final Map cache = new ConcurrentHashMap<>(); + private final BiFunction delegate; + + // private constructor - prevent external object initialization + private PrometheusMetricNameMapper() { + this(PrometheusMetricNameMapper::mapToPrometheusName); + } + + // Visible for testing + PrometheusMetricNameMapper(BiFunction delegate) { + this.delegate = delegate; + } + + @Override + public String apply(MetricData rawMetric, PrometheusType prometheusType) { + return cache.computeIfAbsent( + createKeyForCacheMapping(rawMetric, prometheusType), + metricData -> delegate.apply(rawMetric, prometheusType)); + } + + private static String mapToPrometheusName(MetricData rawMetric, PrometheusType prometheusType) { + String name = NameSanitizer.INSTANCE.apply(rawMetric.getName()); + String prometheusEquivalentUnit = + PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit()); + // append prometheus unit if not null or empty. + if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit) + && !name.contains(prometheusEquivalentUnit)) { + name = name + "_" + prometheusEquivalentUnit; + } + + // special case - counter + if (prometheusType == PrometheusType.COUNTER && !name.contains("total")) { + name = name + "_total"; + } + // special case - gauge + if (rawMetric.getUnit().equals("1") + && prometheusType == PrometheusType.GAUGE + && !name.contains("ratio")) { + name = name + "_ratio"; + } + return name; + } + + /** + * Create key from a combination of raw metric name, raw metric unit and the prometheus type since + * all of them are used to compute the prometheus equivalent name. + * + * @param metricData the metric data for which the mapping is to be created. + * @param prometheusType the prometheus type to which the metric is to be mapped. + * @return a String that acts as the key for mapping between metric data and its prometheus + * equivalent name. + */ + private static String createKeyForCacheMapping( + MetricData metricData, PrometheusType prometheusType) { + return metricData.getName() + metricData.getUnit() + prometheusType.name(); + } +} diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java index 50fd54aca43..969b0192385 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Serializer.java @@ -23,7 +23,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.internal.StringUtils; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -119,7 +118,7 @@ final Set write(Collection metrics, OutputStream output) thr continue; } PrometheusType prometheusType = PrometheusType.forMetric(metric); - String metricName = metricName(metric, prometheusType); + String metricName = PrometheusMetricNameMapper.INSTANCE.apply(metric, prometheusType); // Skip metrics which do not pass metricNameFilter if (!metricNameFilter.test(metricName)) { continue; @@ -651,31 +650,6 @@ static Collection getPoints(MetricData metricData) { return Collections.emptyList(); } - // Visible for testing - static String metricName(MetricData rawMetric, PrometheusType type) { - String name = NameSanitizer.INSTANCE.apply(rawMetric.getName()); - String prometheusEquivalentUnit = - PrometheusUnitsHelper.getEquivalentPrometheusUnit(rawMetric.getUnit()); - // append prometheus unit if not null or empty. - if (!StringUtils.isNullOrEmpty(prometheusEquivalentUnit) - && !name.contains(prometheusEquivalentUnit)) { - name = name + "_" + prometheusEquivalentUnit; - } - - // special case - counter - if (type == PrometheusType.COUNTER && !name.contains("total")) { - name = name + "_total"; - } - // special case - gauge - if (rawMetric.getUnit().equals("1") - && type == PrometheusType.GAUGE - && !name.contains("ratio")) { - name = name + "_ratio"; - } - - return name; - } - private static double getExemplarValue(ExemplarData exemplar) { return exemplar instanceof DoubleExemplarData ? ((DoubleExemplarData) exemplar).getValue() diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapperTest.java new file mode 100644 index 00000000000..1d275d3464f --- /dev/null +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapperTest.java @@ -0,0 +1,163 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +import static io.opentelemetry.exporter.prometheus.TestConstants.DELTA_HISTOGRAM; +import static io.opentelemetry.exporter.prometheus.TestConstants.DOUBLE_GAUGE; +import static io.opentelemetry.exporter.prometheus.TestConstants.MONOTONIC_CUMULATIVE_LONG_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.SUMMARY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +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 PrometheusMetricNameMapperTest { + + @Test + void prometheusMetricNameMapperCaching() { + AtomicInteger count = new AtomicInteger(); + BiFunction delegate = + (metricData, prometheusType) -> + String.join( + "_", + metricData.getName(), + prometheusType.name(), + Integer.toString(count.incrementAndGet())); + PrometheusMetricNameMapper mapper = new PrometheusMetricNameMapper(delegate); + + assertThat(mapper.apply(MONOTONIC_CUMULATIVE_LONG_SUM, PrometheusType.GAUGE)) + .isEqualTo("monotonic.cumulative.long.sum_GAUGE_1"); + assertThat(mapper.apply(MONOTONIC_CUMULATIVE_LONG_SUM, PrometheusType.GAUGE)) + .isEqualTo("monotonic.cumulative.long.sum_GAUGE_1"); + assertThat(mapper.apply(MONOTONIC_CUMULATIVE_LONG_SUM, PrometheusType.GAUGE)) + .isEqualTo("monotonic.cumulative.long.sum_GAUGE_1"); + assertThat(mapper.apply(MONOTONIC_CUMULATIVE_LONG_SUM, PrometheusType.GAUGE)) + .isEqualTo("monotonic.cumulative.long.sum_GAUGE_1"); + assertThat(mapper.apply(MONOTONIC_CUMULATIVE_LONG_SUM, PrometheusType.GAUGE)) + .isEqualTo("monotonic.cumulative.long.sum_GAUGE_1"); + assertThat(count).hasValue(1); + } + + @ParameterizedTest + @MethodSource("provideRawMetricDataForTest") + void metricNameSerializationTest(MetricData metricData, String expectedSerializedName) { + assertEquals( + expectedSerializedName, + PrometheusMetricNameMapper.INSTANCE.apply( + metricData, PrometheusType.forMetric(metricData))); + } + + private static Stream provideRawMetricDataForTest() { + return Stream.of( + // special case for gauge + Arguments.of(createSampleMetricData("sample", "1", PrometheusType.GAUGE), "sample_ratio"), + // special case for gauge with drop - metric unit should match "1" to be converted to + // "ratio" + Arguments.of( + createSampleMetricData("sample", "1{dropped}", PrometheusType.GAUGE), "sample"), + // Gauge without "1" as unit + Arguments.of(createSampleMetricData("sample", "unit", PrometheusType.GAUGE), "sample_unit"), + // special case with counter + Arguments.of( + createSampleMetricData("sample", "unit", PrometheusType.COUNTER), "sample_unit_total"), + // special case unit "1", but no gauge - "1" is dropped + Arguments.of(createSampleMetricData("sample", "1", PrometheusType.COUNTER), "sample_total"), + // units expressed as numbers other than 1 are retained + Arguments.of( + createSampleMetricData("sample", "2", PrometheusType.COUNTER), "sample_2_total"), + // metric name with unsupported characters + Arguments.of( + createSampleMetricData("s%%ple", "%/m", PrometheusType.SUMMARY), + "s_ple_percent_per_minute"), + // metric name with dropped portions + Arguments.of( + createSampleMetricData("s%%ple", "%/m", PrometheusType.SUMMARY), + "s_ple_percent_per_minute"), + // metric unit as a number other than 1 is not treated specially + Arguments.of( + createSampleMetricData("metric_name", "2", PrometheusType.SUMMARY), "metric_name_2"), + // metric unit is not appended if the name already contains the unit + Arguments.of( + createSampleMetricData("metric_name_total", "total", PrometheusType.COUNTER), + "metric_name_total"), + // metric unit is not appended if the name already contains the unit - special case for + // total with non-counter type + Arguments.of( + createSampleMetricData("metric_name_total", "total", PrometheusType.SUMMARY), + "metric_name_total"), + // metric unit not appended if present in metric name - special case for ratio + Arguments.of( + createSampleMetricData("metric_name_ratio", "1", PrometheusType.GAUGE), + "metric_name_ratio"), + // metric unit not appended if present in metric name - special case for ratio - unit not + // gauge + Arguments.of( + createSampleMetricData("metric_name_ratio", "1", PrometheusType.SUMMARY), + "metric_name_ratio"), + // metric unit is not appended if the name already contains the unit - unit can be anywhere + Arguments.of( + createSampleMetricData("metric_hertz", "hertz", PrometheusType.GAUGE), "metric_hertz"), + // metric unit is not appended if the name already contains the unit - applies to every unit + Arguments.of( + createSampleMetricData("metric_hertz_total", "hertz_total", PrometheusType.COUNTER), + "metric_hertz_total"), + // metric unit is not appended if the name already contains the unit - order matters + Arguments.of( + createSampleMetricData("metric_total_hertz", "hertz_total", PrometheusType.COUNTER), + "metric_total_hertz_hertz_total"), + // metric name cannot start with a number + Arguments.of( + createSampleMetricData("2_metric_name", "By", PrometheusType.SUMMARY), + "_metric_name_bytes")); + } + + static MetricData createSampleMetricData( + String metricName, String metricUnit, PrometheusType prometheusType) { + switch (prometheusType) { + case SUMMARY: + return ImmutableMetricData.createDoubleSummary( + SUMMARY.getResource(), + SUMMARY.getInstrumentationScopeInfo(), + metricName, + SUMMARY.getDescription(), + metricUnit, + SUMMARY.getSummaryData()); + case COUNTER: + return ImmutableMetricData.createLongSum( + MONOTONIC_CUMULATIVE_LONG_SUM.getResource(), + MONOTONIC_CUMULATIVE_LONG_SUM.getInstrumentationScopeInfo(), + metricName, + MONOTONIC_CUMULATIVE_LONG_SUM.getDescription(), + metricUnit, + MONOTONIC_CUMULATIVE_LONG_SUM.getLongSumData()); + case GAUGE: + return ImmutableMetricData.createDoubleGauge( + DOUBLE_GAUGE.getResource(), + DOUBLE_GAUGE.getInstrumentationScopeInfo(), + metricName, + DOUBLE_GAUGE.getDescription(), + metricUnit, + DOUBLE_GAUGE.getDoubleGaugeData()); + case HISTOGRAM: + return ImmutableMetricData.createDoubleHistogram( + DELTA_HISTOGRAM.getResource(), + DELTA_HISTOGRAM.getInstrumentationScopeInfo(), + metricName, + DELTA_HISTOGRAM.getDescription(), + metricUnit, + DELTA_HISTOGRAM.getHistogramData()); + } + throw new IllegalArgumentException(); + } +} diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index 86c166528e8..fdf3e2eef3b 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -5,355 +5,32 @@ package io.opentelemetry.exporter.prometheus; -import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.exporter.prometheus.TestConstants.CUMULATIVE_HISTOGRAM_NO_ATTRIBUTES; +import static io.opentelemetry.exporter.prometheus.TestConstants.CUMULATIVE_HISTOGRAM_SINGLE_ATTRIBUTE; +import static io.opentelemetry.exporter.prometheus.TestConstants.DELTA_DOUBLE_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.DELTA_HISTOGRAM; +import static io.opentelemetry.exporter.prometheus.TestConstants.DELTA_LONG_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.DOUBLE_GAUGE; +import static io.opentelemetry.exporter.prometheus.TestConstants.DOUBLE_GAUGE_MULTIPLE_ATTRIBUTES; +import static io.opentelemetry.exporter.prometheus.TestConstants.DOUBLE_GAUGE_NO_ATTRIBUTES; +import static io.opentelemetry.exporter.prometheus.TestConstants.LONG_GAUGE; +import static io.opentelemetry.exporter.prometheus.TestConstants.MONOTONIC_CUMULATIVE_DOUBLE_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.MONOTONIC_CUMULATIVE_DOUBLE_SUM_WITH_SUFFIX_TOTAL; +import static io.opentelemetry.exporter.prometheus.TestConstants.MONOTONIC_CUMULATIVE_LONG_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.NON_MONOTONIC_CUMULATIVE_DOUBLE_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.NON_MONOTONIC_CUMULATIVE_LONG_SUM; +import static io.opentelemetry.exporter.prometheus.TestConstants.SUMMARY; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoubleExemplarData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableValueAtQuantile; -import io.opentelemetry.sdk.resources.Resource; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Arrays; -import java.util.Collections; -import java.util.concurrent.TimeUnit; -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 SerializerTest { - private static final AttributeKey TYPE = stringKey("type"); - - private static final MetricData MONOTONIC_CUMULATIVE_DOUBLE_SUM = - ImmutableMetricData.createDoubleSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "monotonic.cumulative.double.sum", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "mcds"), - 5)))); - - private static final MetricData MONOTONIC_CUMULATIVE_DOUBLE_SUM_WITH_SUFFIX_TOTAL = - ImmutableMetricData.createDoubleSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "monotonic.cumulative.double.sum.suffix.total", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "mcds"), - 5)))); - private static final MetricData NON_MONOTONIC_CUMULATIVE_DOUBLE_SUM = - ImmutableMetricData.createDoubleSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "non.monotonic.cumulative.double.sum", - "description", - "1", - ImmutableSumData.create( - /* isMonotonic= */ false, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "nmcds"), - 5)))); - private static final MetricData DELTA_DOUBLE_SUM = - ImmutableMetricData.createDoubleSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "delta.double.sum", - "unused", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.DELTA, - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "mdds"), - 5)))); - private static final MetricData MONOTONIC_CUMULATIVE_LONG_SUM = - ImmutableMetricData.createLongSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "monotonic.cumulative.long.sum", - "unused", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "mcls"), - 5)))); - private static final MetricData NON_MONOTONIC_CUMULATIVE_LONG_SUM = - ImmutableMetricData.createLongSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "non.monotonic.cumulative.long_sum", - "unused", - "1", - ImmutableSumData.create( - /* isMonotonic= */ false, - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableLongPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "nmcls"), - 5)))); - private static final MetricData DELTA_LONG_SUM = - ImmutableMetricData.createLongSum( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "delta.long.sum", - "unused", - "1", - ImmutableSumData.create( - /* isMonotonic= */ true, - AggregationTemporality.DELTA, - Collections.singletonList( - ImmutableLongPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "mdls"), - 5)))); - - private static final MetricData DOUBLE_GAUGE = - ImmutableMetricData.createDoubleGauge( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "double.gauge", - "unused", - "1", - ImmutableGaugeData.create( - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, 1633950672000000000L, Attributes.of(TYPE, "dg"), 5)))); - private static final MetricData LONG_GAUGE = - ImmutableMetricData.createLongGauge( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "long.gauge", - "unused", - "1", - ImmutableGaugeData.create( - Collections.singletonList( - ImmutableLongPointData.create( - 1633947011000000000L, 1633950672000000000L, Attributes.of(TYPE, "lg"), 5)))); - private static final MetricData SUMMARY = - ImmutableMetricData.createDoubleSummary( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "summary", - "unused", - "1", - ImmutableSummaryData.create( - Collections.singletonList( - ImmutableSummaryPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "s"), - 5, - 7, - Arrays.asList( - ImmutableValueAtQuantile.create(0.9, 0.1), - ImmutableValueAtQuantile.create(0.99, 0.3)))))); - private static final MetricData DELTA_HISTOGRAM = - ImmutableMetricData.createDoubleHistogram( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "delta.histogram", - "unused", - "1", - ImmutableHistogramData.create( - AggregationTemporality.DELTA, - Collections.singletonList( - ImmutableHistogramPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.empty(), - 1.0, - /* hasMin= */ false, - 0, - /* hasMax= */ false, - 0, - Collections.emptyList(), - Collections.singletonList(2L), - Collections.emptyList())))); - private static final MetricData CUMULATIVE_HISTOGRAM_NO_ATTRIBUTES = - ImmutableMetricData.createDoubleHistogram( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "cumulative.histogram.no.attributes", - "unused", - "1", - ImmutableHistogramData.create( - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableHistogramPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.empty(), - 1.0, - /* hasMin= */ false, - 0, - /* hasMax= */ false, - 0, - Collections.emptyList(), - Collections.singletonList(2L), - Collections.singletonList( - ImmutableDoubleExemplarData.create( - Attributes.empty(), - TimeUnit.MILLISECONDS.toNanos(1L), - SpanContext.create( - "00000000000000000000000000000001", - "0000000000000002", - TraceFlags.getDefault(), - TraceState.getDefault()), - /* value= */ 4)))))); - private static final MetricData CUMULATIVE_HISTOGRAM_SINGLE_ATTRIBUTE = - ImmutableMetricData.createDoubleHistogram( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "cumulative.histogram.single.attribute", - "unused", - "1", - ImmutableHistogramData.create( - AggregationTemporality.CUMULATIVE, - Collections.singletonList( - ImmutableHistogramPointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "hs"), - 1.0, - /* hasMin= */ false, - 0, - /* hasMax= */ false, - 0, - Collections.emptyList(), - Collections.singletonList(2L), - Collections.singletonList( - ImmutableDoubleExemplarData.create( - Attributes.empty(), - TimeUnit.MILLISECONDS.toNanos(1L), - SpanContext.create( - "00000000000000000000000000000001", - "0000000000000002", - TraceFlags.getDefault(), - TraceState.getDefault()), - /* value= */ 4)))))); - private static final MetricData DOUBLE_GAUGE_NO_ATTRIBUTES = - ImmutableMetricData.createDoubleGauge( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "double.gauge.no.attributes", - "unused", - "1", - ImmutableGaugeData.create( - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, 1633950672000000000L, Attributes.empty(), 7)))); - private static final MetricData DOUBLE_GAUGE_MULTIPLE_ATTRIBUTES = - ImmutableMetricData.createDoubleGauge( - Resource.create(Attributes.of(stringKey("kr"), "vr")), - InstrumentationScopeInfo.builder("full") - .setVersion("version") - .setAttributes(Attributes.of(stringKey("ks"), "vs")) - .build(), - "double.gauge.multiple.attributes", - "unused", - "1", - ImmutableGaugeData.create( - Collections.singletonList( - ImmutableDoublePointData.create( - 1633947011000000000L, - 1633950672000000000L, - Attributes.of(TYPE, "dgma", stringKey("animal"), "bear"), - 8)))); - @Test void prometheus004() { // Same output as prometheus client library except for these changes which are compatible with @@ -501,77 +178,6 @@ void openMetrics() { + "# EOF\n"); } - @ParameterizedTest - @MethodSource("provideRawMetricDataForTest") - void metricNameSerializationTest(MetricData metricData, String expectedSerializedName) { - assertEquals( - expectedSerializedName, - Serializer.metricName(metricData, PrometheusType.forMetric(metricData))); - } - - private static Stream provideRawMetricDataForTest() { - return Stream.of( - // special case for gauge - Arguments.of(createSampleMetricData("sample", "1", PrometheusType.GAUGE), "sample_ratio"), - // special case for gauge with drop - metric unit should match "1" - Arguments.of( - createSampleMetricData("sample", "1{dropped}", PrometheusType.GAUGE), "sample"), - // Gauge without "1" as unit - Arguments.of(createSampleMetricData("sample", "unit", PrometheusType.GAUGE), "sample_unit"), - // special case with counter - Arguments.of( - createSampleMetricData("sample", "unit", PrometheusType.COUNTER), "sample_unit_total"), - // special case unit "1", but no gauge - "1" is dropped - Arguments.of(createSampleMetricData("sample", "1", PrometheusType.COUNTER), "sample_total"), - // units expressed as numbers other than 1 are retained - Arguments.of( - createSampleMetricData("sample", "2", PrometheusType.COUNTER), "sample_2_total"), - // metric name with unsupported characters - Arguments.of( - createSampleMetricData("s%%ple", "%/m", PrometheusType.SUMMARY), - "s_ple_percent_per_minute"), - // metric name with dropped portions - Arguments.of( - createSampleMetricData("s%%ple", "%/m", PrometheusType.SUMMARY), - "s_ple_percent_per_minute"), - // metric unit as a number other than 1 is not treated specially - Arguments.of( - createSampleMetricData("metric_name", "2", PrometheusType.SUMMARY), "metric_name_2"), - // metric unit is not appended if the name already contains the unit - Arguments.of( - createSampleMetricData("metric_name_total", "total", PrometheusType.COUNTER), - "metric_name_total"), - // metric unit is not appended if the name already contains the unit - special case for - // total with non-counter type - Arguments.of( - createSampleMetricData("metric_name_total", "total", PrometheusType.SUMMARY), - "metric_name_total"), - // metric unit not appended if present in metric name - special case for ratio - Arguments.of( - createSampleMetricData("metric_name_ratio", "1", PrometheusType.GAUGE), - "metric_name_ratio"), - // metric unit not appended if present in metric name - special case for ratio - unit not - // gauge - Arguments.of( - createSampleMetricData("metric_name_ratio", "1", PrometheusType.SUMMARY), - "metric_name_ratio"), - // metric unit is not appended if the name already contains the unit - unit can be anywhere - Arguments.of( - createSampleMetricData("metric_hertz", "hertz", PrometheusType.GAUGE), "metric_hertz"), - // metric unit is not appended if the name already contains the unit - applies to every unit - Arguments.of( - createSampleMetricData("metric_hertz_total", "hertz_total", PrometheusType.COUNTER), - "metric_hertz_total"), - // metric unit is not appended if the name already contains the unit - order matters - Arguments.of( - createSampleMetricData("metric_total_hertz", "hertz_total", PrometheusType.COUNTER), - "metric_total_hertz_hertz_total"), - // metric name cannot start with a number - Arguments.of( - createSampleMetricData("2_metric_name", "By", PrometheusType.SUMMARY), - "_metric_name_bytes")); - } - private static String serialize004(MetricData... metrics) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { @@ -591,43 +197,4 @@ private static String serializeOpenMetrics(MetricData... metrics) { throw new UncheckedIOException(e); } } - - private static MetricData createSampleMetricData( - String metricName, String metricUnit, PrometheusType prometheusType) { - switch (prometheusType) { - case SUMMARY: - return ImmutableMetricData.createDoubleSummary( - SUMMARY.getResource(), - SUMMARY.getInstrumentationScopeInfo(), - metricName, - SUMMARY.getDescription(), - metricUnit, - SUMMARY.getSummaryData()); - case COUNTER: - return ImmutableMetricData.createLongSum( - MONOTONIC_CUMULATIVE_LONG_SUM.getResource(), - MONOTONIC_CUMULATIVE_LONG_SUM.getInstrumentationScopeInfo(), - metricName, - MONOTONIC_CUMULATIVE_LONG_SUM.getDescription(), - metricUnit, - MONOTONIC_CUMULATIVE_LONG_SUM.getLongSumData()); - case GAUGE: - return ImmutableMetricData.createDoubleGauge( - DOUBLE_GAUGE.getResource(), - DOUBLE_GAUGE.getInstrumentationScopeInfo(), - metricName, - DOUBLE_GAUGE.getDescription(), - metricUnit, - DOUBLE_GAUGE.getDoubleGaugeData()); - case HISTOGRAM: - return ImmutableMetricData.createDoubleHistogram( - DELTA_HISTOGRAM.getResource(), - DELTA_HISTOGRAM.getInstrumentationScopeInfo(), - metricName, - DELTA_HISTOGRAM.getDescription(), - metricUnit, - DELTA_HISTOGRAM.getHistogramData()); - } - throw new IllegalArgumentException(); - } } diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java new file mode 100644 index 00000000000..0c004e81ec0 --- /dev/null +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java @@ -0,0 +1,360 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoubleExemplarData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableValueAtQuantile; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +/** A helper class encapsulating immutable static data that can be shared across all the tests. */ +class TestConstants { + + private TestConstants() { + // Private constructor to prevent instantiation + } + + private static final AttributeKey TYPE = stringKey("type"); + + static final MetricData MONOTONIC_CUMULATIVE_DOUBLE_SUM = + ImmutableMetricData.createDoubleSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "monotonic.cumulative.double.sum", + "description", + "1", + ImmutableSumData.create( + /* isMonotonic= */ true, + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "mcds"), + 5)))); + + static final MetricData MONOTONIC_CUMULATIVE_DOUBLE_SUM_WITH_SUFFIX_TOTAL = + ImmutableMetricData.createDoubleSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "monotonic.cumulative.double.sum.suffix.total", + "description", + "1", + ImmutableSumData.create( + /* isMonotonic= */ true, + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "mcds"), + 5)))); + + static final MetricData NON_MONOTONIC_CUMULATIVE_DOUBLE_SUM = + ImmutableMetricData.createDoubleSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "non.monotonic.cumulative.double.sum", + "description", + "1", + ImmutableSumData.create( + /* isMonotonic= */ false, + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "nmcds"), + 5)))); + + static final MetricData DELTA_DOUBLE_SUM = + ImmutableMetricData.createDoubleSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "delta.double.sum", + "unused", + "1", + ImmutableSumData.create( + /* isMonotonic= */ true, + AggregationTemporality.DELTA, + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "mdds"), + 5)))); + + static final MetricData MONOTONIC_CUMULATIVE_LONG_SUM = + ImmutableMetricData.createLongSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "monotonic.cumulative.long.sum", + "unused", + "1", + ImmutableSumData.create( + /* isMonotonic= */ true, + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableLongPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "mcls"), + 5)))); + + static final MetricData NON_MONOTONIC_CUMULATIVE_LONG_SUM = + ImmutableMetricData.createLongSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "non.monotonic.cumulative.long_sum", + "unused", + "1", + ImmutableSumData.create( + /* isMonotonic= */ false, + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableLongPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "nmcls"), + 5)))); + static final MetricData DELTA_LONG_SUM = + ImmutableMetricData.createLongSum( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "delta.long.sum", + "unused", + "1", + ImmutableSumData.create( + /* isMonotonic= */ true, + AggregationTemporality.DELTA, + Collections.singletonList( + ImmutableLongPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "mdls"), + 5)))); + + static final MetricData DOUBLE_GAUGE = + ImmutableMetricData.createDoubleGauge( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "double.gauge", + "unused", + "1", + ImmutableGaugeData.create( + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, 1633950672000000000L, Attributes.of(TYPE, "dg"), 5)))); + static final MetricData LONG_GAUGE = + ImmutableMetricData.createLongGauge( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "long.gauge", + "unused", + "1", + ImmutableGaugeData.create( + Collections.singletonList( + ImmutableLongPointData.create( + 1633947011000000000L, 1633950672000000000L, Attributes.of(TYPE, "lg"), 5)))); + static final MetricData SUMMARY = + ImmutableMetricData.createDoubleSummary( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "summary", + "unused", + "1", + ImmutableSummaryData.create( + Collections.singletonList( + ImmutableSummaryPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "s"), + 5, + 7, + Arrays.asList( + ImmutableValueAtQuantile.create(0.9, 0.1), + ImmutableValueAtQuantile.create(0.99, 0.3)))))); + + static final MetricData DELTA_HISTOGRAM = + ImmutableMetricData.createDoubleHistogram( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "delta.histogram", + "unused", + "1", + ImmutableHistogramData.create( + AggregationTemporality.DELTA, + Collections.singletonList( + ImmutableHistogramPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.empty(), + 1.0, + /* hasMin= */ false, + 0, + /* hasMax= */ false, + 0, + Collections.emptyList(), + Collections.singletonList(2L), + Collections.emptyList())))); + + static final MetricData CUMULATIVE_HISTOGRAM_NO_ATTRIBUTES = + ImmutableMetricData.createDoubleHistogram( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "cumulative.histogram.no.attributes", + "unused", + "1", + ImmutableHistogramData.create( + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableHistogramPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.empty(), + 1.0, + /* hasMin= */ false, + 0, + /* hasMax= */ false, + 0, + Collections.emptyList(), + Collections.singletonList(2L), + Collections.singletonList( + ImmutableDoubleExemplarData.create( + Attributes.empty(), + TimeUnit.MILLISECONDS.toNanos(1L), + SpanContext.create( + "00000000000000000000000000000001", + "0000000000000002", + TraceFlags.getDefault(), + TraceState.getDefault()), + /* value= */ 4)))))); + + static final MetricData CUMULATIVE_HISTOGRAM_SINGLE_ATTRIBUTE = + ImmutableMetricData.createDoubleHistogram( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "cumulative.histogram.single.attribute", + "unused", + "1", + ImmutableHistogramData.create( + AggregationTemporality.CUMULATIVE, + Collections.singletonList( + ImmutableHistogramPointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "hs"), + 1.0, + /* hasMin= */ false, + 0, + /* hasMax= */ false, + 0, + Collections.emptyList(), + Collections.singletonList(2L), + Collections.singletonList( + ImmutableDoubleExemplarData.create( + Attributes.empty(), + TimeUnit.MILLISECONDS.toNanos(1L), + SpanContext.create( + "00000000000000000000000000000001", + "0000000000000002", + TraceFlags.getDefault(), + TraceState.getDefault()), + /* value= */ 4)))))); + + static final MetricData DOUBLE_GAUGE_NO_ATTRIBUTES = + ImmutableMetricData.createDoubleGauge( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "double.gauge.no.attributes", + "unused", + "1", + ImmutableGaugeData.create( + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, 1633950672000000000L, Attributes.empty(), 7)))); + + static final MetricData DOUBLE_GAUGE_MULTIPLE_ATTRIBUTES = + ImmutableMetricData.createDoubleGauge( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationScopeInfo.builder("full") + .setVersion("version") + .setAttributes(Attributes.of(stringKey("ks"), "vs")) + .build(), + "double.gauge.multiple.attributes", + "unused", + "1", + ImmutableGaugeData.create( + Collections.singletonList( + ImmutableDoublePointData.create( + 1633947011000000000L, + 1633950672000000000L, + Attributes.of(TYPE, "dgma", stringKey("animal"), "bear"), + 8)))); +} From cd89da87310bbe9e1f5930abd923801ffb40596a Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Sat, 3 Jun 2023 19:57:37 +0000 Subject: [PATCH 22/23] Remove 1 as unit for non-gauges in test data --- .../opentelemetry/exporter/prometheus/SerializerTest.java | 8 ++++---- .../opentelemetry/exporter/prometheus/TestConstants.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java index fdf3e2eef3b..ee87af9b101 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/SerializerTest.java @@ -69,13 +69,13 @@ void prometheus004() { + "monotonic_cumulative_double_sum_suffix_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcds\"} 5.0 1633950672000\n" + "# TYPE non_monotonic_cumulative_double_sum gauge\n" + "# HELP non_monotonic_cumulative_double_sum description\n" - + "non_monotonic_cumulative_double_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672000\n" + + "non_monotonic_cumulative_double_sum_seconds{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672000\n" + "# TYPE monotonic_cumulative_long_sum_total counter\n" + "# HELP monotonic_cumulative_long_sum_total unused\n" + "monotonic_cumulative_long_sum_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcls\"} 5.0 1633950672000\n" + "# TYPE non_monotonic_cumulative_long_sum gauge\n" + "# HELP non_monotonic_cumulative_long_sum unused\n" - + "non_monotonic_cumulative_long_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672000\n" + + "non_monotonic_cumulative_long_sum_seconds{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672000\n" + "# TYPE double_gauge gauge\n" + "# HELP double_gauge unused\n" + "double_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672000\n" @@ -140,13 +140,13 @@ void openMetrics() { + "monotonic_cumulative_double_sum_suffix_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcds\"} 5.0 1633950672.000\n" + "# TYPE non_monotonic_cumulative_double_sum gauge\n" + "# HELP non_monotonic_cumulative_double_sum description\n" - + "non_monotonic_cumulative_double_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672.000\n" + + "non_monotonic_cumulative_double_sum_seconds{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcds\"} 5.0 1633950672.000\n" + "# TYPE monotonic_cumulative_long_sum counter\n" + "# HELP monotonic_cumulative_long_sum unused\n" + "monotonic_cumulative_long_sum_total{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"mcls\"} 5.0 1633950672.000\n" + "# TYPE non_monotonic_cumulative_long_sum gauge\n" + "# HELP non_monotonic_cumulative_long_sum unused\n" - + "non_monotonic_cumulative_long_sum_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672.000\n" + + "non_monotonic_cumulative_long_sum_seconds{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"nmcls\"} 5.0 1633950672.000\n" + "# TYPE double_gauge gauge\n" + "# HELP double_gauge unused\n" + "double_gauge_ratio{otel_scope_name=\"full\",otel_scope_version=\"version\",type=\"dg\"} 5.0 1633950672.000\n" diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java index 0c004e81ec0..496cba3786f 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/TestConstants.java @@ -89,7 +89,7 @@ private TestConstants() { .build(), "non.monotonic.cumulative.double.sum", "description", - "1", + "s", ImmutableSumData.create( /* isMonotonic= */ false, AggregationTemporality.CUMULATIVE, @@ -149,7 +149,7 @@ private TestConstants() { .build(), "non.monotonic.cumulative.long_sum", "unused", - "1", + "s", ImmutableSumData.create( /* isMonotonic= */ false, AggregationTemporality.CUMULATIVE, From c302e9183d36700e2511a06c17211d5192f2f2ac Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 5 Jun 2023 17:02:59 +0000 Subject: [PATCH 23/23] Creates an AutoValue class for cache mapping keys A dedicated class to represent cache mapping keys would prevent certain edge cases where String concatination could yield same result even when individual Strings being concatinated are different. --- exporters/prometheus/build.gradle.kts | 3 ++ .../PrometheusMetricNameMapper.java | 37 +++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/exporters/prometheus/build.gradle.kts b/exporters/prometheus/build.gradle.kts index c73701a31e2..0465aca53d1 100644 --- a/exporters/prometheus/build.gradle.kts +++ b/exporters/prometheus/build.gradle.kts @@ -14,6 +14,9 @@ dependencies { implementation(project(":sdk-extensions:autoconfigure-spi")) compileOnly("com.sun.net.httpserver:http") + compileOnly("com.google.auto.value:auto-value-annotations") + + annotationProcessor("com.google.auto.value:auto-value") testImplementation(project(":semconv")) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java index a9f2defea01..ceb2e7b1eb4 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java @@ -5,18 +5,20 @@ package io.opentelemetry.exporter.prometheus; +import com.google.auto.value.AutoValue; import io.opentelemetry.api.internal.StringUtils; import io.opentelemetry.sdk.metrics.data.MetricData; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; +import javax.annotation.concurrent.Immutable; /** A class that maps a raw metric name to Prometheus equivalent name. */ class PrometheusMetricNameMapper implements BiFunction { static final PrometheusMetricNameMapper INSTANCE = new PrometheusMetricNameMapper(); - private final Map cache = new ConcurrentHashMap<>(); + private final Map cache = new ConcurrentHashMap<>(); private final BiFunction delegate; // private constructor - prevent external object initialization @@ -60,16 +62,37 @@ private static String mapToPrometheusName(MetricData rawMetric, PrometheusType p } /** - * Create key from a combination of raw metric name, raw metric unit and the prometheus type since - * all of them are used to compute the prometheus equivalent name. + * Creates a suitable mapping key to be used for maintaining mapping between raw metric and its + * equivalent Prometheus name. * * @param metricData the metric data for which the mapping is to be created. * @param prometheusType the prometheus type to which the metric is to be mapped. - * @return a String that acts as the key for mapping between metric data and its prometheus - * equivalent name. + * @return an {@link ImmutableMappingKey} that can be used as a key for mapping between metric + * data and its prometheus equivalent name. */ - private static String createKeyForCacheMapping( + private static ImmutableMappingKey createKeyForCacheMapping( MetricData metricData, PrometheusType prometheusType) { - return metricData.getName() + metricData.getUnit() + prometheusType.name(); + return ImmutableMappingKey.create( + metricData.getName(), metricData.getUnit(), prometheusType.name()); + } + + /** + * Objects of this class acts as mapping keys for Prometheus metric mapping cache used in {@link + * PrometheusMetricNameMapper}. + */ + @Immutable + @AutoValue + abstract static class ImmutableMappingKey { + static ImmutableMappingKey create( + String rawMetricName, String rawMetricUnit, String prometheusType) { + return new AutoValue_PrometheusMetricNameMapper_ImmutableMappingKey( + rawMetricName, rawMetricUnit, prometheusType); + } + + abstract String rawMetricName(); + + abstract String rawMetricUnit(); + + abstract String prometheusType(); } }