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/NameSanitizer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/NameSanitizer.java index 40465d30b07..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,6 +15,8 @@ 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_:]"); @@ -36,8 +38,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/PrometheusMetricNameMapper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java new file mode 100644 index 00000000000..ceb2e7b1eb4 --- /dev/null +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricNameMapper.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +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 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; + } + + /** + * 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 an {@link ImmutableMappingKey} that can be used as a key for mapping between metric + * data and its prometheus equivalent name. + */ + private static ImmutableMappingKey createKeyForCacheMapping( + MetricData metricData, PrometheusType prometheusType) { + 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(); + } +} 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..b4ba94d4709 --- /dev/null +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -0,0 +1,224 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +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; + +/** + * A utility class that contains helper function(s) to aid conversion from OTLP to Prometheus units. + * + * @see OpenMetrics + * specification for units + * @see Prometheus best practices + * for units + */ +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 + } + + /** + * A utility function that returns the equivalent Prometheus name for the provided OTLP metric + * 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 un + */ + static String getEquivalentPrometheusUnit(String rawMetricUnitName) { + if (StringUtils.isNullOrEmpty(rawMetricUnitName)) { + return rawMetricUnitName; + } + // 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)); + } + + /** + * 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 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 + * function returns it as-is. + */ + 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("")) { + return rateExpressedUnit; + } + return getPrometheusUnit(rateEntities[0]) + "_per_" + getPrometheusPerUnit(rateEntities[1]); + } + + /** + * 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 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 + * mappings is adopted from OpenTelemetry + * Collector Contrib. + * + * @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; + } + } +} 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 193bc622383..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 @@ -118,7 +118,7 @@ final Set write(Collection metrics, OutputStream output) thr continue; } PrometheusType prometheusType = PrometheusType.forMetric(metric); - String metricName = metricName(metric.getName(), prometheusType); + String metricName = PrometheusMetricNameMapper.INSTANCE.apply(metric, prometheusType); // Skip metrics which do not pass metricNameFilter if (!metricNameFilter.test(metricName)) { continue; @@ -650,14 +650,6 @@ static Collection getPoints(MetricData metricData) { return Collections.emptyList(); } - private static String metricName(String rawMetricName, PrometheusType type) { - String name = NameSanitizer.INSTANCE.apply(rawMetricName); - if (type == PrometheusType.COUNTER && !name.endsWith("_total")) { - name = name + "_total"; - } - 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/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/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(); 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/PrometheusUnitsHelperTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java new file mode 100644 index 00000000000..a510e726215 --- /dev/null +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelperTest.java @@ -0,0 +1,115 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +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("providePrometheusOTelUnitEquivalentPairs") + 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"), + Arguments.of("B", "bytes"), + // Simple expansion - storage KB + Arguments.of("KB", "kilobytes"), + Arguments.of("KBy", "kilobytes"), + // Simple expansion - storage MB + Arguments.of("MB", "megabytes"), + Arguments.of("MBy", "megabytes"), + // Simple expansion - storage GB + Arguments.of("GB", "gigabytes"), + Arguments.of("GBy", "gigabytes"), + // Simple expansion - storage TB + Arguments.of("TB", "terabytes"), + Arguments.of("TBy", "terabytes"), + // Simple expansion - storage KiBy + Arguments.of("KiBy", "kibibytes"), + // Simple expansion - storage MiBy + Arguments.of("MiBy", "mebibytes"), + // Simple expansion - storage GiBy + Arguments.of("GiBy", "gibibytes"), + // Simple expansion - storage TiBy + Arguments.of("TiBy", "tibibytes"), + // Simple expansion - Time unit d + Arguments.of("d", "days"), + // Simple expansion - Time unit h + Arguments.of("h", "hours"), + // Simple expansion - Time unit s + Arguments.of("s", "seconds"), + // Simple expansion - Time unit ms + Arguments.of("ms", "milliseconds"), + // Simple expansion - Time unit us + Arguments.of("us", "microseconds"), + // Simple expansion - Time unit ns + Arguments.of("ns", "nanoseconds"), + // Simple expansion - Time unit min + Arguments.of("min", "minutes"), + // Simple expansion - special symbol - % + Arguments.of("%", "percent"), + // Simple expansion - special symbols - $ + Arguments.of("$", "dollars"), + // Simple expansion - frequency + Arguments.of("Hz", "hertz"), + // Simple expansion - temperature + Arguments.of("Cel", "celsius"), + // Unit not found - Case sensitive + Arguments.of("S", "S"), + // Special case - 1 + Arguments.of("1", ""), + // Special Case - Drop metric units in {} + Arguments.of("{packets}", ""), + // 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}", ""), + // Special Case - Dropped metric units with 'per' unit handling applicable + 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 + Arguments.of("m/m", "meters_per_minute"), + // Units expressing rate - per day + Arguments.of("A/d", "amperes_per_day"), + // Units expressing rate - per week + Arguments.of("W/w", "watts_per_week"), + // Units expressing rate - per month + Arguments.of("J/mo", "joules_per_month"), + // Units expressing rate - per year + Arguments.of("TB/y", "terabytes_per_year"), + // Units expressing rate - 'per' units, both units unknown + Arguments.of("v/v", "v_per_v"), + // Units expressing rate - 'per' units, first unit unknown + Arguments.of("km/h", "km_per_hour"), + // Units expressing rate - 'per' units, 'per' unit unknown + Arguments.of("g/g", "grams_per_g"), + // Misc - unit containing known abbreviations improperly formatted + 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", ""), + // Null unit + Arguments.of(null, null), + // Misc - unit cleanup - no case match special char + Arguments.of("$1000", "1000")); + } +} 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 df1f8416e16..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 @@ -5,350 +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 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 org.junit.jupiter.api.Test; 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 @@ -387,19 +69,19 @@ 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{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{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{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" @@ -418,10 +100,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 @@ -458,19 +140,19 @@ 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{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{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{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" @@ -489,10 +171,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"); } 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..496cba3786f --- /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", + "s", + 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", + "s", + 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)))); +}