1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-04-15 11:36:44 +02:00
Sam Xie 3429e15b9a
Revert Cleanup interaction of exemplar and aggregation (#5913)
Topic: #5249

This reverts commit 8041156518ee2f31ec9b36852fdc29f093d0f468 (PR: #5899)
due to the performance degradation found by Benchmarks CI
https://github.com/open-telemetry/opentelemetry-go/actions/runs/11447364022/job/31848519243

Here is the benchmark test on my machine:

```
goos: darwin
goarch: arm64
pkg: go.opentelemetry.io/otel/sdk/metric
                                       │   old.txt   │                new.txt                 │
                                       │   sec/op    │    sec/op     vs base                  │
Instrument/instrumentImpl/aggregate-10   3.378µ ± 3%   49.366µ ± 1%  +1361.40% (p=0.000 n=10)
Instrument/observable/observe-10         2.288µ ± 2%   37.791µ ± 1%  +1551.73% (p=0.000 n=10)
geomean                                  2.780µ         43.19µ       +1453.65%

                                       │   old.txt    │                 new.txt                 │
                                       │     B/op     │     B/op       vs base                  │
Instrument/instrumentImpl/aggregate-10   1.245Ki ± 1%   22.363Ki ± 0%  +1696.08% (p=0.000 n=10)
Instrument/observable/observe-10           823.0 ± 1%    17432.5 ± 0%  +2018.17% (p=0.000 n=10)
geomean                                  1.000Ki         19.51Ki       +1850.48%

                                       │  old.txt   │                new.txt                │
                                       │ allocs/op  │  allocs/op   vs base                  │
Instrument/instrumentImpl/aggregate-10   1.000 ± 0%   21.000 ± 0%  +2000.00% (p=0.000 n=10)
Instrument/observable/observe-10         1.000 ± 0%   16.000 ± 0%  +1500.00% (p=0.000 n=10)
```
2024-10-23 10:48:07 -07:00

195 lines
4.9 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
import (
"context"
"strconv"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)
var (
keyUser = "user"
userAlice = attribute.String(keyUser, "Alice")
userBob = attribute.String(keyUser, "Bob")
userCarol = attribute.String(keyUser, "Carol")
userDave = attribute.String(keyUser, "Dave")
adminTrue = attribute.Bool("admin", true)
adminFalse = attribute.Bool("admin", false)
alice = attribute.NewSet(userAlice, adminTrue)
bob = attribute.NewSet(userBob, adminFalse)
carol = attribute.NewSet(userCarol, adminFalse)
dave = attribute.NewSet(userDave, adminFalse)
// Filtered.
attrFltr = func(kv attribute.KeyValue) bool {
return kv.Key == attribute.Key(keyUser)
}
fltrAlice = attribute.NewSet(userAlice)
fltrBob = attribute.NewSet(userBob)
// Sat Jan 01 2000 00:00:00 GMT+0000.
y2k = time.Unix(946684800, 0)
)
// y2kPlus returns the timestamp at n seconds past Sat Jan 01 2000 00:00:00 GMT+0000.
func y2kPlus(n int64) time.Time {
d := time.Duration(n) * time.Second
return y2k.Add(d)
}
// clock is a test clock. It provides a predictable value for now() that can be
// reset.
type clock struct {
ticks atomic.Int64
}
// Now returns the mocked time starting at y2kPlus(0). Each call to Now will
// increment the returned value by one second.
func (c *clock) Now() time.Time {
old := c.ticks.Add(1) - 1
return y2kPlus(old)
}
// Reset resets the clock c to tick from y2kPlus(0).
func (c *clock) Reset() { c.ticks.Store(0) }
// Register registers clock c's Now method as the now var. It returns an
// unregister func that should be called to restore the original now value.
func (c *clock) Register() (unregister func()) {
orig := now
now = c.Now
return func() { now = orig }
}
func dropExemplars[N int64 | float64](attr attribute.Set) FilteredExemplarReservoir[N] {
return dropReservoir[N](attr)
}
func TestBuilderFilter(t *testing.T) {
t.Run("Int64", testBuilderFilter[int64]())
t.Run("Float64", testBuilderFilter[float64]())
}
func testBuilderFilter[N int64 | float64]() func(t *testing.T) {
return func(t *testing.T) {
t.Helper()
value, attr := N(1), alice
run := func(b Builder[N], wantF attribute.Set, wantD []attribute.KeyValue) func(*testing.T) {
return func(t *testing.T) {
t.Helper()
meas := b.filter(func(_ context.Context, v N, f attribute.Set, d []attribute.KeyValue) {
assert.Equal(t, value, v, "measured incorrect value")
assert.Equal(t, wantF, f, "measured incorrect filtered attributes")
assert.ElementsMatch(t, wantD, d, "measured incorrect dropped attributes")
})
meas(context.Background(), value, attr)
}
}
t.Run("NoFilter", run(Builder[N]{}, attr, nil))
t.Run("Filter", run(Builder[N]{Filter: attrFltr}, fltrAlice, []attribute.KeyValue{adminTrue}))
}
}
type arg[N int64 | float64] struct {
ctx context.Context
value N
attr attribute.Set
}
type output struct {
n int
agg metricdata.Aggregation
}
type teststep[N int64 | float64] struct {
input []arg[N]
expect output
}
func test[N int64 | float64](meas Measure[N], comp ComputeAggregation, steps []teststep[N]) func(*testing.T) {
return func(t *testing.T) {
t.Helper()
got := new(metricdata.Aggregation)
for i, step := range steps {
for _, args := range step.input {
meas(args.ctx, args.value, args.attr)
}
t.Logf("step: %d", i)
assert.Equal(t, step.expect.n, comp(got), "incorrect data size")
metricdatatest.AssertAggregationsEqual(t, step.expect.agg, *got)
}
}
}
func benchmarkAggregate[N int64 | float64](factory func() (Measure[N], ComputeAggregation)) func(*testing.B) {
counts := []int{1, 10, 100}
return func(b *testing.B) {
for _, n := range counts {
b.Run(strconv.Itoa(n), func(b *testing.B) {
benchmarkAggregateN(b, factory, n)
})
}
}
}
var bmarkRes metricdata.Aggregation
func benchmarkAggregateN[N int64 | float64](b *testing.B, factory func() (Measure[N], ComputeAggregation), count int) {
ctx := context.Background()
attrs := make([]attribute.Set, count)
for i := range attrs {
attrs[i] = attribute.NewSet(attribute.Int("value", i))
}
b.Run("Measure", func(b *testing.B) {
got := &bmarkRes
meas, comp := factory()
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, attr := range attrs {
meas(ctx, 1, attr)
}
}
comp(got)
})
b.Run("ComputeAggregation", func(b *testing.B) {
comps := make([]ComputeAggregation, b.N)
for n := range comps {
meas, comp := factory()
for _, attr := range attrs {
meas(ctx, 1, attr)
}
comps[n] = comp
}
got := &bmarkRes
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
comps[n](got)
}
})
}