@@ -20,17 +20,17 @@ import (
20
20
"go.opentelemetry.io/otel/api/core"
21
21
export "go.opentelemetry.io/otel/sdk/export/metric"
22
22
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
23
+ "go.opentelemetry.io/otel/sdk/internal"
23
24
)
24
25
25
26
type (
26
27
// Aggregator aggregates measure events, keeping only the max,
27
28
// sum, and count.
28
29
Aggregator struct {
29
- // current has to be aligned for 64-bit atomic operations.
30
- current state
31
- // checkpoint has to be aligned for 64-bit atomic operations.
32
- checkpoint state
33
- kind core.NumberKind
30
+ // states has to be aligned for 64-bit atomic operations.
31
+ states [2 ]state
32
+ lock internal.StateLocker
33
+ kind core.NumberKind
34
34
}
35
35
36
36
state struct {
@@ -48,104 +48,116 @@ var _ aggregator.MinMaxSumCount = &Aggregator{}
48
48
// New returns a new measure aggregator for computing min, max, sum, and
49
49
// count. It does not compute quantile information other than Max.
50
50
//
51
- // Note that this aggregator maintains each value using independent
52
- // atomic operations, which introduces the possibility that
53
- // checkpoints are inconsistent. For greater consistency and lower
54
- // performance, consider using Array or DDSketch aggregators.
51
+ // This aggregator uses the StateLocker pattern to guarantee
52
+ // the count, sum, min and max are consistent within a checkpoint
55
53
func New (desc * export.Descriptor ) * Aggregator {
54
+ kind := desc .NumberKind ()
56
55
return & Aggregator {
57
- kind : desc .NumberKind (),
58
- current : unsetMinMaxSumCount (desc .NumberKind ()),
56
+ kind : kind ,
57
+ states : [2 ]state {
58
+ {
59
+ count : core .NewUint64Number (0 ),
60
+ sum : kind .Zero (),
61
+ min : kind .Maximum (),
62
+ max : kind .Minimum (),
63
+ },
64
+ {
65
+ count : core .NewUint64Number (0 ),
66
+ sum : kind .Zero (),
67
+ min : kind .Maximum (),
68
+ max : kind .Minimum (),
69
+ },
70
+ },
59
71
}
60
72
}
61
73
62
- func unsetMinMaxSumCount (kind core.NumberKind ) state {
63
- return state {min : kind .Maximum (), max : kind .Minimum ()}
64
- }
65
-
66
74
// Sum returns the sum of values in the checkpoint.
67
75
func (c * Aggregator ) Sum () (core.Number , error ) {
68
- return c .checkpoint .sum , nil
76
+ c .lock .Lock ()
77
+ defer c .lock .Unlock ()
78
+ return c .checkpoint ().sum , nil
69
79
}
70
80
71
81
// Count returns the number of values in the checkpoint.
72
82
func (c * Aggregator ) Count () (int64 , error ) {
73
- return int64 (c .checkpoint .count .AsUint64 ()), nil
83
+ c .lock .Lock ()
84
+ defer c .lock .Unlock ()
85
+ return c .checkpoint ().count .CoerceToInt64 (core .Uint64NumberKind ), nil
74
86
}
75
87
76
88
// Min returns the minimum value in the checkpoint.
77
- // The error value aggregator.ErrEmptyDataSet will be returned if
78
- // (due to a race condition) the checkpoint was set prior to
79
- // current.min being computed in Update().
80
- //
81
- // Note: If a measure's recorded values for a given checkpoint are
82
- // all equal to NumberKind.Maximum(), Min() will return ErrEmptyDataSet
89
+ // The error value aggregator.ErrEmptyDataSet will be returned
90
+ // if there were no measurements recorded during the checkpoint.
83
91
func (c * Aggregator ) Min () (core.Number , error ) {
84
- if c .checkpoint .min == c .kind .Maximum () {
85
- return core .Number (0 ), aggregator .ErrEmptyDataSet
92
+ c .lock .Lock ()
93
+ defer c .lock .Unlock ()
94
+ if c .checkpoint ().count .IsZero (core .Uint64NumberKind ) {
95
+ return c .kind .Zero (), aggregator .ErrEmptyDataSet
86
96
}
87
- return c .checkpoint .min , nil
97
+ return c .checkpoint () .min , nil
88
98
}
89
99
90
100
// Max returns the maximum value in the checkpoint.
91
- // The error value aggregator.ErrEmptyDataSet will be returned if
92
- // (due to a race condition) the checkpoint was set prior to
93
- // current.max being computed in Update().
94
- //
95
- // Note: If a measure's recorded values for a given checkpoint are
96
- // all equal to NumberKind.Minimum(), Max() will return ErrEmptyDataSet
101
+ // The error value aggregator.ErrEmptyDataSet will be returned
102
+ // if there were no measurements recorded during the checkpoint.
97
103
func (c * Aggregator ) Max () (core.Number , error ) {
98
- if c .checkpoint .max == c .kind .Minimum () {
99
- return core .Number (0 ), aggregator .ErrEmptyDataSet
104
+ c .lock .Lock ()
105
+ defer c .lock .Unlock ()
106
+ if c .checkpoint ().count .IsZero (core .Uint64NumberKind ) {
107
+ return c .kind .Zero (), aggregator .ErrEmptyDataSet
100
108
}
101
- return c .checkpoint .max , nil
109
+ return c .checkpoint () .max , nil
102
110
}
103
111
104
112
// Checkpoint saves the current state and resets the current state to
105
- // the empty set. Since no locks are taken, there is a chance that
106
- // the independent Min, Max, Sum, and Count are not consistent with each
107
- // other.
113
+ // the empty set.
108
114
func (c * Aggregator ) Checkpoint (ctx context.Context , desc * export.Descriptor ) {
109
- // N.B. There is no atomic operation that can update all three
110
- // values at once without a memory allocation.
111
- //
112
- // This aggregator is intended to trade this correctness for
113
- // speed.
114
- //
115
- // Therefore, atomically swap fields independently, knowing
116
- // that individually the three parts of this aggregation could
117
- // be spread across multiple collections in rare cases.
118
-
119
- c .checkpoint .count .SetUint64 (c .current .count .SwapUint64Atomic (0 ))
120
- c .checkpoint .sum = c .current .sum .SwapNumberAtomic (core .Number (0 ))
121
- c .checkpoint .max = c .current .max .SwapNumberAtomic (c .kind .Minimum ())
122
- c .checkpoint .min = c .current .min .SwapNumberAtomic (c .kind .Maximum ())
115
+ c .lock .SwapActiveState (c .resetCheckpoint )
116
+ }
117
+
118
+ // checkpoint returns the "cold" state, i.e. state collected prior to the
119
+ // most recent Checkpoint() call
120
+ func (c * Aggregator ) checkpoint () * state {
121
+ return & c .states [c .lock .ColdIdx ()]
122
+ }
123
+
124
+ func (c * Aggregator ) resetCheckpoint () {
125
+ checkpoint := c .checkpoint ()
126
+
127
+ checkpoint .count .SetUint64 (0 )
128
+ checkpoint .sum .SetNumber (c .kind .Zero ())
129
+ checkpoint .min .SetNumber (c .kind .Maximum ())
130
+ checkpoint .max .SetNumber (c .kind .Minimum ())
123
131
}
124
132
125
133
// Update adds the recorded measurement to the current data set.
126
134
func (c * Aggregator ) Update (_ context.Context , number core.Number , desc * export.Descriptor ) error {
127
135
kind := desc .NumberKind ()
128
136
129
- c .current .count .AddUint64Atomic (1 )
130
- c .current .sum .AddNumberAtomic (kind , number )
137
+ cIdx := c .lock .Start ()
138
+ defer c .lock .End (cIdx )
139
+
140
+ current := & c .states [cIdx ]
141
+ current .count .AddUint64Atomic (1 )
142
+ current .sum .AddNumberAtomic (kind , number )
131
143
132
144
for {
133
- current := c . current .min .AsNumberAtomic ()
145
+ cmin := current .min .AsNumberAtomic ()
134
146
135
- if number .CompareNumber (kind , current ) >= 0 {
147
+ if number .CompareNumber (kind , cmin ) >= 0 {
136
148
break
137
149
}
138
- if c . current .min .CompareAndSwapNumber (current , number ) {
150
+ if current .min .CompareAndSwapNumber (cmin , number ) {
139
151
break
140
152
}
141
153
}
142
154
for {
143
- current := c . current .max .AsNumberAtomic ()
155
+ cmax := current .max .AsNumberAtomic ()
144
156
145
- if number .CompareNumber (kind , current ) <= 0 {
157
+ if number .CompareNumber (kind , cmax ) <= 0 {
146
158
break
147
159
}
148
- if c . current .max .CompareAndSwapNumber (current , number ) {
160
+ if current .max .CompareAndSwapNumber (cmax , number ) {
149
161
break
150
162
}
151
163
}
@@ -159,14 +171,22 @@ func (c *Aggregator) Merge(oa export.Aggregator, desc *export.Descriptor) error
159
171
return aggregator .NewInconsistentMergeError (c , oa )
160
172
}
161
173
162
- c .checkpoint .sum .AddNumber (desc .NumberKind (), o .checkpoint .sum )
163
- c .checkpoint .count .AddNumber (core .Uint64NumberKind , o .checkpoint .count )
174
+ // Lock() synchronizes Merge() and Checkpoint() to ensure all operations of
175
+ // Merge() are performed on the same state.
176
+ c .lock .Lock ()
177
+ defer c .lock .Unlock ()
178
+
179
+ current := c .checkpoint ()
180
+ ocheckpoint := o .checkpoint ()
181
+
182
+ current .count .AddNumber (core .Uint64NumberKind , ocheckpoint .count )
183
+ current .sum .AddNumber (desc .NumberKind (), ocheckpoint .sum )
164
184
165
- if c . checkpoint . min .CompareNumber (desc .NumberKind (), o . checkpoint .min ) > 0 {
166
- c . checkpoint . min .SetNumber (o . checkpoint .min )
185
+ if current . min .CompareNumber (desc .NumberKind (), ocheckpoint .min ) > 0 {
186
+ current . min .SetNumber (ocheckpoint .min )
167
187
}
168
- if c . checkpoint . max .CompareNumber (desc .NumberKind (), o . checkpoint .max ) < 0 {
169
- c . checkpoint . max .SetNumber (o . checkpoint .max )
188
+ if current . max .CompareNumber (desc .NumberKind (), ocheckpoint .max ) < 0 {
189
+ current . max .SetNumber (ocheckpoint .max )
170
190
}
171
191
return nil
172
192
}
0 commit comments