Append unit to prometheus metric names (#5400)

This commit is contained in:
Pranav Sharma 2023-06-08 14:20:04 -04:00 committed by GitHub
parent 951221efcc
commit 4d034b08e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1038 additions and 363 deletions

View File

@ -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"))

View File

@ -15,6 +15,8 @@ class NameSanitizer implements Function<String, String> {
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 @@ class NameSanitizer implements Function<String, String> {
}
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("_");
}
}

View File

@ -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<MetricData, PrometheusType, String> {
static final PrometheusMetricNameMapper INSTANCE = new PrometheusMetricNameMapper();
private final Map<ImmutableMappingKey, String> cache = new ConcurrentHashMap<>();
private final BiFunction<MetricData, PrometheusType, String> delegate;
// private constructor - prevent external object initialization
private PrometheusMetricNameMapper() {
this(PrometheusMetricNameMapper::mapToPrometheusName);
}
// Visible for testing
PrometheusMetricNameMapper(BiFunction<MetricData, PrometheusType, String> 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();
}
}

View File

@ -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 <a
* href="https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#units-and-base-units">OpenMetrics
* specification for units</a>
* @see <a href="https://prometheus.io/docs/practices/naming/#base-units">Prometheus best practices
* for units</a>
*/
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 '{}'.
*
* <p>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 <a href="https://ucum.org/ucum.html">UCUM</a>. The list of
* mappings is adopted from <a
* href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/9a9d4778bbbf242dba233db28e2fbcfda3416959/pkg/translator/prometheus/normalize_name.go#L30">OpenTelemetry
* Collector Contrib</a>.
*
* @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;
}
}
}

View File

@ -118,7 +118,7 @@ abstract class Serializer {
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 @@ abstract class Serializer {
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()

View File

@ -9,7 +9,12 @@ import static org.assertj.core.api.Assertions.assertThat;
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 @@ class NameSanitizerTest {
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<Arguments> 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"));
}
}

View File

@ -236,7 +236,7 @@ class PrometheusHttpServerTest {
InstrumentationScopeInfo.create("scope1"),
"foo",
"description1",
"unit1",
"unit",
ImmutableSumData.create(
/* isMonotonic= */ true,
AggregationTemporality.CUMULATIVE,
@ -248,7 +248,7 @@ class PrometheusHttpServerTest {
InstrumentationScopeInfo.create("scope2"),
"foo",
"description2",
"unit2",
"unit",
ImmutableSumData.create(
/* isMonotonic= */ true,
AggregationTemporality.CUMULATIVE,
@ -259,9 +259,9 @@ class PrometheusHttpServerTest {
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 @@ class PrometheusHttpServerTest {
+ "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();

View File

@ -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<MetricData, PrometheusType, String> 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<Arguments> 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();
}
}

View File

@ -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<Arguments> 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"));
}
}

View File

@ -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<String> 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 @@ class SerializerTest {
+ "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 @@ class SerializerTest {
+ "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 @@ class SerializerTest {
+ "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 @@ class SerializerTest {
+ "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");
}

View File

@ -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<String> 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))));
}