[Metrics] Implement cumulative aggregation for async Long and Double counters in MetricPoint#7227
[Metrics] Implement cumulative aggregation for async Long and Double counters in MetricPoint#7227nabutabu wants to merge 22 commits into
Conversation
…counters in MetricPoint
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7227 +/- ##
==========================================
- Coverage 90.19% 90.17% -0.03%
==========================================
Files 285 279 -6
Lines 15376 14864 -512
==========================================
- Hits 13869 13404 -465
+ Misses 1507 1460 -47
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
|
||
| case AggregationType.LongSumIncomingCumulative: | ||
| { | ||
| Interlocked.Add(ref this.runningValue.AsLong, number); |
There was a problem hiding this comment.
This is not correct. If incoming is cumulative, then it is already the sum. We (i.e SDK) shouldn't add it.
cijothomas
left a comment
There was a problem hiding this comment.
See
https://github.com/open-telemetry/opentelemetry-dotnet/pull/7227/changes#r3168921622
Let me know if I misunderstood the fix.
… Double counters in MetricPoint" This reverts commit 76d6a88.
|
Fix was incorrect and the issue was that I did not take into account a couple of things which the failing tests also pointed out. Changing Basically if a MetricPoint is in state CollectPending and is IsAsynchronous then the value sent by |
| ref var metricPoint = ref this.metricPoints[index]; | ||
| if (metricPoint.MetricPointStatus == MetricPointStatus.CollectPending) | ||
| { | ||
| value += metricPoint.GetRunningValueLong(); |
There was a problem hiding this comment.
The idea of modifying the incoming value to achieve "spatial aggregation" is good - but it does create incorrect Exemplar values.
It is not that bad - as Exemplars have limited utility in Observable instruments anyway and Exemplars are disabled also by-default for Observable. We should see if the fix can keep Exemplars correct as well. (Exemplar should get the original unmodified value, but Metricpoint should get the updated one..)
There was a problem hiding this comment.
Read more about Exemplars and this does complicate this issue a bit. Blurting out my understanding of what's happening for future me:
Exemplars are defined as: "access to the raw measurement value, time stamp when measurement was made, and trace context", which means each exemplar recording should be the raw reading instead of the cumulative value that the (non-exemplar) async cumulative instrument uses.
This is important because each exemplar metric recording is also associated with a trace. If exemplars stored aggregated values, we'd lose the connection to individual traces for each recording. The exemplar's purpose is to preserve the context of specific measurements that contribute to the aggregate, which this current change loses.
I'll come back when I have more on how we could fix this.
- First thought is to add a parameter to
UpdateWithExemplarinMetricPoint-> "number" would be the cumulative value and the new parameter is the measured value. - MetricPoint does already have access to its own running value, maybe we could just subtract that when calling UpdateExemplar, this would need to happen before
Interlocked.Addis called on the running value. Also would this work for every kind of instrument?
| } | ||
|
|
||
| [Theory(Skip = "https://github.com/open-telemetry/opentelemetry-specification/issues/1874")] | ||
| [Theory] |
There was a problem hiding this comment.
I added just one test to prove that we don't do spatial-aggregation in this SDK. We need much more coverage when fixing the issue. Covering delta/cumulative, multiple cycles, various view combinations.
cijothomas
left a comment
There was a problem hiding this comment.
The current iteration looks very promising (thanks! This was indeed a hard problem!).
Requesting changes to address below comment
https://github.com/open-telemetry/opentelemetry-dotnet/pull/7227/changes#r3183554865
Also, we need to vastly improve test coverage when doing this fix- lot of potential edge cases. (I spotted only one about Exemplar value, but could be more)
…ng and double metrics
|
@cijothomas I added as many tests as I could think of, but I am not sure if this is all that needs coverage. Would appreciate any guidance on any other nuances of the measurements that could potentially be affected! |
|
This PR was marked stale due to lack of activity and will be closed in 7 days. Commenting or pushing will instruct the bot to automatically remove the label. This bot runs once per day. |
There was a problem hiding this comment.
Pull request overview
Implements spatial (cumulative-within-collection) aggregation for asynchronous ObservableCounter<T> streams when a view filters attributes (TagKeys) such that multiple measurements collapse into the same resulting attribute set, while ensuring exemplars continue to record the raw measurement value (not the spatially-aggregated sum). This aligns the SDK’s observable counter behavior with expected semantics and re-enables previously skipped coverage.
Changes:
- Add spatial-aggregation handling for async cumulative long/double sums under tag filtering, and plumb raw measurement values separately into exemplar updates.
- Unskip and expand regression tests for observable counter spatial aggregation (including multi-cycle + double).
- Add exemplar-focused tests validating exemplar collection under spatial aggregation and across cycles.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/OpenTelemetry/Metrics/AggregatorStore.cs |
Adjusts async cumulative update behavior under tag filtering to spatially aggregate collapsed series and preserve raw exemplar values. |
src/OpenTelemetry/Metrics/MetricPoint/MetricPoint.cs |
Extends UpdateWithExemplar to accept a separate exemplar value and exposes running-value reads needed for spatial aggregation. |
test/OpenTelemetry.Tests/Metrics/MetricApiTests.cs |
Unskips and adds/extends spatial aggregation regression tests for observable counters (long/double, multi-cycle, partial filtering). |
test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs |
Adds exemplar regression coverage under spatial aggregation (including multi-cycle behavior). |
test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs |
Adds a regression test guarding synchronous counter behavior under tag filtering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Asked Codex/GPT5.5 to check this PR. Review Notes and Unit Test Proposals for PR #7227I think there are two correctness risks worth covering with targeted unit tests. Issue 1: Delta temporality is not correct when filtered streams changeThe current fix spatially aggregates async cumulative counter values by updating the already-filtered That works for simple cumulative export cases where the same underlying streams are reported every cycle. The problem is delta export. That is not equivalent to summing per-stream deltas if one original measurement stream disappears or reappears between callbacks. Example: If the SDK diffs collapsed values, delta export becomes: The expected delta is: The absent stream should not subtract from the collapsed aggregate. Delta needs to be calculated per original measurement stream before spatial aggregation, or the implementation needs equivalent per-stream state. Suggested test: long observable counter, one stream disappearsAdd a regression test for Scenario: This should fail if the SDK diffs only the collapsed aggregate, because it would export Suggested test: long observable counter, unchanged stream and missing streamThis is the smallest version of the same bug. Scenario: An aggregate-diff implementation would export: The expected result is zero because the only stream still reported did not change. Suggested test: double observable counter, one stream disappearsAdd the same coverage for Scenario: This covers the Suggested test: stream reappears after being absentThis verifies that per-stream state is preserved across multiple collection cycles. Scenario: Collection 3 should be: This catches both disappearing and reappearing stream behavior under spatial aggregation. Issue 2: Custom-tag overflow can throw before the overflow metric point is used
The new custom-tag async branches read the metric point before that existing negative-index handling runs: If This applies to both: Suggested test: long observable counter custom-tag overflowAdd a test that forces the custom-tag path and exhausts the cardinality limit. Shape: This guards against indexing |
[MetricApiTests] Add regression tests for delta correctness in spatial aggregation [MetricExemplarTests] Verify single exported item after flush [MetricViewTests] Update test name for clarity on tag filtering aggregation ObservableCounterSpatialAggregationDelta_StreamDisappears tests added expose how the current fix accumulates streams into a single runningValue on the MetricPoint. TakeSnapshot(outputDelta: true) then computes the delta as:
|
The delta temporality behavior under spatial aggregation is a known open question for the spec and @cijothomas has already filed open-telemetry/opentelemetry-specification#4861 to track it. The core issue is what delta to emit when a stream disappears or reappears:
I think we should skip the four failing delta tests I added and reference the issue mentioned above: ObservableCounterSpatialAggregationDelta_UnchangedStreamAndMissingStream_DeltaIsZero
ObservableCounterSpatialAggregationDelta_StreamDisappears_DeltaIsCorrect
ObservableCounterSpatialAggregationDelta_StreamReappearsAfterGap_DeltaIsCorrect
ObservableCounterSpatialAggregationDelta_Double_StreamDisappears_DeltaIsCorrectSeems like we added the fix to remove |
… pending spec decision
|
@cijothomas Could I get another review here whenever possible? Thank you :) |
Fixes #7215
Design discussion issue NA
Changes
Use
InterlockedHelper.Addin Update calls and reset aggregation values inTakeSnapshotusingInterlocked.Exchange(ref this.runningValue.AsLong, 0);Merge requirement checklist
CHANGELOG.mdfiles updated for non-trivial changes