diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/MetricAdapter.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/MetricAdapter.java index deb82a6e24..00217c900a 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/MetricAdapter.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/MetricAdapter.java @@ -149,6 +149,9 @@ public final class MetricAdapter { .addAllDataPoints(toDoubleDataPoints(doubleGaugeData.getPoints())) .build()); break; + case HISTOGRAM: + // no-op, will add in the following PRs + break; } return builder.build(); } 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 e04cb914a9..c4e99b00c3 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 @@ -85,6 +85,8 @@ final class MetricAdapter { return Collector.Type.GAUGE; case SUMMARY: return Collector.Type.SUMMARY; + case HISTOGRAM: + return Collector.Type.HISTOGRAM; } return Collector.Type.UNKNOWN; } @@ -122,6 +124,9 @@ final class MetricAdapter { addSummarySamples( (DoubleSummaryPointData) pointData, name, labelNames, labelValues, samples); break; + case HISTOGRAM: + // no-op, will add in the following PRs + break; } } return samples; @@ -189,6 +194,8 @@ final class MetricAdapter { return metricData.getLongSumData().getPoints(); case SUMMARY: return metricData.getDoubleSummaryData().getPoints(); + case HISTOGRAM: + return metricData.getDoubleHistogramData().getPoints(); } return Collections.emptyList(); } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/DoubleHistogramData.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/DoubleHistogramData.java new file mode 100644 index 0000000000..a5f90422fa --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/DoubleHistogramData.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics.data; + +import com.google.auto.value.AutoValue; +import java.util.Collection; +import javax.annotation.concurrent.Immutable; + +@Immutable +@AutoValue +public abstract class DoubleHistogramData implements Data { + DoubleHistogramData() {} + + public static DoubleHistogramData create( + AggregationTemporality temporality, Collection points) { + return new AutoValue_DoubleHistogramData(temporality, points); + } + + /** + * Returns the {@code AggregationTemporality} of this metric, + * + *

