-
Notifications
You must be signed in to change notification settings - Fork 941
Description
Describe the bug
MetricData.getDoubleSumData() incorrectly casts its result to the internal ImmutableSumData class instead of the public SumData interface. This causes a ClassCastException when using a custom MetricData implementation.
Steps to reproduce
Create a custom implementation of MetricData, SumData and DoublePointData interfaces, then use invoke any MetricExporter's export method on an instance of those.
public class MetricDataDoubleSumBug {
public static void main(String[] args) {
long timeNanos = Instant.now().getEpochSecond() * 1_000_000_000L;
OtlpGrpcMetricExporter exporter = OtlpGrpcMetricExporter.getDefault();
List<MyDoublePointData> myPoints = List.of(
new MyDoublePointData(8.88, timeNanos, timeNanos,
Attributes.empty(), List.of())
);
SumData<? extends PointData> mySumData = new MySumData<>(
true, AggregationTemporality.CUMULATIVE, myPoints
);
MetricData myMetricData = new MyMetricData(
Resource.getDefault(), InstrumentationScopeInfo.empty(),
"my-sweet-metric", "my-description", "1",
MetricDataType.DOUBLE_SUM, mySumData);
exporter.export(List.of(myMetricData)); // -> ClassCastException
SumData<DoublePointData> myDoubleSumData = myMetricData.getDoubleSumData(); // -> ClassCastException
}
}
record MyMetricData(Resource resource, InstrumentationScopeInfo instrumentationScopeInfo,
String name, String description, String unit, MetricDataType type, Data<? extends PointData> data)
implements MetricData {/* getters */}
record MySumData<T extends PointData>(boolean isMonotonic,
AggregationTemporality aggregationTemporality, Collection<T> points)
implements SumData<T> {/* getters */}
record MyDoublePointData(double value, long startEpochNanos, long epochNanos,
Attributes attributes, List<DoubleExemplarData> exemplars
) implements DoublePointData {/* getters */}
What did you expect to see?
exporter.export(...) should successfully export the metric and its datapoints.
myData.getDoubleSumData() should successfully return the MySumData instance, as it conforms to the SumData interface. No exception should be thrown.
What did you see instead?
A java.lang.ClassCastException is thrown because the MetricData.getDoubleSumData() method attempts to cast MySumData to io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData:
Exception in thread "main" java.lang.ClassCastException: class MySumData cannot be cast to class io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData (MySumData and io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData are in unnamed module of loader 'app')
at io.opentelemetry.sdk.metrics.data.MetricData.getDoubleSumData(MetricData.java:125)
What version and what artifacts are you using?
Artifacts: opentelemetry-sdk-metrics
Version: 1.52.0
How did you reference these artifacts?
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>2.18.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
</dependencies>
Environment
Compiler: openlogic-openjdk-17.0.12+7-windows-x64
OS: Windows 11
Additional context
The very same experiment works with Long type metrics, as in MetricData.getLongSumData return (SumData<LongPointData>) getData(); is used. On the contrary, MetricData.getDoubleSumData() uses this cast: (ImmutableSumData<DoublePointData>) getData(). Using the ImmutableSumData class in a custom MetricData implementation is advised against in its documentation: "This class is internal and is hence not for public use. Its APIs are unstable and can change at any time.". I believe the intent was to cast the getData result to SumData in getDoubleSumData, just like in getLongSumData, having the ImmutableSumData class is just a oversight.