You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-29 23:07:45 +02:00
Handle duplicate Aggregators and log instrument conflicts (#3251)
* Add the cache type * Add cache unit tests * Test cache concurrency * Add the instrumentCache * Use the instrumentCache to deduplicate creation * Drop unique check from addAggregator * Fix aggregatorCache* docs * Update cachedAggregator and aggregator method docs * Remove unnecessary type constraint * Remove unused errAlreadyRegistered * Rename to not shadow imports * Add changes to changelog * Fix changelog English * Store resolvers in the meter instead of caches * Test all Aggregator[N] impls are comparable * Fix lint * Add documentation that Aggregators need to be comparable * Update sdk/metric/internal/aggregator.go Co-authored-by: Anthony Mirabella <a9@aneurysm9.com> * Update sdk/metric/instrument.go Co-authored-by: Anthony Mirabella <a9@aneurysm9.com> * Update sdk/metric/instrument.go Co-authored-by: Anthony Mirabella <a9@aneurysm9.com> * Update sdk/metric/internal/aggregator_test.go Co-authored-by: Anthony Mirabella <a9@aneurysm9.com> * Fix pipeline_test.go use of newInstrumentCache Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
This commit is contained in:
@@ -15,11 +15,15 @@
|
||||
package metric // import "go.opentelemetry.io/otel/sdk/metric"
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/logr/testr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric/unit"
|
||||
"go.opentelemetry.io/otel/sdk/metric/aggregation"
|
||||
@@ -211,7 +215,8 @@ func testCreateAggregators[N int64 | float64](t *testing.T) {
|
||||
}
|
||||
for _, tt := range testcases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
i := newInserter[N](newPipeline(nil, tt.reader, tt.views))
|
||||
c := newInstrumentCache[N](nil, nil)
|
||||
i := newInserter(newPipeline(nil, tt.reader, tt.views), c)
|
||||
got, err := i.Instrument(tt.inst, unit.Dimensionless)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
require.Len(t, got, tt.wantLen)
|
||||
@@ -223,7 +228,8 @@ func testCreateAggregators[N int64 | float64](t *testing.T) {
|
||||
}
|
||||
|
||||
func testInvalidInstrumentShouldPanic[N int64 | float64]() {
|
||||
i := newInserter[N](newPipeline(nil, NewManualReader(), []view.View{{}}))
|
||||
c := newInstrumentCache[N](nil, nil)
|
||||
i := newInserter(newPipeline(nil, NewManualReader(), []view.View{{}}), c)
|
||||
inst := view.Instrument{
|
||||
Name: "foo",
|
||||
Kind: view.InstrumentKind(255),
|
||||
@@ -334,7 +340,8 @@ func TestPipelineRegistryCreateAggregators(t *testing.T) {
|
||||
func testPipelineRegistryResolveIntAggregators(t *testing.T, p pipelines, wantCount int) {
|
||||
inst := view.Instrument{Name: "foo", Kind: view.SyncCounter}
|
||||
|
||||
r := newResolver[int64](p)
|
||||
c := newInstrumentCache[int64](nil, nil)
|
||||
r := newResolver(p, c)
|
||||
aggs, err := r.Aggregators(inst, unit.Dimensionless)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -344,7 +351,8 @@ func testPipelineRegistryResolveIntAggregators(t *testing.T, p pipelines, wantCo
|
||||
func testPipelineRegistryResolveFloatAggregators(t *testing.T, p pipelines, wantCount int) {
|
||||
inst := view.Instrument{Name: "foo", Kind: view.SyncCounter}
|
||||
|
||||
r := newResolver[float64](p)
|
||||
c := newInstrumentCache[float64](nil, nil)
|
||||
r := newResolver(p, c)
|
||||
aggs, err := r.Aggregators(inst, unit.Dimensionless)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -375,20 +383,40 @@ func TestPipelineRegistryCreateAggregatorsIncompatibleInstrument(t *testing.T) {
|
||||
p := newPipelines(resource.Empty(), views)
|
||||
inst := view.Instrument{Name: "foo", Kind: view.AsyncGauge}
|
||||
|
||||
ri := newResolver[int64](p)
|
||||
vc := cache[string, instrumentID]{}
|
||||
ri := newResolver(p, newInstrumentCache[int64](nil, &vc))
|
||||
intAggs, err := ri.Aggregators(inst, unit.Dimensionless)
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, intAggs, 0)
|
||||
|
||||
p = newPipelines(resource.Empty(), views)
|
||||
|
||||
rf := newResolver[float64](p)
|
||||
rf := newResolver(p, newInstrumentCache[float64](nil, &vc))
|
||||
floatAggs, err := rf.Aggregators(inst, unit.Dimensionless)
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, floatAggs, 0)
|
||||
}
|
||||
|
||||
func TestPipelineRegistryCreateAggregatorsDuplicateErrors(t *testing.T) {
|
||||
type logCounter struct {
|
||||
logr.LogSink
|
||||
|
||||
infoN uint32
|
||||
}
|
||||
|
||||
func (l *logCounter) Info(level int, msg string, keysAndValues ...interface{}) {
|
||||
atomic.AddUint32(&l.infoN, 1)
|
||||
l.LogSink.Info(level, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l *logCounter) InfoN() int {
|
||||
return int(atomic.SwapUint32(&l.infoN, 0))
|
||||
}
|
||||
|
||||
func TestResolveAggregatorsDuplicateErrors(t *testing.T) {
|
||||
tLog := testr.NewWithOptions(t, testr.Options{Verbosity: 6})
|
||||
l := &logCounter{LogSink: tLog.GetSink()}
|
||||
otel.SetLogger(logr.New(l))
|
||||
|
||||
renameView, _ := view.New(
|
||||
view.MatchInstrumentName("bar"),
|
||||
view.WithRename("foo"),
|
||||
@@ -405,29 +433,40 @@ func TestPipelineRegistryCreateAggregatorsDuplicateErrors(t *testing.T) {
|
||||
|
||||
p := newPipelines(resource.Empty(), views)
|
||||
|
||||
ri := newResolver[int64](p)
|
||||
vc := cache[string, instrumentID]{}
|
||||
ri := newResolver(p, newInstrumentCache[int64](nil, &vc))
|
||||
intAggs, err := ri.Aggregators(fooInst, unit.Dimensionless)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, l.InfoN(), "no info logging should happen")
|
||||
assert.Len(t, intAggs, 1)
|
||||
|
||||
// The Rename view should error, because it creates a foo instrument.
|
||||
// The Rename view should produce the same instrument without an error, the
|
||||
// default view should also cause a new aggregator to be returned.
|
||||
intAggs, err = ri.Aggregators(barInst, unit.Dimensionless)
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, l.InfoN(), "no info logging should happen")
|
||||
assert.Len(t, intAggs, 2)
|
||||
|
||||
// Creating a float foo instrument should error because there is an int foo instrument.
|
||||
rf := newResolver[float64](p)
|
||||
// Creating a float foo instrument should log a warning because there is an
|
||||
// int foo instrument.
|
||||
rf := newResolver(p, newInstrumentCache[float64](nil, &vc))
|
||||
floatAggs, err := rf.Aggregators(fooInst, unit.Dimensionless)
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, l.InfoN(), "instrument conflict not logged")
|
||||
assert.Len(t, floatAggs, 1)
|
||||
|
||||
fooInst = view.Instrument{Name: "foo-float", Kind: view.SyncCounter}
|
||||
|
||||
_, err = rf.Aggregators(fooInst, unit.Dimensionless)
|
||||
floatAggs, err = rf.Aggregators(fooInst, unit.Dimensionless)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, l.InfoN(), "no info logging should happen")
|
||||
assert.Len(t, floatAggs, 1)
|
||||
|
||||
floatAggs, err = rf.Aggregators(barInst, unit.Dimensionless)
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, err)
|
||||
// Both the rename and default view aggregators created above should now
|
||||
// conflict. Therefore, 2 warning messages should be logged.
|
||||
assert.Equal(t, 2, l.InfoN(), "instrument conflicts not logged")
|
||||
assert.Len(t, floatAggs, 2)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user