AggregationTemporality describes if the aggregator reports delta changes since last report + * time, or cumulative changes since a fixed start time. + * + * @return the {@code AggregationTemporality} of this metric + */ + public abstract AggregationTemporality getAggregationTemporality(); + + @Override + public abstract Collection getPoints(); +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/DoubleHistogramPointData.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/DoubleHistogramPointData.java new file mode 100644 index 0000000000..22e5e13461 --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/DoubleHistogramPointData.java @@ -0,0 +1,105 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics.data; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.metrics.common.Labels; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * DoubleHistogramPointData represents an approximate representation of the distribution of + * measurements. + */ +@Immutable +@AutoValue +public abstract class DoubleHistogramPointData implements PointData { + /** + * Creates a DoubleHistogramPointData. For a Histogram with N defined boundaries, there should be + * N+1 counts. + * + * @return a DoubleHistogramPointData. + * @throws IllegalArgumentException if the given boundaries/counts were invalid + */ + public static DoubleHistogramPointData create( + long startEpochNanos, + long epochNanos, + Labels labels, + double sum, + List boundaries, + List counts) { + if (counts.size() != boundaries.size() + 1) { + throw new IllegalArgumentException( + "invalid counts: size should be " + + (boundaries.size() + 1) + + " instead of " + + counts.size()); + } + if (!isStrictlyIncreasing(boundaries)) { + throw new IllegalArgumentException("invalid boundaries: " + boundaries); + } + if (!boundaries.isEmpty() + && (boundaries.get(0).isInfinite() || boundaries.get(boundaries.size() - 1).isInfinite())) { + throw new IllegalArgumentException("invalid boundaries: contains explicit +/-Inf"); + } + + long totalCount = 0; + for (long c : counts) { + totalCount += c; + } + return new AutoValue_DoubleHistogramPointData( + startEpochNanos, + epochNanos, + labels, + sum, + totalCount, + Collections.unmodifiableList(new ArrayList<>(boundaries)), + Collections.unmodifiableList(new ArrayList<>(counts))); + } + + DoubleHistogramPointData() {} + + /** + * The sum of all measurements recorded. + * + * @return the sum of recorded measurements. + */ + public abstract double getSum(); + + /** + * The number of measurements taken. + * + * @return the count of recorded measurements. + */ + public abstract long getCount(); + + /** + * The bucket boundaries. For a Histogram with N defined boundaries, e.g, [x, y, z]. There are N+1 + * counts: (-inf, x], (x, y], (y, z], (z, +inf). + * + * @return the read-only bucket boundaries in increasing order. do not mutate the returned + * object. + */ + public abstract List getBoundaries(); + + /** + * The counts in each bucket. + * + * @return the read-only counts in each bucket. do not mutate the returned object. + */ + public abstract List getCounts(); + + private static boolean isStrictlyIncreasing(List xs) { + for (int i = 0; i < xs.size() - 1; i++) { + if (xs.get(i).compareTo(xs.get(i + 1)) >= 0) { + return false; + } + } + return true; + } +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricData.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricData.java index d7e2939635..61df82f5db 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricData.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricData.java @@ -29,6 +29,8 @@ public abstract class MetricData { /* isMonotonic= */ false, AggregationTemporality.CUMULATIVE, Collections.emptyList()); private static final DoubleSummaryData DEFAULT_DOUBLE_SUMMARY_DATA = DoubleSummaryData.create(Collections.emptyList()); + private static final DoubleHistogramData DEFAULT_DOUBLE_HISTOGRAM_DATA = + DoubleHistogramData.create(AggregationTemporality.CUMULATIVE, Collections.emptyList()); /** * Returns a new MetricData wih a {@link MetricDataType#DOUBLE_GAUGE} type. @@ -140,6 +142,28 @@ public abstract class MetricData { data); } + /** + * Returns a new MetricData with a {@link MetricDataType#HISTOGRAM} type. + * + * @return a new MetricData wih a {@link MetricDataType#HISTOGRAM} type. + */ + public static MetricData createDoubleHistogram( + Resource resource, + InstrumentationLibraryInfo instrumentationLibraryInfo, + String name, + String description, + String unit, + DoubleHistogramData data) { + return new AutoValue_MetricData( + resource, + instrumentationLibraryInfo, + name, + description, + unit, + MetricDataType.HISTOGRAM, + data); + } + MetricData() {} /** @@ -265,4 +289,18 @@ public abstract class MetricData { } return DEFAULT_DOUBLE_SUMMARY_DATA; } + + /** + * Returns the {@code DoubleHistogramData} if type is {@link MetricDataType#HISTOGRAM}, otherwise + * a default empty data. + * + * @return the {@code DoubleHistogramData} if type is {@link MetricDataType#HISTOGRAM}, otherwise + * a default empty data. + */ + public final DoubleHistogramData getDoubleHistogramData() { + if (getType() == MetricDataType.HISTOGRAM) { + return (DoubleHistogramData) getData(); + } + return DEFAULT_DOUBLE_HISTOGRAM_DATA; + } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricDataType.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricDataType.java index 50adc159fb..0749807848 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricDataType.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/data/MetricDataType.java @@ -30,4 +30,10 @@ public enum MetricDataType { * value recorded, the sum of all measurements and the total number of measurements recorded. */ SUMMARY, + + /** + * A Histogram represents an approximate representation of the distribution of measurements + * recorded. + */ + HISTOGRAM, } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/data/MetricDataTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/data/MetricDataTest.java index 01eae936c1..c72fa92d25 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/data/MetricDataTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/data/MetricDataTest.java @@ -7,12 +7,14 @@ package io.opentelemetry.sdk.metrics.data; import static org.assertj.core.api.Assertions.assertThat; +import com.google.common.collect.ImmutableList; import io.opentelemetry.api.metrics.common.Labels; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; import io.opentelemetry.sdk.resources.Resource; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** Unit tests for {@link io.opentelemetry.sdk.metrics.data.MetricData}. */ @@ -40,6 +42,14 @@ class MetricDataTest { Arrays.asList( ValueAtPercentile.create(0.0, DOUBLE_VALUE), ValueAtPercentile.create(100, DOUBLE_VALUE))); + private static final DoubleHistogramPointData HISTOGRAM_POINT = + DoubleHistogramPointData.create( + START_EPOCH_NANOS, + EPOCH_NANOS, + Labels.of("key", "value"), + DOUBLE_VALUE, + ImmutableList.of(1.0), + ImmutableList.of(1L, 1L)); @Test void metricData_Getters() { @@ -146,6 +156,55 @@ class MetricDataTest { assertThat(metricData.getDoubleSummaryData().getPoints()).containsExactly(SUMMARY_POINT); } + @Test + void metricData_HistogramPoints() { + assertThat(HISTOGRAM_POINT.getStartEpochNanos()).isEqualTo(START_EPOCH_NANOS); + assertThat(HISTOGRAM_POINT.getEpochNanos()).isEqualTo(EPOCH_NANOS); + assertThat(HISTOGRAM_POINT.getLabels().size()).isEqualTo(1); + assertThat(HISTOGRAM_POINT.getLabels().get("key")).isEqualTo("value"); + assertThat(HISTOGRAM_POINT.getCount()).isEqualTo(2L); + assertThat(HISTOGRAM_POINT.getSum()).isEqualTo(DOUBLE_VALUE); + assertThat(HISTOGRAM_POINT.getBoundaries()).isEqualTo(ImmutableList.of(1.0)); + assertThat(HISTOGRAM_POINT.getCounts()).isEqualTo(ImmutableList.of(1L, 1L)); + + MetricData metricData = + MetricData.createDoubleHistogram( + Resource.empty(), + InstrumentationLibraryInfo.empty(), + "metric_name", + "metric_description", + "ms", + DoubleHistogramData.create( + AggregationTemporality.DELTA, Collections.singleton(HISTOGRAM_POINT))); + assertThat(metricData.getDoubleHistogramData().getPoints()).containsExactly(HISTOGRAM_POINT); + + Assertions.assertThrows( + IllegalArgumentException.class, + () -> + DoubleHistogramPointData.create( + 0, 0, Labels.empty(), 0.0, ImmutableList.of(), ImmutableList.of())); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> + DoubleHistogramPointData.create( + 0, + 0, + Labels.empty(), + 0.0, + ImmutableList.of(1.0, 1.0), + ImmutableList.of(0L, 0L, 0L))); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> + DoubleHistogramPointData.create( + 0, + 0, + Labels.empty(), + 0.0, + ImmutableList.of(Double.NEGATIVE_INFINITY), + ImmutableList.of(0L, 0L))); + } + @Test void metricData_GetDefault() { MetricData metricData = @@ -160,6 +219,7 @@ class MetricDataTest { assertThat(metricData.getLongGaugeData().getPoints()).isEmpty(); assertThat(metricData.getDoubleSumData().getPoints()).isEmpty(); assertThat(metricData.getLongGaugeData().getPoints()).isEmpty(); + assertThat(metricData.getDoubleHistogramData().getPoints()).isEmpty(); assertThat(metricData.getDoubleSummaryData().getPoints()).containsExactly(SUMMARY_POINT); metricData = @@ -174,6 +234,7 @@ class MetricDataTest { assertThat(metricData.getLongGaugeData().getPoints()).isEmpty(); assertThat(metricData.getDoubleSumData().getPoints()).isEmpty(); assertThat(metricData.getLongGaugeData().getPoints()).isEmpty(); + assertThat(metricData.getDoubleHistogramData().getPoints()).isEmpty(); assertThat(metricData.getDoubleSummaryData().getPoints()).isEmpty(); } }