Skip to content

[METRICS] Fix stale async attribute sets in cumulative exports (#4108) - #4484

Open
vahle-at-psu wants to merge 1 commit into
open-telemetry:mainfrom
vahle-at-psu:pr-4140
Open

[METRICS] Fix stale async attribute sets in cumulative exports (#4108)#4484
vahle-at-psu wants to merge 1 commit into
open-telemetry:mainfrom
vahle-at-psu:pr-4140

Conversation

@vahle-at-psu

@vahle-at-psu vahle-at-psu commented Aug 26, 2026

Copy link
Copy Markdown

Summary

Fixes #4108.

This PR continues the work from #4140 by @pranitaurlam. Since the original author has been unresponsive for several weeks and the fix is needed in production, I am taking over the PR, giving full credit to @pranitaurlam for the original analysis and fix skeleton.

Async instruments (ObservableCounter, ObservableGauge, ObservableUpDownCounter) under cumulative temporality emitted attribute sets indefinitely after the callback stopped reporting them, violating the OTel spec:

The implementation SHOULD NOT produce aggregated metric data for a
previously-observed attribute set which is not observed during a
successful callback.


Root cause

TemporalMetricStorage::buildMetrics() unconditionally carried every entry from last_reported_metrics_ into the cumulative output, even when the attribute set was absent from the current delta.


Changes

temporal_metric_storage.h / .cc

  • Add is_async_ boolean (default false) to TemporalMetricStorage.
  • In the cumulative merge lambda, add else if (!is_async_) guard: sync instruments still carry forward all attribute sets (existing behaviour, spec-correct); async instruments skip entries not present in the current delta.

async_metric_storage.h

  • Pass is_async = true when constructing TemporalMetricStorage.
  • Do not prune cumulative_hash_map_ in Collect() (deviation from [METRICS] Drop stale async attribute sets from cumulative exports #4140): the absolute-value baseline is preserved across absent cycles. This ensures correct incremental delta computation when an attribute set reappears after a gap (delta = new − last_seen, not new), consistent with opentelemetry-dotnet#6883. Stale entries are suppressed at export time by the is_async_ guard instead.

Behaviour

Scenario Before After
Async cumulative, attribute dropped by callback Emitted forever with stale value Dropped from next export
Async delta, attribute reappears after gap Emitted as full new value Emitted as increment since last seen
Sync cumulative, attribute not measured this cycle Carried forward (correct) Unchanged
Delta temporality (async or sync) Correct (unaffected) Unchanged

Sister SDKs with equivalent fixes:

  • opentelemetry-dotnet#6883 (merged Feb 2026)
  • opentelemetry-rust#2618 (merged Feb 2025)

Tests

Two regression tests added in async_metric_storage_test.cc, addressing lalitb's review request on #4140:

  • StaleAttributeSetDroppedInCumulativeExport: attribute present in
    collection 1, absent in collection 2 → must not appear in collection
    2's cumulative export.
  • AttributeReappearanceAfterGapDeltaTemporality: A=10 → missing → A=11 under delta temporality → emitted delta must be 1, not 11,
    confirming the baseline is preserved across the absent cycle.

Co-authored-by: pranitaurlam 227409059+pranitaurlam@users.noreply.github.com

@vahle-at-psu
vahle-at-psu requested a review from a team as a code owner August 26, 2026 09:29
@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 26, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: pranitaurlam / name: pranitaurlam (e9cc6f3)
  • ✅ login: vahle-at-psu / name: Patrick Summerer (e9cc6f3)

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.13%. Comparing base (75e36ac) to head (fabd660).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4484      +/-   ##
==========================================
+ Coverage   83.12%   83.13%   +0.02%     
==========================================
  Files         519      519              
  Lines       20256    20258       +2     
==========================================
+ Hits        16835    16839       +4     
+ Misses       3421     3419       -2     
Files with missing lines Coverage Δ
...telemetry/sdk/metrics/state/async_metric_storage.h 93.19% <100.00%> (ø)
sdk/src/metrics/state/temporal_metric_storage.cc 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…telemetry#4108)

Async instruments (ObservableCounter, ObservableGauge,
ObservableUpDownCounter) under cumulative temporality were emitting
attribute sets indefinitely after the callback stopped reporting them,
violating the OTel spec requirement:

  "The implementation SHOULD NOT produce aggregated metric data for a
   previously-observed attribute set which is not observed during a
   successful callback."

Root cause: `TemporalMetricStorage::buildMetrics()` unconditionally
carried every entry from `last_reported_metrics_` into the output even
when it was absent from the current delta.

Fix:
- Add `is_async_` flag (default false) to `TemporalMetricStorage`.
  The cumulative merge now skips entries not present in the current
  delta for async instruments, while sync instruments retain the
  existing carry-forward behaviour.
- Pass `is_async = true` when constructing `TemporalMetricStorage`
  from `AsyncMetricStorage`.
- Do NOT prune `cumulative_hash_map_` in `AsyncMetricStorage::Collect()`
  so that the absolute-value baseline is preserved across absent cycles.
  This ensures correct delta computation (new - last_seen, not the full
  new value) when an attribute set reappears after a gap — consistent
  with the approach taken by opentelemetry-dotnet#6883.

Tests added in async_metric_storage_test.cc:
- StaleAttributeSetDroppedInCumulativeExport: verifies that an
  attribute set absent from the callback is not emitted in subsequent
  cumulative exports.
- AttributeReappearanceAfterGapDeltaTemporality: verifies that an
  attribute set reappearing after an absent cycle emits only the
  increment since last observed (delta = 1, not 11), confirming the
  baseline is correctly preserved.

Fixes open-telemetry#4108

Co-authored-by: pranitaurlam <227409059+pranitaurlam@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[METRICS SDK] Async instruments don't drop unreported attribute sets under Cumulative temporality

1 participant