Skip to content

MetricData.getDoubleSumData() causes ClassCastException by casting to internal ImmutableSumData #7571

@halasz-csaba

Description

@halasz-csaba

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions