diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/MetricAdapter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/MetricAdapter.java index c4e99b00c33..27e3a19088c 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/MetricAdapter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/MetricAdapter.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.metrics.common.Labels; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.DoubleHistogramPointData; import io.opentelemetry.sdk.metrics.data.DoublePointData; import io.opentelemetry.sdk.metrics.data.DoubleSumData; import io.opentelemetry.sdk.metrics.data.DoubleSummaryPointData; @@ -46,7 +47,9 @@ final class MetricAdapter { static final String SAMPLE_SUFFIX_COUNT = "_count"; static final String SAMPLE_SUFFIX_SUM = "_sum"; + static final String SAMPLE_SUFFIX_BUCKET = "_bucket"; static final String LABEL_NAME_QUANTILE = "quantile"; + static final String LABEL_NAME_LE = "le"; // Converts a MetricData to a Prometheus MetricFamilySamples. static MetricFamilySamples toMetricFamilySamples(MetricData metricData) { @@ -125,7 +128,8 @@ static List toSamples( (DoubleSummaryPointData) pointData, name, labelNames, labelValues, samples); break; case HISTOGRAM: - // no-op, will add in the following PRs + addHistogramSamples( + (DoubleHistogramPointData) pointData, name, labelNames, labelValues, samples); break; } } @@ -174,6 +178,42 @@ private static void addSummarySamples( } } + private static void addHistogramSamples( + DoubleHistogramPointData doubleHistogramPointData, + String name, + List labelNames, + List labelValues, + List samples) { + samples.add( + new Sample( + name + SAMPLE_SUFFIX_COUNT, + labelNames, + labelValues, + doubleHistogramPointData.getCount())); + samples.add( + new Sample( + name + SAMPLE_SUFFIX_SUM, labelNames, labelValues, doubleHistogramPointData.getSum())); + + List labelNamesWithLe = new ArrayList<>(labelNames.size() + 1); + labelNamesWithLe.addAll(labelNames); + labelNamesWithLe.add(LABEL_NAME_LE); + + long cumulativeCount = 0; + List boundaries = doubleHistogramPointData.getBoundaries(); + List counts = doubleHistogramPointData.getCounts(); + for (int i = 0; i < counts.size(); i++) { + List labelValuesWithLe = new ArrayList<>(labelValues.size() + 1); + labelValuesWithLe.addAll(labelValues); + labelValuesWithLe.add( + doubleToGoString(i < boundaries.size() ? boundaries.get(i) : Double.POSITIVE_INFINITY)); + + cumulativeCount += counts.get(i); + samples.add( + new Sample( + name + SAMPLE_SUFFIX_BUCKET, labelNamesWithLe, labelValuesWithLe, cumulativeCount)); + } + } + private static int estimateNumSamples(int numPoints, MetricDataType type) { if (type == MetricDataType.SUMMARY) { // count + sum + estimated 2 percentiles (default MinMaxSumCount aggregator). diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/MetricAdapterTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/MetricAdapterTest.java index f14b82a10af..cb9764850f1 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/MetricAdapterTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/MetricAdapterTest.java @@ -14,6 +14,8 @@ import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.DoubleGaugeData; +import io.opentelemetry.sdk.metrics.data.DoubleHistogramData; +import io.opentelemetry.sdk.metrics.data.DoubleHistogramPointData; import io.opentelemetry.sdk.metrics.data.DoublePointData; import io.opentelemetry.sdk.metrics.data.DoubleSumData; import io.opentelemetry.sdk.metrics.data.DoubleSummaryData; @@ -157,6 +159,23 @@ class MetricAdapterTest { Collections.singletonList( DoubleSummaryPointData.create( 123, 456, Labels.of("kp", "vp"), 5, 7, Collections.emptyList())))); + private static final MetricData HISTOGRAM = + MetricData.createDoubleHistogram( + Resource.create(Attributes.of(stringKey("kr"), "vr")), + InstrumentationLibraryInfo.create("full", "version"), + "instrument.name", + "description", + "1", + DoubleHistogramData.create( + AggregationTemporality.DELTA, + Collections.singletonList( + DoubleHistogramPointData.create( + 123, + 456, + Labels.of("kp", "vp"), + 1.0, + Collections.emptyList(), + Collections.singletonList(2L))))); @Test void toProtoMetricDescriptorType() { @@ -204,6 +223,10 @@ void toProtoMetricDescriptorType() { metricFamilySamples = MetricAdapter.toMetricFamilySamples(LONG_GAUGE); assertThat(metricFamilySamples.type).isEqualTo(Collector.Type.GAUGE); assertThat(metricFamilySamples.samples).hasSize(1); + + metricFamilySamples = MetricAdapter.toMetricFamilySamples(HISTOGRAM); + assertThat(metricFamilySamples.type).isEqualTo(Collector.Type.HISTOGRAM); + assertThat(metricFamilySamples.samples).hasSize(3); } @Test @@ -323,6 +346,36 @@ void toSamples_SummaryPoints() { 12.3)); } + @Test + void toSamples_HistogramPoints() { + assertThat( + MetricAdapter.toSamples("full_name", MetricDataType.HISTOGRAM, Collections.emptyList())) + .isEmpty(); + + assertThat( + MetricAdapter.toSamples( + "full_name", + MetricDataType.HISTOGRAM, + ImmutableList.of( + DoubleHistogramPointData.create( + 321, + 654, + Labels.of("kp", "vp"), + 18.3, + ImmutableList.of(1.0), + ImmutableList.of(4L, 9L))))) + .containsExactly( + new Sample("full_name_count", ImmutableList.of("kp"), ImmutableList.of("vp"), 13), + new Sample("full_name_sum", ImmutableList.of("kp"), ImmutableList.of("vp"), 18.3), + new Sample( + "full_name_bucket", ImmutableList.of("kp", "le"), ImmutableList.of("vp", "1.0"), 4), + new Sample( + "full_name_bucket", + ImmutableList.of("kp", "le"), + ImmutableList.of("vp", "+Inf"), + 13)); + } + @Test void toMetricFamilySamples() { MetricData metricData = MONOTONIC_CUMULATIVE_DOUBLE_SUM;