Repository navigation
[METRICS] Fix stale async attribute sets in cumulative exports (#4108) - #4484
vahle-at-psu wants to merge 17 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4484 +/- ##
==========================================
+ Coverage 86.79% 86.82% +0.03%
==========================================
Files 525 525
Lines 20729 20753 +24
==========================================
+ Hits 17989 18016 +27
+ Misses 2740 2737 -3
🚀 New features to boost your workflow:
|
…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>
|
Built this on
Would you rather keep the carry-forward in |
good catch! I moved the is_async guard logic to the export callback, so the baseline state gets kept.
Hope everything is addressed now :-) |
|
rebuilt on also ran the two-collector cumulative case, since the new multi-collector test is delta and the one note, not a blocker. the filter tests looks right to me otherwise. |
…ttribute suppression The is_async_ cumulative export guard tested the shared delta_metrics map, which is only populated for whichever collector drains it first in a given cycle. Every other collector saw an empty map and had its already-observed attribute sets incorrectly suppressed for that cycle. Replace the check with a per-collector "observed this cycle" set, captured from each collector's own merged unreported deltas before the cumulative baseline is merged in. This preserves the open-telemetry#4108 stale-drop behavior while making it independent of collection order across collectors. Adds regression coverage for: - stale attribute suppression with two collectors - a collector lagging behind another by multiple cycles - start_ts/end_ts correctness for the multi-collector cumulative and delta paths
Again well analyzed - thank you! |
…ttribute suppression The is_async_ cumulative export guard tested the shared delta_metrics map, which is only populated for whichever collector drains it first in a given cycle. Every other collector saw an empty map and had its already-observed attribute sets incorrectly suppressed for that cycle. Replace the check with a per-collector "observed this cycle" set, captured from each collector's own merged unreported deltas before the cumulative baseline is merged in. This preserves the open-telemetry#4108 stale-drop behavior while making it independent of collection order across collectors. Adds regression coverage for: - stale attribute suppression with two collectors - a collector lagging behind another by multiple cycles - start_ts/end_ts correctness for the multi-collector cumulative and delta paths
|
In PR desc,
Please update the description's delta-reappearance row to say "unchanged." Main already preserves the baseline across gaps, and this PR does not change that delta behavior. |
# Conflicts: # sdk/test/metrics/async_metric_storage_test.cc
…sync_metric_storage_test.cc
|
641ce25 fixed format and clang-tidy issues |
mateenali66
left a comment
There was a problem hiding this comment.
re-checked at 641ce251. the cumulative baseline point is fixed, and lalitb's two-reader case now exports {GET=12} for counter, gauge and up-down.
one regression at the cardinality limit. two readers, default limit, no view. reader 1's round observes 2000 sets, then reader 2 (cumulative) observes only x=7. reader 2 exports 0 points at head, main exports OVERFLOW=7. x goes to the overflow key when reader 2's queue is merged, and the filter at temporal_metric_storage.cc:206 looks the overflow key up in delta_metrics, where it never is. the spec says measurements MUST NOT be dropped during an overflow.
smaller thing, every new test uses ObservableCounter, but #4108 reports gauge and up-down, which go through BuildDeltaFromRound since #4635.
Thx! 5781472 incorporates your feedback |
mateenali66
left a comment
There was a problem hiding this comment.
re-checked at d6f95afc. the overflow drop is fixed at both limits, and the new test covers gauge and up-down.
one regression from the new merge order. temporal_metric_storage.cc:144-157 admits the previous cumulative state before the current round for async instruments too, and stale async sets stay there, so once a reader has held limit sets every later set exports only as overflow. one cumulative reader, gauge, 2000 sets once, then only x=7. head exports {OVERFLOW=7}, main and 641ce251 export x=7. the next round, {x=8, y=3}, gives {OVERFLOW=8}. the spec has async instruments prefer the callback's first-observed attributes.
keeping that order for sync and merging the reader's own round first for async cumulative fixed both locally. reader 2 then exports x=7, and only AsyncCumulativeOverflowIsExportedForLaggingReader changes.
the sync side changed too, limit 2 now keeps a and b plus OVERFLOW=12, but the description says sync is unchanged and no test pins it.
sorry for always reintroducing regressions - I am missing the big picture and dependencies within the code base. With cdf2aff I instructed Copilot to fulfill all relevant metric specs, address feedback of all reviewers in this PR while keeping the changes minimal (except in tests) Do you agree with continuing this change/review iteration in this way or does it make sense to revisit the whole topic again from beginning? |
Summary
Fixes #4108. Asynchronous instruments using cumulative aggregation temporality no longer export attribute sets that were absent from the current reader callback. Their cumulative baselines are retained so values remain correct when observations reappear.
Root Cause
TemporalMetricStorage::buildMetrics()carries cumulative aggregations forward between collections. This state is needed to preserve cumulative values, but stale attribute sets must not be included in every export.Cardinality overflow also needs special handling: a current observation can be merged into
otel.metric.overflow, even though its original attribute set is not present under that key.Changes
Behavior
With multiple readers, cumulative async points are exported only when observed in that reader's current callback round. A lagging reader may omit a point at an intermediate collection; the cumulative baseline is retained and exported after the attribute is observed again.
Tests
The following tests run with both cumulative and delta temporality:
StaleAttributeSetDroppedAttributeReappearanceAfterGapNonMonotonicInstrumentsDropStaleObservationsStaleAttributeSetDroppedMultiCollectorMultiCollectorLaggingCollectorMultiCollectorAdditional tests:
WritableMetricStorageCardinalityLimit.AsyncCumulativeOverflowIsExportedForLaggingReaderMeterTest.AsyncCumulativeObservationsAreReaderScopedValidated with
cmake --build build --target async_metric_storage_test -j2andbuild/sdk/test/metrics/async_metric_storage_test --gtest_brief=1(26 tests passed).Co-authored-by: pranitaurlam 227409059+pranitaurlam@users.noreply.github.com