diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/Serializer.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/Serializer.java index 6668501c01..f3e31ad75d 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/Serializer.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/Serializer.java @@ -179,6 +179,18 @@ public abstract class Serializer implements AutoCloseable { writeEndRepeatedPrimitive(); } + /** Serializes a {@code repeated fixed64} field. */ + public void serializeRepeatedFixed64(ProtoFieldInfo field, long[] values) throws IOException { + if (values.length == 0) { + return; + } + writeStartRepeatedPrimitive(field, WireFormat.FIXED64_SIZE, values.length); + for (long value : values) { + writeFixed64Value(value); + } + writeEndRepeatedPrimitive(); + } + /** Serializes a {@code repeated double} field. */ public void serializeRepeatedDouble(ProtoFieldInfo field, List values) throws IOException { diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/metrics/HistogramDataPointMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/metrics/HistogramDataPointMarshaler.java index 01d49add77..05c4b001df 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/metrics/HistogramDataPointMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/otlp/internal/metrics/HistogramDataPointMarshaler.java @@ -10,6 +10,7 @@ import io.opentelemetry.exporter.otlp.internal.MarshalerUtil; import io.opentelemetry.exporter.otlp.internal.MarshalerWithSize; import io.opentelemetry.exporter.otlp.internal.Serializer; import io.opentelemetry.proto.metrics.v1.internal.HistogramDataPoint; +import io.opentelemetry.sdk.internal.PrimitiveLongList; import io.opentelemetry.sdk.metrics.data.DoubleHistogramPointData; import java.io.IOException; import java.util.Collection; @@ -85,7 +86,8 @@ final class HistogramDataPointMarshaler extends MarshalerWithSize { output.serializeFixed64(HistogramDataPoint.TIME_UNIX_NANO, timeUnixNano); output.serializeFixed64(HistogramDataPoint.COUNT, count); output.serializeDouble(HistogramDataPoint.SUM, sum); - output.serializeRepeatedFixed64(HistogramDataPoint.BUCKET_COUNTS, bucketCounts); + output.serializeRepeatedFixed64( + HistogramDataPoint.BUCKET_COUNTS, PrimitiveLongList.toArray(bucketCounts)); output.serializeRepeatedDouble(HistogramDataPoint.EXPLICIT_BOUNDS, explicitBounds); output.serializeRepeatedMessage(HistogramDataPoint.EXEMPLARS, exemplars); output.serializeRepeatedMessage(HistogramDataPoint.ATTRIBUTES, attributes); diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/PrimitiveLongList.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/PrimitiveLongList.java new file mode 100644 index 0000000000..333a8109c6 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/PrimitiveLongList.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.internal; + +import java.util.AbstractList; +import java.util.List; + +/** + * A list of longs backed by, and exposing, an array of primitives. Values will be boxed on demand + * when using standard List operations. Operations should generally use the static methods in this + * class to operate directly on the backing array instead. The idea is that in almost all apps, the + * list will only be accessed by our internal code, and if it does happen to be used elsewhere, + * performance of on-demand boxing isn't prohibitive while still providing expected ergonomics. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class PrimitiveLongList { + + /** + * Returns a list that wraps the primitive array. Modifications in the array will be visible in + * the list. + */ + public static List wrap(long[] values) { + return new LongListImpl(values); + } + + /** + * Returns a primitive array with the values of the list. The list should generally have been + * created with {@link PrimitiveLongList#wrap(long[])}. + */ + public static long[] toArray(List list) { + if (list instanceof LongListImpl) { + return ((LongListImpl) list).values; + } + + long[] values = new long[list.size()]; + for (int i = 0; i < values.length; i++) { + values[i] = list.get(i); + } + return values; + } + + private static class LongListImpl extends AbstractList { + + private final long[] values; + + LongListImpl(long[] values) { + this.values = values; + } + + @Override + public Long get(int index) { + // If out of bounds, the array access will produce a perfectly fine IndexOutOfBoundsException. + return values[index]; + } + + @Override + public int size() { + return values.length; + } + } + + private PrimitiveLongList() {} +} diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/internal/PrimitiveLongListTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/internal/PrimitiveLongListTest.java new file mode 100644 index 0000000000..7dff7153a6 --- /dev/null +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/internal/PrimitiveLongListTest.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class PrimitiveLongListTest { + + @Test + void wrap() { + long[] array = new long[] {1, 2}; + List wrapped = PrimitiveLongList.wrap(array); + // Standard List operations + assertThat(wrapped).containsExactly(1L, 2L); + assertThat(wrapped).hasSize(2); + // Message can change between Java versions, so instead check it's the same as a normal List's + // exception. + Throwable referenceException = catchThrowable(() -> Arrays.asList(1, 2).get(3)); + assertThatThrownBy(() -> wrapped.get(3)) + .isInstanceOf(IndexOutOfBoundsException.class) + .hasMessage(referenceException.getMessage()); + + assertThat(PrimitiveLongList.toArray(wrapped)).isSameAs(array).containsExactly(1L, 2L); + } + + @Test + void notWrapped() { + List list = Arrays.asList(1L, 2L); + assertThat(PrimitiveLongList.toArray(list)).containsExactly(1L, 2L); + } +} 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 index d5f0405c78..2842663c5d 100644 --- 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 @@ -7,6 +7,7 @@ package io.opentelemetry.sdk.metrics.data; import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.internal.PrimitiveLongList; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -69,7 +70,7 @@ public abstract class DoubleHistogramPointData implements PointData { } long totalCount = 0; - for (long c : counts) { + for (long c : PrimitiveLongList.toArray(counts)) { totalCount += c; } return new AutoValue_DoubleHistogramPointData( diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/MetricDataUtils.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/MetricDataUtils.java index 2016b8cb68..1f0bf4afd5 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/MetricDataUtils.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/MetricDataUtils.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.metrics.internal.aggregator; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.internal.PrimitiveLongList; import io.opentelemetry.sdk.metrics.common.InstrumentType; import io.opentelemetry.sdk.metrics.data.DoubleHistogramPointData; import io.opentelemetry.sdk.metrics.data.DoublePointData; @@ -77,10 +78,7 @@ final class MetricDataUtils { List points = new ArrayList<>(accumulationMap.size()); accumulationMap.forEach( (labels, aggregator) -> { - List counts = new ArrayList<>(aggregator.getCounts().length); - for (long v : aggregator.getCounts()) { - counts.add(v); - } + List counts = PrimitiveLongList.wrap(aggregator.getCounts().clone()); points.add( DoubleHistogramPointData.create( startEpochNanos,