Skip to content

Commit 2f662db

Browse files
MrAliasXSAM
andauthored
Refactor exemplars to not use generic argument (open-telemetry#5285)
* Refactor exemplars to not use generic argument * Update internal/aggregate * Update metric SDK * Test exemplar value type * Add TestCollectExemplars * Fix lint --------- Co-authored-by: Sam Xie <[email protected]>
1 parent f8b9fe3 commit 2f662db

24 files changed

+334
-126
lines changed

sdk/metric/exemplar.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@ import (
1919
// Note: This will only return non-nil values when the experimental exemplar
2020
// feature is enabled and the OTEL_METRICS_EXEMPLAR_FILTER environment variable
2121
// is not set to always_off.
22-
func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.Reservoir[N] {
22+
func reservoirFunc(agg Aggregation) func() exemplar.Reservoir {
2323
if !x.Exemplars.Enabled() {
2424
return nil
2525
}
2626

2727
// https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/metrics/sdk.md#exemplar-defaults
28-
resF := func() func() exemplar.Reservoir[N] {
28+
resF := func() func() exemplar.Reservoir {
2929
// Explicit bucket histogram aggregation with more than 1 bucket will
3030
// use AlignedHistogramBucketExemplarReservoir.
3131
a, ok := agg.(AggregationExplicitBucketHistogram)
3232
if ok && len(a.Boundaries) > 0 {
3333
cp := slices.Clone(a.Boundaries)
34-
return func() exemplar.Reservoir[N] {
34+
return func() exemplar.Reservoir {
3535
bounds := cp
36-
return exemplar.Histogram[N](bounds)
36+
return exemplar.Histogram(bounds)
3737
}
3838
}
3939

@@ -61,8 +61,8 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.Reservoir
6161
}
6262
}
6363

64-
return func() exemplar.Reservoir[N] {
65-
return exemplar.FixedSize[N](n)
64+
return func() exemplar.Reservoir {
65+
return exemplar.FixedSize(n)
6666
}
6767
}
6868

@@ -73,12 +73,12 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.Reservoir
7373
case "always_on":
7474
return resF()
7575
case "always_off":
76-
return exemplar.Drop[N]
76+
return exemplar.Drop
7777
case "trace_based":
7878
fallthrough
7979
default:
8080
newR := resF()
81-
return func() exemplar.Reservoir[N] {
81+
return func() exemplar.Reservoir {
8282
return exemplar.SampledFilter(newR())
8383
}
8484
}

sdk/metric/internal/aggregate/aggregate.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type Builder[N int64 | float64] struct {
3939
//
4040
// If this is not provided a default factory function that returns an
4141
// exemplar.Drop reservoir will be used.
42-
ReservoirFunc func() exemplar.Reservoir[N]
42+
ReservoirFunc func() exemplar.Reservoir
4343
// AggregationLimit is the cardinality limit of measurement attributes. Any
4444
// measurement for new attributes once the limit has been reached will be
4545
// aggregated into a single aggregate for the "otel.metric.overflow"
@@ -50,12 +50,12 @@ type Builder[N int64 | float64] struct {
5050
AggregationLimit int
5151
}
5252

53-
func (b Builder[N]) resFunc() func() exemplar.Reservoir[N] {
53+
func (b Builder[N]) resFunc() func() exemplar.Reservoir {
5454
if b.ReservoirFunc != nil {
5555
return b.ReservoirFunc
5656
}
5757

58-
return exemplar.Drop[N]
58+
return exemplar.Drop
5959
}
6060

6161
type fltrMeasure[N int64 | float64] func(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue)

sdk/metric/internal/aggregate/aggregate_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ var (
4949
}
5050
)
5151

52-
func dropExemplars[N int64 | float64]() exemplar.Reservoir[N] {
53-
return exemplar.Drop[N]()
52+
func dropExemplars[N int64 | float64]() exemplar.Reservoir {
53+
return exemplar.Drop()
5454
}
5555

5656
func TestBuilderFilter(t *testing.T) {
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
5+
6+
import (
7+
"sync"
8+
9+
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
10+
"go.opentelemetry.io/otel/sdk/metric/metricdata"
11+
)
12+
13+
var exemplarPool = sync.Pool{
14+
New: func() any { return new([]exemplar.Exemplar) },
15+
}
16+
17+
func collectExemplars[N int64 | float64](out *[]metricdata.Exemplar[N], f func(*[]exemplar.Exemplar)) {
18+
dest := exemplarPool.Get().(*[]exemplar.Exemplar)
19+
defer func() {
20+
*dest = (*dest)[:0]
21+
exemplarPool.Put(dest)
22+
}()
23+
24+
*dest = reset(*dest, len(*out), cap(*out))
25+
26+
f(dest)
27+
28+
*out = reset(*out, len(*dest), cap(*dest))
29+
for i, e := range *dest {
30+
(*out)[i].FilteredAttributes = e.FilteredAttributes
31+
(*out)[i].Time = e.Time
32+
(*out)[i].SpanID = e.SpanID
33+
(*out)[i].TraceID = e.TraceID
34+
35+
switch e.Value.Type() {
36+
case exemplar.Int64ValueType:
37+
(*out)[i].Value = N(e.Value.Int64())
38+
case exemplar.Float64ValueType:
39+
(*out)[i].Value = N(e.Value.Float64())
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package aggregate
5+
6+
import (
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
12+
"go.opentelemetry.io/otel/attribute"
13+
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
14+
"go.opentelemetry.io/otel/sdk/metric/metricdata"
15+
)
16+
17+
func TestCollectExemplars(t *testing.T) {
18+
t.Run("Int64", testCollectExemplars[int64]())
19+
t.Run("Float64", testCollectExemplars[float64]())
20+
}
21+
22+
func testCollectExemplars[N int64 | float64]() func(t *testing.T) {
23+
return func(t *testing.T) {
24+
now := time.Now()
25+
alice := attribute.String("user", "Alice")
26+
value := N(1)
27+
spanID := [8]byte{0x1}
28+
traceID := [16]byte{0x1}
29+
30+
out := new([]metricdata.Exemplar[N])
31+
collectExemplars(out, func(in *[]exemplar.Exemplar) {
32+
*in = reset(*in, 1, 1)
33+
(*in)[0] = exemplar.Exemplar{
34+
FilteredAttributes: []attribute.KeyValue{alice},
35+
Time: now,
36+
Value: exemplar.NewValue(value),
37+
SpanID: spanID[:],
38+
TraceID: traceID[:],
39+
}
40+
})
41+
42+
assert.Equal(t, []metricdata.Exemplar[N]{{
43+
FilteredAttributes: []attribute.KeyValue{alice},
44+
Time: now,
45+
Value: value,
46+
SpanID: spanID[:],
47+
TraceID: traceID[:],
48+
}}, *out)
49+
}
50+
}

sdk/metric/internal/aggregate/exponential_histogram.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const (
3131
// expoHistogramDataPoint is a single data point in an exponential histogram.
3232
type expoHistogramDataPoint[N int64 | float64] struct {
3333
attrs attribute.Set
34-
res exemplar.Reservoir[N]
34+
res exemplar.Reservoir
3535

3636
count uint64
3737
min N
@@ -282,7 +282,7 @@ func (b *expoBuckets) downscale(delta int) {
282282
// newExponentialHistogram returns an Aggregator that summarizes a set of
283283
// measurements as an exponential histogram. Each histogram is scoped by attributes
284284
// and the aggregation cycle the measurements were made in.
285-
func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir[N]) *expoHistogram[N] {
285+
func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir) *expoHistogram[N] {
286286
return &expoHistogram[N]{
287287
noSum: noSum,
288288
noMinMax: noMinMax,
@@ -305,7 +305,7 @@ type expoHistogram[N int64 | float64] struct {
305305
maxSize int
306306
maxScale int
307307

308-
newRes func() exemplar.Reservoir[N]
308+
newRes func() exemplar.Reservoir
309309
limit limiter[*expoHistogramDataPoint[N]]
310310
values map[attribute.Distinct]*expoHistogramDataPoint[N]
311311
valuesMu sync.Mutex
@@ -333,7 +333,7 @@ func (e *expoHistogram[N]) measure(ctx context.Context, value N, fltrAttr attrib
333333
e.values[attr.Equivalent()] = v
334334
}
335335
v.record(value)
336-
v.res.Offer(ctx, t, value, droppedAttr)
336+
v.res.Offer(ctx, t, exemplar.NewValue(value), droppedAttr)
337337
}
338338

339339
func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
@@ -376,7 +376,7 @@ func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
376376
hDPts[i].Max = metricdata.NewExtrema(val.max)
377377
}
378378

379-
val.res.Collect(&hDPts[i].Exemplars)
379+
collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
380380

381381
i++
382382
}
@@ -429,7 +429,7 @@ func (e *expoHistogram[N]) cumulative(dest *metricdata.Aggregation) int {
429429
hDPts[i].Max = metricdata.NewExtrema(val.max)
430430
}
431431

432-
val.res.Collect(&hDPts[i].Exemplars)
432+
collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
433433

434434
i++
435435
// TODO (#3006): This will use an unbounded amount of memory if there

sdk/metric/internal/aggregate/histogram.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

1818
type buckets[N int64 | float64] struct {
1919
attrs attribute.Set
20-
res exemplar.Reservoir[N]
20+
res exemplar.Reservoir
2121

2222
counts []uint64
2323
count uint64
@@ -48,13 +48,13 @@ type histValues[N int64 | float64] struct {
4848
noSum bool
4949
bounds []float64
5050

51-
newRes func() exemplar.Reservoir[N]
51+
newRes func() exemplar.Reservoir
5252
limit limiter[*buckets[N]]
5353
values map[attribute.Distinct]*buckets[N]
5454
valuesMu sync.Mutex
5555
}
5656

57-
func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() exemplar.Reservoir[N]) *histValues[N] {
57+
func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() exemplar.Reservoir) *histValues[N] {
5858
// The responsibility of keeping all buckets correctly associated with the
5959
// passed boundaries is ultimately this type's responsibility. Make a copy
6060
// here so we can always guarantee this. Or, in the case of failure, have
@@ -106,12 +106,12 @@ func (s *histValues[N]) measure(ctx context.Context, value N, fltrAttr attribute
106106
if !s.noSum {
107107
b.sum(value)
108108
}
109-
b.res.Offer(ctx, t, value, droppedAttr)
109+
b.res.Offer(ctx, t, exemplar.NewValue(value), droppedAttr)
110110
}
111111

112112
// newHistogram returns an Aggregator that summarizes a set of measurements as
113113
// an histogram.
114-
func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir[N]) *histogram[N] {
114+
func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir) *histogram[N] {
115115
return &histogram[N]{
116116
histValues: newHistValues[N](boundaries, noSum, limit, r),
117117
noMinMax: noMinMax,
@@ -163,7 +163,7 @@ func (s *histogram[N]) delta(dest *metricdata.Aggregation) int {
163163
hDPts[i].Max = metricdata.NewExtrema(val.max)
164164
}
165165

166-
val.res.Collect(&hDPts[i].Exemplars)
166+
collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
167167

168168
i++
169169
}
@@ -219,7 +219,7 @@ func (s *histogram[N]) cumulative(dest *metricdata.Aggregation) int {
219219
hDPts[i].Max = metricdata.NewExtrema(val.max)
220220
}
221221

222-
val.res.Collect(&hDPts[i].Exemplars)
222+
collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
223223

224224
i++
225225
// TODO (#3006): This will use an unbounded amount of memory if there

sdk/metric/internal/aggregate/lastvalue.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ type datapoint[N int64 | float64] struct {
1818
attrs attribute.Set
1919
timestamp time.Time
2020
value N
21-
res exemplar.Reservoir[N]
21+
res exemplar.Reservoir
2222
}
2323

24-
func newLastValue[N int64 | float64](limit int, r func() exemplar.Reservoir[N]) *lastValue[N] {
24+
func newLastValue[N int64 | float64](limit int, r func() exemplar.Reservoir) *lastValue[N] {
2525
return &lastValue[N]{
2626
newRes: r,
2727
limit: newLimiter[datapoint[N]](limit),
@@ -33,7 +33,7 @@ func newLastValue[N int64 | float64](limit int, r func() exemplar.Reservoir[N])
3333
type lastValue[N int64 | float64] struct {
3434
sync.Mutex
3535

36-
newRes func() exemplar.Reservoir[N]
36+
newRes func() exemplar.Reservoir
3737
limit limiter[datapoint[N]]
3838
values map[attribute.Distinct]datapoint[N]
3939
}
@@ -53,7 +53,7 @@ func (s *lastValue[N]) measure(ctx context.Context, value N, fltrAttr attribute.
5353
d.attrs = attr
5454
d.timestamp = t
5555
d.value = value
56-
d.res.Offer(ctx, t, value, droppedAttr)
56+
d.res.Offer(ctx, t, exemplar.NewValue(value), droppedAttr)
5757

5858
s.values[attr.Equivalent()] = d
5959
}
@@ -72,7 +72,7 @@ func (s *lastValue[N]) computeAggregation(dest *[]metricdata.DataPoint[N]) {
7272
// ignored.
7373
(*dest)[i].Time = v.timestamp
7474
(*dest)[i].Value = v.value
75-
v.res.Collect(&(*dest)[i].Exemplars)
75+
collectExemplars(&(*dest)[i].Exemplars, v.res.Collect)
7676
i++
7777
}
7878
// Do not report stale values.

0 commit comments

Comments
 (0)