Skip to content

Metric attribute hashing folds True, 1 and 1.0 into a single stream #5572

Description

@dwin-gharibi

Describe your environment

Labels: bug, sdk, metrics, data-correctness
Affected packages: opentelemetry-sdk
Found on: main @ 0a5d76b6
Environment: CPython 3.12

What happened?

_hash_attributes returns scalar attribute values unchanged as part of the aggregation key. Python compares True == 1 == 1.0 and hashes all three identically, so attribute sets that are distinct in the OpenTelemetry data model - OTLP encodes them as bool_value, int_value and double_value - collapse into a single time series. The surviving series is labelled with whichever type happened to arrive first.

The same flaw makes a sequence of pairs hash identically to the mapping it resembles.

Steps to Reproduce

from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader

reader = InMemoryMetricReader()
provider = MeterProvider(metric_readers=[reader])
counter = provider.get_meter("m").create_counter("c")

counter.add(1, {"k": True})
counter.add(1, {"k": 1})
counter.add(1, {"k": 1.0})

data = reader.get_metrics_data()
points = data.resource_metrics[0].scope_metrics[0].metrics[0].data.data_points
print(len(points), [dict(p.attributes) for p in points], [p.value for p in points])

Expected Result

Three data points, each labelled with the attribute value and type that was actually recorded.

Actual Result

data points produced: 1   (expected 3)
  attributes={'k': True}  value_type=bool  count=3

# unit level
_hash_attributes({'k': True}) = (('k', True),)
_hash_attributes({'k': 1})    = (('k', 1),)
_hash_attributes({'k': 1.0})  = (('k', 1.0),)
all equal as dict keys: True, same hash: True

# a different instrument, different values
h.record(1, {"flag": False}); h.record(2, {"flag": 0})
data points produced: 1 (expected 2)

Additional context

Silent metric corruption in two directions at once. Counts from unrelated series are summed together, so the surviving series over-reports; and that series is exported with an attribute value of the wrong type, so a backend grouping on it sees a label that was never recorded.

This is easy to hit without doing anything unusual: a boolean flag attribute recorded as True by one code path and as 1 by another, or a numeric attribute that arrives as an int from one caller and a float from another, is enough.

Would you like to implement a fix?

Yes

Tip

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

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