-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Use sync.Map and atomics for lastvalue aggregations #7478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,117 +5,179 @@ package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggreg | |||||
|
|
||||||
| import ( | ||||||
| "context" | ||||||
| "sync" | ||||||
| "time" | ||||||
|
|
||||||
| "go.opentelemetry.io/otel/attribute" | ||||||
| "go.opentelemetry.io/otel/sdk/metric/metricdata" | ||||||
| ) | ||||||
|
|
||||||
| // datapoint is timestamped measurement data. | ||||||
| type datapoint[N int64 | float64] struct { | ||||||
| // lastValuePoint is timestamped measurement data. | ||||||
| type lastValuePoint[N int64 | float64] struct { | ||||||
| attrs attribute.Set | ||||||
| value N | ||||||
| value atomicN[N] | ||||||
| res FilteredExemplarReservoir[N] | ||||||
| } | ||||||
|
|
||||||
| func newLastValue[N int64 | float64](limit int, r func(attribute.Set) FilteredExemplarReservoir[N]) *lastValue[N] { | ||||||
| return &lastValue[N]{ | ||||||
| // lastValue summarizes a set of measurements as the last one made. | ||||||
| type lastValueMap[N int64 | float64] struct { | ||||||
| newRes func(attribute.Set) FilteredExemplarReservoir[N] | ||||||
| values limitedSyncMap | ||||||
| } | ||||||
|
|
||||||
| func (s *lastValueMap[N]) measure( | ||||||
| ctx context.Context, | ||||||
| value N, | ||||||
| fltrAttr attribute.Set, | ||||||
| droppedAttr []attribute.KeyValue, | ||||||
| ) { | ||||||
| lv := s.values.LoadOrStoreAttr(fltrAttr, func(attr attribute.Set) any { | ||||||
| return &lastValuePoint[N]{ | ||||||
| res: s.newRes(attr), | ||||||
| attrs: attr, | ||||||
| } | ||||||
| }).(*lastValuePoint[N]) | ||||||
|
|
||||||
| lv.value.Store(value) | ||||||
| lv.res.Offer(ctx, value, droppedAttr) | ||||||
| } | ||||||
|
|
||||||
| func newDeltaLastValue[N int64 | float64]( | ||||||
| limit int, | ||||||
| r func(attribute.Set) FilteredExemplarReservoir[N], | ||||||
| ) *deltaLastValue[N] { | ||||||
| return &deltaLastValue[N]{ | ||||||
| newRes: r, | ||||||
| limit: newLimiter[datapoint[N]](limit), | ||||||
| values: make(map[attribute.Distinct]*datapoint[N]), | ||||||
| start: now(), | ||||||
| hotColdValMap: [2]lastValueMap[N]{ | ||||||
| { | ||||||
| values: limitedSyncMap{aggLimit: limit}, | ||||||
| newRes: r, | ||||||
| }, | ||||||
| { | ||||||
| values: limitedSyncMap{aggLimit: limit}, | ||||||
| newRes: r, | ||||||
| }, | ||||||
| }, | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // lastValue summarizes a set of measurements as the last one made. | ||||||
| type lastValue[N int64 | float64] struct { | ||||||
| sync.Mutex | ||||||
|
|
||||||
| // deltaLastValue summarizes a set of measurements as the last one made. | ||||||
| type deltaLastValue[N int64 | float64] struct { | ||||||
| newRes func(attribute.Set) FilteredExemplarReservoir[N] | ||||||
| limit limiter[datapoint[N]] | ||||||
| values map[attribute.Distinct]*datapoint[N] | ||||||
| start time.Time | ||||||
| } | ||||||
|
|
||||||
| func (s *lastValue[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) { | ||||||
| s.Lock() | ||||||
| defer s.Unlock() | ||||||
|
|
||||||
| d, ok := s.values[fltrAttr.Equivalent()] | ||||||
| if !ok { | ||||||
| fltrAttr = s.limit.Attributes(fltrAttr, s.values) | ||||||
| d = &datapoint[N]{ | ||||||
| res: s.newRes(fltrAttr), | ||||||
| attrs: fltrAttr, | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| d.value = value | ||||||
| d.res.Offer(ctx, value, droppedAttr) | ||||||
| hcwg hotColdWaitGroup | ||||||
| hotColdValMap [2]lastValueMap[N] | ||||||
| } | ||||||
|
|
||||||
| s.values[fltrAttr.Equivalent()] = d | ||||||
| func (s *deltaLastValue[N]) measure( | ||||||
| ctx context.Context, | ||||||
| value N, | ||||||
| fltrAttr attribute.Set, | ||||||
| droppedAttr []attribute.KeyValue, | ||||||
| ) { | ||||||
| hotIdx := s.hcwg.start() | ||||||
| defer s.hcwg.done(hotIdx) | ||||||
| s.hotColdValMap[hotIdx].measure(ctx, value, fltrAttr, droppedAttr) | ||||||
| } | ||||||
|
|
||||||
| func (s *lastValue[N]) delta( | ||||||
| func (s *deltaLastValue[N]) collect( | ||||||
| dest *metricdata.Aggregation, //nolint:gocritic // The pointer is needed for the ComputeAggregation interface | ||||||
| ) int { | ||||||
| t := now() | ||||||
| n := s.copyAndClearDpts(dest, t) | ||||||
| // Update start time for delta temporality. | ||||||
| s.start = t | ||||||
| return n | ||||||
| } | ||||||
|
|
||||||
| // copyAndClearDpts copies the lastValuePoints held by s into dest. The number of lastValuePoints | ||||||
| // copied is returned. | ||||||
| func (s *deltaLastValue[N]) copyAndClearDpts( | ||||||
| dest *metricdata.Aggregation, //nolint:gocritic // The pointer is needed for the ComputeAggregation interface | ||||||
| t time.Time, | ||||||
| ) int { | ||||||
| // Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of | ||||||
| // the DataPoints is missed (better luck next time). | ||||||
| // the lastValuePoints is missed (better luck next time). | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This is still referring to the |
||||||
| gData, _ := (*dest).(metricdata.Gauge[N]) | ||||||
| // delta always clears values on collection | ||||||
| readIdx := s.hcwg.swapHotAndWait() | ||||||
| // The len will not change while we iterate over values, since we waited | ||||||
| // for all writes to finish to the cold values and len. | ||||||
| n := s.hotColdValMap[readIdx].values.Len() | ||||||
| dPts := reset(gData.DataPoints, n, n) | ||||||
|
|
||||||
| s.Lock() | ||||||
| defer s.Unlock() | ||||||
|
|
||||||
| n := s.copyDpts(&gData.DataPoints, t) | ||||||
| var i int | ||||||
| s.hotColdValMap[readIdx].values.Range(func(_, value any) bool { | ||||||
| v := value.(*lastValuePoint[N]) | ||||||
| dPts[i].Attributes = v.attrs | ||||||
| dPts[i].StartTime = s.start | ||||||
| dPts[i].Time = t | ||||||
| dPts[i].Value = v.value.Load() | ||||||
| collectExemplars[N](&dPts[i].Exemplars, v.res.Collect) | ||||||
| i++ | ||||||
| return true | ||||||
| }) | ||||||
| gData.DataPoints = dPts | ||||||
| // Do not report stale values. | ||||||
| clear(s.values) | ||||||
| // Update start time for delta temporality. | ||||||
| s.start = t | ||||||
|
|
||||||
| s.hotColdValMap[readIdx].values.Clear() | ||||||
| *dest = gData | ||||||
|
|
||||||
| return n | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
If range for some reason does not iterate over |
||||||
| } | ||||||
|
|
||||||
| func (s *lastValue[N]) cumulative( | ||||||
| // cumulativeLastValue summarizes a set of measurements as the last one made. | ||||||
| type cumulativeLastValue[N int64 | float64] struct { | ||||||
| lastValueMap[N] | ||||||
| start time.Time | ||||||
| } | ||||||
|
|
||||||
| func newCumulativeLastValue[N int64 | float64]( | ||||||
| limit int, | ||||||
| r func(attribute.Set) FilteredExemplarReservoir[N], | ||||||
| ) *cumulativeLastValue[N] { | ||||||
| return &cumulativeLastValue[N]{ | ||||||
| lastValueMap: lastValueMap[N]{ | ||||||
| values: limitedSyncMap{aggLimit: limit}, | ||||||
| newRes: r, | ||||||
| }, | ||||||
| start: now(), | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| func (s *cumulativeLastValue[N]) collect( | ||||||
| dest *metricdata.Aggregation, //nolint:gocritic // The pointer is needed for the ComputeAggregation interface | ||||||
| ) int { | ||||||
| t := now() | ||||||
| // Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of | ||||||
| // the DataPoints is missed (better luck next time). | ||||||
| // the lastValuePoints is missed (better luck next time). | ||||||
| gData, _ := (*dest).(metricdata.Gauge[N]) | ||||||
|
|
||||||
| s.Lock() | ||||||
| defer s.Unlock() | ||||||
| // Values are being concurrently written while we iterate, so only use the | ||||||
| // current length for capacity. | ||||||
| dPts := reset(gData.DataPoints, 0, s.values.Len()) | ||||||
|
|
||||||
| n := s.copyDpts(&gData.DataPoints, t) | ||||||
| var i int | ||||||
| s.values.Range(func(_, value any) bool { | ||||||
| v := value.(*lastValuePoint[N]) | ||||||
| newPt := metricdata.DataPoint[N]{ | ||||||
| Attributes: v.attrs, | ||||||
| StartTime: s.start, | ||||||
| Time: t, | ||||||
| Value: v.value.Load(), | ||||||
| } | ||||||
| collectExemplars[N](&newPt.Exemplars, v.res.Collect) | ||||||
| dPts = append(dPts, newPt) | ||||||
| i++ | ||||||
| return true | ||||||
| }) | ||||||
| gData.DataPoints = dPts | ||||||
| // TODO (#3006): This will use an unbounded amount of memory if there | ||||||
| // are unbounded number of attribute sets being aggregated. Attribute | ||||||
| // sets that become "stale" need to be forgotten so this will not | ||||||
| // overload the system. | ||||||
| *dest = gData | ||||||
|
|
||||||
| return n | ||||||
| } | ||||||
|
|
||||||
| // copyDpts copies the datapoints held by s into dest. The number of datapoints | ||||||
| // copied is returned. | ||||||
| func (s *lastValue[N]) copyDpts(dest *[]metricdata.DataPoint[N], t time.Time) int { | ||||||
| n := len(s.values) | ||||||
| *dest = reset(*dest, n, n) | ||||||
|
|
||||||
| var i int | ||||||
| for _, v := range s.values { | ||||||
| (*dest)[i].Attributes = v.attrs | ||||||
| (*dest)[i].StartTime = s.start | ||||||
| (*dest)[i].Time = t | ||||||
| (*dest)[i].Value = v.value | ||||||
| collectExemplars(&(*dest)[i].Exemplars, v.res.Collect) | ||||||
| i++ | ||||||
| } | ||||||
| return n | ||||||
| return i | ||||||
| } | ||||||
|
|
||||||
| // newPrecomputedLastValue returns an aggregator that summarizes a set of | ||||||
|
|
@@ -124,51 +186,23 @@ func newPrecomputedLastValue[N int64 | float64]( | |||||
| limit int, | ||||||
| r func(attribute.Set) FilteredExemplarReservoir[N], | ||||||
| ) *precomputedLastValue[N] { | ||||||
| return &precomputedLastValue[N]{lastValue: newLastValue[N](limit, r)} | ||||||
| return &precomputedLastValue[N]{deltaLastValue: newDeltaLastValue[N](limit, r)} | ||||||
| } | ||||||
|
|
||||||
| // precomputedLastValue summarizes a set of observations as the last one made. | ||||||
| type precomputedLastValue[N int64 | float64] struct { | ||||||
| *lastValue[N] | ||||||
| *deltaLastValue[N] | ||||||
| } | ||||||
|
|
||||||
| func (s *precomputedLastValue[N]) delta( | ||||||
| dest *metricdata.Aggregation, //nolint:gocritic // The pointer is needed for the ComputeAggregation interface | ||||||
| ) int { | ||||||
| t := now() | ||||||
| // Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of | ||||||
| // the DataPoints is missed (better luck next time). | ||||||
| gData, _ := (*dest).(metricdata.Gauge[N]) | ||||||
|
|
||||||
| s.Lock() | ||||||
| defer s.Unlock() | ||||||
|
|
||||||
| n := s.copyDpts(&gData.DataPoints, t) | ||||||
| // Do not report stale values. | ||||||
| clear(s.values) | ||||||
| // Update start time for delta temporality. | ||||||
| s.start = t | ||||||
|
|
||||||
| *dest = gData | ||||||
|
|
||||||
| return n | ||||||
| return s.collect(dest) | ||||||
| } | ||||||
|
|
||||||
| func (s *precomputedLastValue[N]) cumulative( | ||||||
| dest *metricdata.Aggregation, //nolint:gocritic // The pointer is needed for the ComputeAggregation interface | ||||||
| ) int { | ||||||
| t := now() | ||||||
| // Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of | ||||||
| // the DataPoints is missed (better luck next time). | ||||||
| gData, _ := (*dest).(metricdata.Gauge[N]) | ||||||
|
|
||||||
| s.Lock() | ||||||
| defer s.Unlock() | ||||||
|
|
||||||
| n := s.copyDpts(&gData.DataPoints, t) | ||||||
| // Do not report stale values. | ||||||
| clear(s.values) | ||||||
| *dest = gData | ||||||
|
|
||||||
| return n | ||||||
| // Do not reset the start time. | ||||||
| return s.copyAndClearDpts(dest, now()) | ||||||
| } | ||||||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.