1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-25 22:41:46 +02:00

Combine precomputed values of filtered attribute sets (#3549)

* Combine spatially aggregated precomputed vals

Fix #3439

When an attribute filter drops a distinguishing attribute during the
aggregation of a precomputed sum add that value to existing, instead of
just setting the value as an override (current behavior).

* Ignore false positive lint error and test method

* Add fix to changelog

* Handle edge case of exact set after filter

* Fix filter and measure algo for precomp

* Add tests for precomp sums

* Unify precomputedMap

* Adds example from supplimental guide

* Fixes for lint

* Update sdk/metric/meter_example_test.go

* Fix async example test

* Reduce duplicate code in TestAsynchronousExample

* Clarify naming and documentation

* Fix spelling errors

* Add a noop filter to default view

Co-authored-by: Chester Cheung <cheung.zhy.csu@gmail.com>
Co-authored-by: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com>
This commit is contained in:
Tyler Yahn
2023-01-20 09:54:42 -08:00
committed by GitHub
parent 88f6000318
commit a1ce7e5f0d
7 changed files with 752 additions and 92 deletions

View File

@@ -896,6 +896,11 @@ func TestRegisterCallbackDropAggregations(t *testing.T) {
}
func TestAttributeFilter(t *testing.T) {
t.Run("Delta", testAttributeFilter(metricdata.DeltaTemporality))
t.Run("Cumulative", testAttributeFilter(metricdata.CumulativeTemporality))
}
func testAttributeFilter(temporality metricdata.Temporality) func(*testing.T) {
one := 1.0
two := 2.0
testcases := []struct {
@@ -912,7 +917,8 @@ func TestAttributeFilter(t *testing.T) {
}
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"))
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -923,10 +929,10 @@ func TestAttributeFilter(t *testing.T) {
DataPoints: []metricdata.DataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
Value: 2.0, // TODO (#3439): This should be 3.0.
Value: 4.0,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: true,
},
},
@@ -940,7 +946,8 @@ func TestAttributeFilter(t *testing.T) {
}
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
o.ObserveFloat64(ctr, 2.0, attribute.String("foo", "bar"))
o.ObserveFloat64(ctr, 1.0, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -951,10 +958,10 @@ func TestAttributeFilter(t *testing.T) {
DataPoints: []metricdata.DataPoint[float64]{
{
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
Value: 2.0, // TODO (#3439): This should be 3.0.
Value: 4.0,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: false,
},
},
@@ -994,7 +1001,8 @@ func TestAttributeFilter(t *testing.T) {
}
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"))
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -1005,10 +1013,10 @@ func TestAttributeFilter(t *testing.T) {
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
Value: 20, // TODO (#3439): This should be 30.
Value: 40,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: true,
},
},
@@ -1022,7 +1030,8 @@ func TestAttributeFilter(t *testing.T) {
}
_, err = mtr.RegisterCallback(func(_ context.Context, o metric.Observer) error {
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 1))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"), attribute.Int("version", 2))
o.ObserveInt64(ctr, 20, attribute.String("foo", "bar"))
o.ObserveInt64(ctr, 10, attribute.String("foo", "bar"), attribute.Int("version", 2))
return nil
}, ctr)
return err
@@ -1033,10 +1042,10 @@ func TestAttributeFilter(t *testing.T) {
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("foo", "bar")),
Value: 20, // TODO (#3439): This should be 30.
Value: 40,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: false,
},
},
@@ -1088,7 +1097,7 @@ func TestAttributeFilter(t *testing.T) {
Value: 3.0,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: true,
},
},
@@ -1114,7 +1123,7 @@ func TestAttributeFilter(t *testing.T) {
Value: 3.0,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: false,
},
},
@@ -1145,7 +1154,7 @@ func TestAttributeFilter(t *testing.T) {
Sum: 3.0,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
},
},
},
@@ -1170,7 +1179,7 @@ func TestAttributeFilter(t *testing.T) {
Value: 30,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: true,
},
},
@@ -1196,7 +1205,7 @@ func TestAttributeFilter(t *testing.T) {
Value: 30,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
IsMonotonic: false,
},
},
@@ -1227,37 +1236,282 @@ func TestAttributeFilter(t *testing.T) {
Sum: 3.0,
},
},
Temporality: metricdata.CumulativeTemporality,
Temporality: temporality,
},
},
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
rdr := NewManualReader()
mtr := NewMeterProvider(
WithReader(rdr),
WithView(NewView(
Instrument{Name: "*"},
Stream{AttributeFilter: func(kv attribute.KeyValue) bool {
return kv.Key == attribute.Key("foo")
}},
)),
).Meter("TestAttributeFilter")
require.NoError(t, tt.register(t, mtr))
return func(t *testing.T) {
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
rdr := NewManualReader(WithTemporalitySelector(func(InstrumentKind) metricdata.Temporality {
return temporality
}))
mtr := NewMeterProvider(
WithReader(rdr),
WithView(NewView(
Instrument{Name: "*"},
Stream{AttributeFilter: func(kv attribute.KeyValue) bool {
return kv.Key == attribute.Key("foo")
}},
)),
).Meter("TestAttributeFilter")
require.NoError(t, tt.register(t, mtr))
m, err := rdr.Collect(context.Background())
assert.NoError(t, err)
m, err := rdr.Collect(context.Background())
assert.NoError(t, err)
require.Len(t, m.ScopeMetrics, 1)
require.Len(t, m.ScopeMetrics[0].Metrics, 1)
require.Len(t, m.ScopeMetrics, 1)
require.Len(t, m.ScopeMetrics[0].Metrics, 1)
metricdatatest.AssertEqual(t, tt.wantMetric, m.ScopeMetrics[0].Metrics[0], metricdatatest.IgnoreTimestamp())
})
metricdatatest.AssertEqual(t, tt.wantMetric, m.ScopeMetrics[0].Metrics[0], metricdatatest.IgnoreTimestamp())
})
}
}
}
func TestAsynchronousExample(t *testing.T) {
// This example can be found:
// https://github.com/open-telemetry/opentelemetry-specification/blob/8b91585e6175dd52b51e1d60bea105041225e35d/specification/metrics/supplementary-guidelines.md#asynchronous-example
var (
threadID1 = attribute.Int("tid", 1)
threadID2 = attribute.Int("tid", 2)
threadID3 = attribute.Int("tid", 3)
processID1001 = attribute.String("pid", "1001")
thread1 = attribute.NewSet(processID1001, threadID1)
thread2 = attribute.NewSet(processID1001, threadID2)
thread3 = attribute.NewSet(processID1001, threadID3)
process1001 = attribute.NewSet(processID1001)
)
setup := func(t *testing.T, temp metricdata.Temporality) (map[attribute.Set]int64, func(*testing.T), *metricdata.ScopeMetrics, *int64, *int64, *int64) {
t.Helper()
const (
instName = "pageFaults"
filteredStream = "filteredPageFaults"
scopeName = "AsynchronousExample"
)
selector := func(InstrumentKind) metricdata.Temporality { return temp }
reader := NewManualReader(WithTemporalitySelector(selector))
noopFilter := func(kv attribute.KeyValue) bool { return true }
noFiltered := NewView(Instrument{Name: instName}, Stream{Name: instName, AttributeFilter: noopFilter})
filter := func(kv attribute.KeyValue) bool { return kv.Key != "tid" }
filtered := NewView(Instrument{Name: instName}, Stream{Name: filteredStream, AttributeFilter: filter})
mp := NewMeterProvider(WithReader(reader), WithView(noFiltered, filtered))
meter := mp.Meter(scopeName)
observations := make(map[attribute.Set]int64)
_, err := meter.Int64ObservableCounter(instName, instrument.WithInt64Callback(
func(ctx context.Context, o instrument.Int64Observer) error {
for attrSet, val := range observations {
o.Observe(ctx, val, attrSet.ToSlice()...)
}
return nil
},
))
require.NoError(t, err)
want := &metricdata.ScopeMetrics{
Scope: instrumentation.Scope{Name: scopeName},
Metrics: []metricdata.Metrics{
{
Name: filteredStream,
Data: metricdata.Sum[int64]{
Temporality: temp,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{Attributes: process1001},
},
},
},
{
Name: instName,
Data: metricdata.Sum[int64]{
Temporality: temp,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{Attributes: thread1},
{Attributes: thread2},
},
},
},
},
}
wantFiltered := &want.Metrics[0].Data.(metricdata.Sum[int64]).DataPoints[0].Value
wantThread1 := &want.Metrics[1].Data.(metricdata.Sum[int64]).DataPoints[0].Value
wantThread2 := &want.Metrics[1].Data.(metricdata.Sum[int64]).DataPoints[1].Value
collect := func(t *testing.T) {
t.Helper()
got, err := reader.Collect(context.Background())
require.NoError(t, err)
require.Len(t, got.ScopeMetrics, 1)
metricdatatest.AssertEqual(t, *want, got.ScopeMetrics[0], metricdatatest.IgnoreTimestamp())
}
return observations, collect, want, wantFiltered, wantThread1, wantThread2
}
t.Run("Cumulative", func(t *testing.T) {
temporality := metricdata.CumulativeTemporality
observations, verify, want, wantFiltered, wantThread1, wantThread2 := setup(t, temporality)
// During the time range (T0, T1]:
// pid = 1001, tid = 1, #PF = 50
// pid = 1001, tid = 2, #PF = 30
observations[thread1] = 50
observations[thread2] = 30
*wantFiltered = 80
*wantThread1 = 50
*wantThread2 = 30
verify(t)
// During the time range (T1, T2]:
// pid = 1001, tid = 1, #PF = 53
// pid = 1001, tid = 2, #PF = 38
observations[thread1] = 53
observations[thread2] = 38
*wantFiltered = 91
*wantThread1 = 53
*wantThread2 = 38
verify(t)
// During the time range (T2, T3]
// pid = 1001, tid = 1, #PF = 56
// pid = 1001, tid = 2, #PF = 42
observations[thread1] = 56
observations[thread2] = 42
*wantFiltered = 98
*wantThread1 = 56
*wantThread2 = 42
verify(t)
// During the time range (T3, T4]:
// pid = 1001, tid = 1, #PF = 60
// pid = 1001, tid = 2, #PF = 47
observations[thread1] = 60
observations[thread2] = 47
*wantFiltered = 107
*wantThread1 = 60
*wantThread2 = 47
verify(t)
// During the time range (T4, T5]:
// thread 1 died, thread 3 started
// pid = 1001, tid = 2, #PF = 53
// pid = 1001, tid = 3, #PF = 5
delete(observations, thread1)
observations[thread2] = 53
observations[thread3] = 5
*wantFiltered = 58
want.Metrics[1].Data = metricdata.Sum[int64]{
Temporality: temporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
// Thread 1 remains at last measured value.
{Attributes: thread1, Value: 60},
{Attributes: thread2, Value: 53},
{Attributes: thread3, Value: 5},
},
}
verify(t)
})
t.Run("Delta", func(t *testing.T) {
temporality := metricdata.DeltaTemporality
observations, verify, want, wantFiltered, wantThread1, wantThread2 := setup(t, temporality)
// During the time range (T0, T1]:
// pid = 1001, tid = 1, #PF = 50
// pid = 1001, tid = 2, #PF = 30
observations[thread1] = 50
observations[thread2] = 30
*wantFiltered = 80
*wantThread1 = 50
*wantThread2 = 30
verify(t)
// During the time range (T1, T2]:
// pid = 1001, tid = 1, #PF = 53
// pid = 1001, tid = 2, #PF = 38
observations[thread1] = 53
observations[thread2] = 38
*wantFiltered = 11
*wantThread1 = 3
*wantThread2 = 8
verify(t)
// During the time range (T2, T3]
// pid = 1001, tid = 1, #PF = 56
// pid = 1001, tid = 2, #PF = 42
observations[thread1] = 56
observations[thread2] = 42
*wantFiltered = 7
*wantThread1 = 3
*wantThread2 = 4
verify(t)
// During the time range (T3, T4]:
// pid = 1001, tid = 1, #PF = 60
// pid = 1001, tid = 2, #PF = 47
observations[thread1] = 60
observations[thread2] = 47
*wantFiltered = 9
*wantThread1 = 4
*wantThread2 = 5
verify(t)
// During the time range (T4, T5]:
// thread 1 died, thread 3 started
// pid = 1001, tid = 2, #PF = 53
// pid = 1001, tid = 3, #PF = 5
delete(observations, thread1)
observations[thread2] = 53
observations[thread3] = 5
*wantFiltered = -49
want.Metrics[1].Data = metricdata.Sum[int64]{
Temporality: temporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
// Thread 1 remains at last measured value.
{Attributes: thread1, Value: 0},
{Attributes: thread2, Value: 6},
{Attributes: thread3, Value: 5},
},
}
verify(t)
})
}
var (
aiCounter instrument.Int64ObservableCounter
aiUpDownCounter instrument.Int64ObservableUpDownCounter