You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	Single-state Aggregator and test refactor (#812)
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
		| @@ -16,7 +16,6 @@ package internal_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/global" | ||||
| @@ -26,9 +25,7 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/api/trace" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	sdk "go.opentelemetry.io/otel/sdk/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| 	sdktrace "go.opentelemetry.io/otel/sdk/trace" | ||||
| ) | ||||
|  | ||||
| @@ -37,6 +34,7 @@ var Must = metric.Must | ||||
| // benchFixture is copied from sdk/metric/benchmark_test.go. | ||||
| // TODO refactor to share this code. | ||||
| type benchFixture struct { | ||||
| 	export.AggregationSelector | ||||
| 	accumulator *sdk.Accumulator | ||||
| 	meter       metric.Meter | ||||
| 	B           *testing.B | ||||
| @@ -47,7 +45,8 @@ var _ metric.Provider = &benchFixture{} | ||||
| func newFixture(b *testing.B) *benchFixture { | ||||
| 	b.ReportAllocs() | ||||
| 	bf := &benchFixture{ | ||||
| 		B: b, | ||||
| 		B:                   b, | ||||
| 		AggregationSelector: test.AggregationSelector(), | ||||
| 	} | ||||
|  | ||||
| 	bf.accumulator = sdk.NewAccumulator(bf) | ||||
| @@ -55,22 +54,6 @@ func newFixture(b *testing.B) *benchFixture { | ||||
| 	return bf | ||||
| } | ||||
|  | ||||
| func (*benchFixture) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| 	switch descriptor.MetricKind() { | ||||
| 	case metric.CounterKind: | ||||
| 		return sum.New() | ||||
| 	case metric.ValueRecorderKind: | ||||
| 		if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") { | ||||
| 			return minmaxsumcount.New(descriptor) | ||||
| 		} else if strings.HasSuffix(descriptor.Name(), "ddsketch") { | ||||
| 			return ddsketch.New(descriptor, ddsketch.NewDefaultConfig()) | ||||
| 		} else if strings.HasSuffix(descriptor.Name(), "array") { | ||||
| 			return ddsketch.New(descriptor, ddsketch.NewDefaultConfig()) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (*benchFixture) Process(export.Record) error { | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @@ -99,11 +100,13 @@ func TestStdoutTimestamp(t *testing.T) { | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Int64NumberKind) | ||||
| 	lvagg := lastvalue.New() | ||||
| 	aggtest.CheckedUpdate(t, lvagg, metric.NewInt64Number(321), &desc) | ||||
| 	lvagg.Checkpoint(&desc) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, lvagg) | ||||
| 	lvagg, ckpt := test.Unslice2(lastvalue.New(2)) | ||||
|  | ||||
| 	aggtest.CheckedUpdate(t, lvagg, metric.NewInt64Number(321), &desc) | ||||
| 	require.NoError(t, lvagg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, ckpt) | ||||
|  | ||||
| 	if err := exporter.Export(ctx, checkpointSet); err != nil { | ||||
| 		t.Fatal("Unexpected export error: ", err) | ||||
| @@ -144,11 +147,13 @@ func TestStdoutCounterFormat(t *testing.T) { | ||||
| 	checkpointSet := test.NewCheckpointSet(testResource) | ||||
|  | ||||
| 	desc := metric.NewDescriptor("test.name", metric.CounterKind, metric.Int64NumberKind) | ||||
| 	cagg := sum.New() | ||||
| 	aggtest.CheckedUpdate(fix.t, cagg, metric.NewInt64Number(123), &desc) | ||||
| 	cagg.Checkpoint(&desc) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, cagg, kv.String("A", "B"), kv.String("C", "D")) | ||||
| 	cagg, ckpt := test.Unslice2(sum.New(2)) | ||||
|  | ||||
| 	aggtest.CheckedUpdate(fix.t, cagg, metric.NewInt64Number(123), &desc) | ||||
| 	require.NoError(t, cagg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, ckpt, kv.String("A", "B"), kv.String("C", "D")) | ||||
|  | ||||
| 	fix.Export(checkpointSet) | ||||
|  | ||||
| @@ -161,11 +166,12 @@ func TestStdoutLastValueFormat(t *testing.T) { | ||||
| 	checkpointSet := test.NewCheckpointSet(testResource) | ||||
|  | ||||
| 	desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Float64NumberKind) | ||||
| 	lvagg := lastvalue.New() | ||||
| 	aggtest.CheckedUpdate(fix.t, lvagg, metric.NewFloat64Number(123.456), &desc) | ||||
| 	lvagg.Checkpoint(&desc) | ||||
| 	lvagg, ckpt := test.Unslice2(lastvalue.New(2)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, lvagg, kv.String("A", "B"), kv.String("C", "D")) | ||||
| 	aggtest.CheckedUpdate(fix.t, lvagg, metric.NewFloat64Number(123.456), &desc) | ||||
| 	require.NoError(t, lvagg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, ckpt, kv.String("A", "B"), kv.String("C", "D")) | ||||
|  | ||||
| 	fix.Export(checkpointSet) | ||||
|  | ||||
| @@ -178,12 +184,14 @@ func TestStdoutMinMaxSumCount(t *testing.T) { | ||||
| 	checkpointSet := test.NewCheckpointSet(testResource) | ||||
|  | ||||
| 	desc := metric.NewDescriptor("test.name", metric.ValueRecorderKind, metric.Float64NumberKind) | ||||
| 	magg := minmaxsumcount.New(&desc) | ||||
|  | ||||
| 	magg, ckpt := test.Unslice2(minmaxsumcount.New(2, &desc)) | ||||
|  | ||||
| 	aggtest.CheckedUpdate(fix.t, magg, metric.NewFloat64Number(123.456), &desc) | ||||
| 	aggtest.CheckedUpdate(fix.t, magg, metric.NewFloat64Number(876.543), &desc) | ||||
| 	magg.Checkpoint(&desc) | ||||
| 	require.NoError(t, magg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, magg, kv.String("A", "B"), kv.String("C", "D")) | ||||
| 	checkpointSet.Add(&desc, ckpt, kv.String("A", "B"), kv.String("C", "D")) | ||||
|  | ||||
| 	fix.Export(checkpointSet) | ||||
|  | ||||
| @@ -198,15 +206,15 @@ func TestStdoutValueRecorderFormat(t *testing.T) { | ||||
| 	checkpointSet := test.NewCheckpointSet(testResource) | ||||
|  | ||||
| 	desc := metric.NewDescriptor("test.name", metric.ValueRecorderKind, metric.Float64NumberKind) | ||||
| 	magg := array.New() | ||||
| 	aagg, ckpt := test.Unslice2(array.New(2)) | ||||
|  | ||||
| 	for i := 0; i < 1000; i++ { | ||||
| 		aggtest.CheckedUpdate(fix.t, magg, metric.NewFloat64Number(float64(i)+0.5), &desc) | ||||
| 		aggtest.CheckedUpdate(fix.t, aagg, metric.NewFloat64Number(float64(i)+0.5), &desc) | ||||
| 	} | ||||
|  | ||||
| 	magg.Checkpoint(&desc) | ||||
| 	require.NoError(t, aagg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, magg, kv.String("A", "B"), kv.String("C", "D")) | ||||
| 	checkpointSet.Add(&desc, ckpt, kv.String("A", "B"), kv.String("C", "D")) | ||||
|  | ||||
| 	fix.Export(checkpointSet) | ||||
|  | ||||
| @@ -239,28 +247,27 @@ func TestStdoutValueRecorderFormat(t *testing.T) { | ||||
|  | ||||
| func TestStdoutNoData(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("test.name", metric.ValueRecorderKind, metric.Float64NumberKind) | ||||
| 	for name, tc := range map[string]export.Aggregator{ | ||||
| 		"ddsketch":       ddsketch.New(&desc, ddsketch.NewDefaultConfig()), | ||||
| 		"minmaxsumcount": minmaxsumcount.New(&desc), | ||||
| 	} { | ||||
| 		tc := tc | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
|  | ||||
| 	runTwoAggs := func(agg, ckpt export.Aggregator) { | ||||
| 		t.Run(fmt.Sprintf("%T", agg), func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
|  | ||||
| 			fix := newFixture(t, stdout.Config{}) | ||||
|  | ||||
| 			checkpointSet := test.NewCheckpointSet(testResource) | ||||
|  | ||||
| 			magg := tc | ||||
| 			magg.Checkpoint(&desc) | ||||
| 			require.NoError(t, agg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 			checkpointSet.Add(&desc, magg) | ||||
| 			checkpointSet.Add(&desc, ckpt) | ||||
|  | ||||
| 			fix.Export(checkpointSet) | ||||
|  | ||||
| 			require.Equal(t, `{"updates":null}`, fix.Output()) | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	runTwoAggs(test.Unslice2(ddsketch.New(2, &desc, ddsketch.NewDefaultConfig()))) | ||||
| 	runTwoAggs(test.Unslice2(minmaxsumcount.New(2, &desc))) | ||||
| } | ||||
|  | ||||
| func TestStdoutLastValueNotSet(t *testing.T) { | ||||
| @@ -269,8 +276,9 @@ func TestStdoutLastValueNotSet(t *testing.T) { | ||||
| 	checkpointSet := test.NewCheckpointSet(testResource) | ||||
|  | ||||
| 	desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Float64NumberKind) | ||||
| 	lvagg := lastvalue.New() | ||||
| 	lvagg.Checkpoint(&desc) | ||||
|  | ||||
| 	lvagg, ckpt := test.Unslice2(lastvalue.New(2)) | ||||
| 	require.NoError(t, lvagg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 	checkpointSet.Add(&desc, lvagg, kv.String("A", "B"), kv.String("C", "D")) | ||||
|  | ||||
| @@ -319,11 +327,12 @@ func TestStdoutResource(t *testing.T) { | ||||
| 		checkpointSet := test.NewCheckpointSet(tc.res) | ||||
|  | ||||
| 		desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Float64NumberKind) | ||||
| 		lvagg := lastvalue.New() | ||||
| 		aggtest.CheckedUpdate(fix.t, lvagg, metric.NewFloat64Number(123.456), &desc) | ||||
| 		lvagg.Checkpoint(&desc) | ||||
| 		lvagg, ckpt := test.Unslice2(lastvalue.New(2)) | ||||
|  | ||||
| 		checkpointSet.Add(&desc, lvagg, tc.attrs...) | ||||
| 		aggtest.CheckedUpdate(fix.t, lvagg, metric.NewFloat64Number(123.456), &desc) | ||||
| 		require.NoError(t, lvagg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 		checkpointSet.Add(&desc, ckpt, tc.attrs...) | ||||
|  | ||||
| 		fix.Export(checkpointSet) | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,7 @@ package test | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| @@ -24,10 +25,6 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/array" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/resource" | ||||
| ) | ||||
|  | ||||
| @@ -36,6 +33,7 @@ type mapkey struct { | ||||
| 	distinct label.Distinct | ||||
| } | ||||
|  | ||||
| // CheckpointSet is useful for testing Exporters. | ||||
| type CheckpointSet struct { | ||||
| 	sync.RWMutex | ||||
| 	records  map[mapkey]export.Record | ||||
| @@ -43,6 +41,26 @@ type CheckpointSet struct { | ||||
| 	resource *resource.Resource | ||||
| } | ||||
|  | ||||
| // NoopAggregator is useful for testing Exporters. | ||||
| type NoopAggregator struct{} | ||||
|  | ||||
| var _ export.Aggregator = (*NoopAggregator)(nil) | ||||
|  | ||||
| // Update implements export.Aggregator. | ||||
| func (*NoopAggregator) Update(context.Context, metric.Number, *metric.Descriptor) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SynchronizedCopy implements export.Aggregator. | ||||
| func (*NoopAggregator) SynchronizedCopy(export.Aggregator, *metric.Descriptor) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Merge implements export.Aggregator. | ||||
| func (*NoopAggregator) Merge(export.Aggregator, *metric.Descriptor) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NewCheckpointSet returns a test CheckpointSet that new records could be added. | ||||
| // Records are grouped by their encoded labels. | ||||
| func NewCheckpointSet(resource *resource.Resource) *CheckpointSet { | ||||
| @@ -52,12 +70,13 @@ func NewCheckpointSet(resource *resource.Resource) *CheckpointSet { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Reset clears the Aggregator state. | ||||
| func (p *CheckpointSet) Reset() { | ||||
| 	p.records = make(map[mapkey]export.Record) | ||||
| 	p.updates = nil | ||||
| } | ||||
|  | ||||
| // Add a new descriptor to a Checkpoint. | ||||
| // Add a new record to a CheckpointSet. | ||||
| // | ||||
| // If there is an existing record with the same descriptor and labels, | ||||
| // the stored aggregator will be returned and should be merged. | ||||
| @@ -78,43 +97,6 @@ func (p *CheckpointSet) Add(desc *metric.Descriptor, newAgg export.Aggregator, l | ||||
| 	return newAgg, true | ||||
| } | ||||
|  | ||||
| func createNumber(desc *metric.Descriptor, v float64) metric.Number { | ||||
| 	if desc.NumberKind() == metric.Float64NumberKind { | ||||
| 		return metric.NewFloat64Number(v) | ||||
| 	} | ||||
| 	return metric.NewInt64Number(int64(v)) | ||||
| } | ||||
|  | ||||
| func (p *CheckpointSet) AddLastValue(desc *metric.Descriptor, v float64, labels ...kv.KeyValue) { | ||||
| 	p.updateAggregator(desc, lastvalue.New(), v, labels...) | ||||
| } | ||||
|  | ||||
| func (p *CheckpointSet) AddCounter(desc *metric.Descriptor, v float64, labels ...kv.KeyValue) { | ||||
| 	p.updateAggregator(desc, sum.New(), v, labels...) | ||||
| } | ||||
|  | ||||
| func (p *CheckpointSet) AddValueRecorder(desc *metric.Descriptor, v float64, labels ...kv.KeyValue) { | ||||
| 	p.updateAggregator(desc, array.New(), v, labels...) | ||||
| } | ||||
|  | ||||
| func (p *CheckpointSet) AddHistogramValueRecorder(desc *metric.Descriptor, boundaries []float64, v float64, labels ...kv.KeyValue) { | ||||
| 	p.updateAggregator(desc, histogram.New(desc, boundaries), v, labels...) | ||||
| } | ||||
|  | ||||
| func (p *CheckpointSet) updateAggregator(desc *metric.Descriptor, newAgg export.Aggregator, v float64, labels ...kv.KeyValue) { | ||||
| 	ctx := context.Background() | ||||
| 	// Updates and checkpoint the new aggregator | ||||
| 	_ = newAgg.Update(ctx, createNumber(desc, v), desc) | ||||
| 	newAgg.Checkpoint(desc) | ||||
|  | ||||
| 	// Try to add this aggregator to the CheckpointSet | ||||
| 	agg, added := p.Add(desc, newAgg, labels...) | ||||
| 	if !added { | ||||
| 		// An aggregator already exist for this descriptor and label set, we should merge them. | ||||
| 		_ = agg.Merge(newAgg, desc) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *CheckpointSet) ForEach(f func(export.Record) error) error { | ||||
| 	for _, r := range p.updates { | ||||
| 		if err := f(r); err != nil && !errors.Is(err, aggregation.ErrNoData) { | ||||
| @@ -123,3 +105,17 @@ func (p *CheckpointSet) ForEach(f func(export.Record) error) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Takes a slice of []some.Aggregator and returns a slice of []export.Aggregator | ||||
| func Unslice2(sl interface{}) (one, two export.Aggregator) { | ||||
| 	slv := reflect.ValueOf(sl) | ||||
| 	if slv.Type().Kind() != reflect.Slice { | ||||
| 		panic("Invalid Unslice2") | ||||
| 	} | ||||
| 	if slv.Len() != 2 { | ||||
| 		panic("Invalid Unslice2: length > 2") | ||||
| 	} | ||||
| 	one = slv.Index(0).Addr().Interface().(export.Aggregator) | ||||
| 	two = slv.Index(1).Addr().Interface().(export.Aggregator) | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								exporters/metric/test/test_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								exporters/metric/test/test_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| // Copyright The OpenTelemetry Authors | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func TestUnslice(t *testing.T) { | ||||
| 	in := make([]NoopAggregator, 2) | ||||
|  | ||||
| 	a, b := Unslice2(in) | ||||
|  | ||||
| 	require.Equal(t, a.(*NoopAggregator), &in[0]) | ||||
| 	require.Equal(t, b.(*NoopAggregator), &in[1]) | ||||
| } | ||||
| @@ -22,11 +22,13 @@ import ( | ||||
| 	commonpb "github.com/open-telemetry/opentelemetry-proto/gen/go/common/v1" | ||||
| 	metricpb "github.com/open-telemetry/opentelemetry-proto/gen/go/metrics/v1" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| 	"go.opentelemetry.io/otel/api/label" | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	"go.opentelemetry.io/otel/api/unit" | ||||
| 	"go.opentelemetry.io/otel/exporters/metric/test" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" | ||||
| 	sumAgg "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| @@ -80,17 +82,18 @@ func TestStringKeyValues(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestMinMaxSumCountValue(t *testing.T) { | ||||
| 	mmsc := minmaxsumcount.New(&metric.Descriptor{}) | ||||
| 	mmsc, ckpt := test.Unslice2(minmaxsumcount.New(2, &metric.Descriptor{})) | ||||
|  | ||||
| 	assert.NoError(t, mmsc.Update(context.Background(), 1, &metric.Descriptor{})) | ||||
| 	assert.NoError(t, mmsc.Update(context.Background(), 10, &metric.Descriptor{})) | ||||
|  | ||||
| 	// Prior to checkpointing ErrNoData should be returned. | ||||
| 	_, _, _, _, err := minMaxSumCountValues(mmsc) | ||||
| 	_, _, _, _, err := minMaxSumCountValues(ckpt.(aggregation.MinMaxSumCount)) | ||||
| 	assert.EqualError(t, err, aggregation.ErrNoData.Error()) | ||||
|  | ||||
| 	// Checkpoint to set non-zero values | ||||
| 	mmsc.Checkpoint(&metric.Descriptor{}) | ||||
| 	min, max, sum, count, err := minMaxSumCountValues(mmsc) | ||||
| 	require.NoError(t, mmsc.SynchronizedCopy(ckpt, &metric.Descriptor{})) | ||||
| 	min, max, sum, count, err := minMaxSumCountValues(ckpt.(aggregation.MinMaxSumCount)) | ||||
| 	if assert.NoError(t, err) { | ||||
| 		assert.Equal(t, min, metric.NewInt64Number(1)) | ||||
| 		assert.Equal(t, max, metric.NewInt64Number(10)) | ||||
| @@ -142,17 +145,17 @@ func TestMinMaxSumCountMetricDescriptor(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	mmsc := minmaxsumcount.New(&metric.Descriptor{}) | ||||
| 	mmsc, ckpt := test.Unslice2(minmaxsumcount.New(2, &metric.Descriptor{})) | ||||
| 	if !assert.NoError(t, mmsc.Update(ctx, 1, &metric.Descriptor{})) { | ||||
| 		return | ||||
| 	} | ||||
| 	mmsc.Checkpoint(&metric.Descriptor{}) | ||||
| 	require.NoError(t, mmsc.SynchronizedCopy(ckpt, &metric.Descriptor{})) | ||||
| 	for _, test := range tests { | ||||
| 		desc := metric.NewDescriptor(test.name, test.metricKind, test.numberKind, | ||||
| 			metric.WithDescription(test.description), | ||||
| 			metric.WithUnit(test.unit)) | ||||
| 		labels := label.NewSet(test.labels...) | ||||
| 		got, err := minMaxSumCount(&desc, &labels, mmsc) | ||||
| 		got, err := minMaxSumCount(&desc, &labels, ckpt.(aggregation.MinMaxSumCount)) | ||||
| 		if assert.NoError(t, err) { | ||||
| 			assert.Equal(t, test.expected, got.MetricDescriptor) | ||||
| 		} | ||||
| @@ -162,10 +165,11 @@ func TestMinMaxSumCountMetricDescriptor(t *testing.T) { | ||||
| func TestMinMaxSumCountDatapoints(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.Int64NumberKind) | ||||
| 	labels := label.NewSet() | ||||
| 	mmsc := minmaxsumcount.New(&desc) | ||||
| 	mmsc, ckpt := test.Unslice2(minmaxsumcount.New(2, &desc)) | ||||
|  | ||||
| 	assert.NoError(t, mmsc.Update(context.Background(), 1, &desc)) | ||||
| 	assert.NoError(t, mmsc.Update(context.Background(), 10, &desc)) | ||||
| 	mmsc.Checkpoint(&desc) | ||||
| 	require.NoError(t, mmsc.SynchronizedCopy(ckpt, &desc)) | ||||
| 	expected := []*metricpb.SummaryDataPoint{ | ||||
| 		{ | ||||
| 			Count: 2, | ||||
| @@ -182,7 +186,7 @@ func TestMinMaxSumCountDatapoints(t *testing.T) { | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	m, err := minMaxSumCount(&desc, &labels, mmsc) | ||||
| 	m, err := minMaxSumCount(&desc, &labels, ckpt.(aggregation.MinMaxSumCount)) | ||||
| 	if assert.NoError(t, err) { | ||||
| 		assert.Equal(t, []*metricpb.Int64DataPoint(nil), m.Int64DataPoints) | ||||
| 		assert.Equal(t, []*metricpb.DoubleDataPoint(nil), m.DoubleDataPoints) | ||||
| @@ -195,7 +199,7 @@ func TestMinMaxSumCountPropagatesErrors(t *testing.T) { | ||||
| 	// ErrNoData should be returned by both the Min and Max values of | ||||
| 	// a MinMaxSumCount Aggregator. Use this fact to check the error is | ||||
| 	// correctly returned. | ||||
| 	mmsc := minmaxsumcount.New(&metric.Descriptor{}) | ||||
| 	mmsc := &minmaxsumcount.New(1, &metric.Descriptor{})[0] | ||||
| 	_, _, _, _, err := minMaxSumCountValues(mmsc) | ||||
| 	assert.Error(t, err) | ||||
| 	assert.Equal(t, aggregation.ErrNoData, err) | ||||
| @@ -249,7 +253,7 @@ func TestSumMetricDescriptor(t *testing.T) { | ||||
| 			metric.WithUnit(test.unit), | ||||
| 		) | ||||
| 		labels := label.NewSet(test.labels...) | ||||
| 		got, err := sum(&desc, &labels, sumAgg.New()) | ||||
| 		got, err := sum(&desc, &labels, &sumAgg.New(1)[0]) | ||||
| 		if assert.NoError(t, err) { | ||||
| 			assert.Equal(t, test.expected, got.MetricDescriptor) | ||||
| 		} | ||||
| @@ -259,10 +263,10 @@ func TestSumMetricDescriptor(t *testing.T) { | ||||
| func TestSumInt64DataPoints(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.Int64NumberKind) | ||||
| 	labels := label.NewSet() | ||||
| 	s := sumAgg.New() | ||||
| 	s, ckpt := test.Unslice2(sumAgg.New(2)) | ||||
| 	assert.NoError(t, s.Update(context.Background(), metric.Number(1), &desc)) | ||||
| 	s.Checkpoint(&desc) | ||||
| 	if m, err := sum(&desc, &labels, s); assert.NoError(t, err) { | ||||
| 	require.NoError(t, s.SynchronizedCopy(ckpt, &desc)) | ||||
| 	if m, err := sum(&desc, &labels, ckpt.(aggregation.Sum)); assert.NoError(t, err) { | ||||
| 		assert.Equal(t, []*metricpb.Int64DataPoint{{Value: 1}}, m.Int64DataPoints) | ||||
| 		assert.Equal(t, []*metricpb.DoubleDataPoint(nil), m.DoubleDataPoints) | ||||
| 		assert.Equal(t, []*metricpb.HistogramDataPoint(nil), m.HistogramDataPoints) | ||||
| @@ -273,10 +277,10 @@ func TestSumInt64DataPoints(t *testing.T) { | ||||
| func TestSumFloat64DataPoints(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.Float64NumberKind) | ||||
| 	labels := label.NewSet() | ||||
| 	s := sumAgg.New() | ||||
| 	s, ckpt := test.Unslice2(sumAgg.New(2)) | ||||
| 	assert.NoError(t, s.Update(context.Background(), metric.NewFloat64Number(1), &desc)) | ||||
| 	s.Checkpoint(&desc) | ||||
| 	if m, err := sum(&desc, &labels, s); assert.NoError(t, err) { | ||||
| 	require.NoError(t, s.SynchronizedCopy(ckpt, &desc)) | ||||
| 	if m, err := sum(&desc, &labels, ckpt.(aggregation.Sum)); assert.NoError(t, err) { | ||||
| 		assert.Equal(t, []*metricpb.Int64DataPoint(nil), m.Int64DataPoints) | ||||
| 		assert.Equal(t, []*metricpb.DoubleDataPoint{{Value: 1}}, m.DoubleDataPoints) | ||||
| 		assert.Equal(t, []*metricpb.HistogramDataPoint(nil), m.HistogramDataPoints) | ||||
| @@ -287,7 +291,7 @@ func TestSumFloat64DataPoints(t *testing.T) { | ||||
| func TestSumErrUnknownValueType(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.NumberKind(-1)) | ||||
| 	labels := label.NewSet() | ||||
| 	s := sumAgg.New() | ||||
| 	s := &sumAgg.New(1)[0] | ||||
| 	_, err := sum(&desc, &labels, s) | ||||
| 	assert.Error(t, err) | ||||
| 	if !errors.Is(err, ErrUnknownValueType) { | ||||
|   | ||||
| @@ -29,6 +29,7 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| 	"go.opentelemetry.io/otel/api/label" | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	"go.opentelemetry.io/otel/exporters/metric/test" | ||||
| 	metricsdk "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" | ||||
| @@ -662,12 +663,12 @@ func runMetricExportTest(t *testing.T, exp *Exporter, rs []record, expected []me | ||||
| 		desc := metric.NewDescriptor(r.name, r.mKind, r.nKind, r.opts...) | ||||
| 		labs := label.NewSet(r.labels...) | ||||
|  | ||||
| 		var agg metricsdk.Aggregator | ||||
| 		var agg, ckpt metricsdk.Aggregator | ||||
| 		switch r.mKind { | ||||
| 		case metric.CounterKind: | ||||
| 			agg = sum.New() | ||||
| 			agg, ckpt = test.Unslice2(sum.New(2)) | ||||
| 		default: | ||||
| 			agg = minmaxsumcount.New(&desc) | ||||
| 			agg, ckpt = test.Unslice2(minmaxsumcount.New(2, &desc)) | ||||
| 		} | ||||
|  | ||||
| 		ctx := context.Background() | ||||
| @@ -684,11 +685,11 @@ func runMetricExportTest(t *testing.T, exp *Exporter, rs []record, expected []me | ||||
| 		default: | ||||
| 			t.Fatalf("invalid number kind: %v", r.nKind) | ||||
| 		} | ||||
| 		agg.Checkpoint(&desc) | ||||
| 		require.NoError(t, agg.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 		equiv := r.resource.Equivalent() | ||||
| 		resources[equiv] = r.resource | ||||
| 		recs[equiv] = append(recs[equiv], metricsdk.NewRecord(&desc, &labs, r.resource, agg)) | ||||
| 		recs[equiv] = append(recs[equiv], metricsdk.NewRecord(&desc, &labs, r.resource, ckpt)) | ||||
| 	} | ||||
| 	for _, records := range recs { | ||||
| 		assert.NoError(t, exp.Export(context.Background(), &checkpointSet{records: records})) | ||||
|   | ||||
| @@ -73,17 +73,24 @@ type Integrator interface { | ||||
| // AggregationSelector supports selecting the kind of Aggregator to | ||||
| // use at runtime for a specific metric instrument. | ||||
| type AggregationSelector interface { | ||||
| 	// AggregatorFor returns the kind of aggregator suited to the | ||||
| 	// requested export.  Returning `nil` indicates to ignore this | ||||
| 	// metric instrument.  This must return a consistent type to | ||||
| 	// avoid confusion in later stages of the metrics export | ||||
| 	// process, i.e., when Merging multiple aggregators for a | ||||
| 	// specific instrument. | ||||
| 	// AggregatorFor allocates a variable number of aggregators of | ||||
| 	// a kind suitable for the requested export.  This method | ||||
| 	// initializes a `...*Aggregator`, to support making a single | ||||
| 	// allocation. | ||||
| 	// | ||||
| 	// When the call returns without initializing the *Aggregator | ||||
| 	// to a non-nil value, the metric instrument is explicitly | ||||
| 	// disabled. | ||||
| 	// | ||||
| 	// This must return a consistent type to avoid confusion in | ||||
| 	// later stages of the metrics export process, i.e., when | ||||
| 	// Merging or Checkpointing aggregators for a specific | ||||
| 	// instrument. | ||||
| 	// | ||||
| 	// Note: This is context-free because the aggregator should | ||||
| 	// not relate to the incoming context.  This call should not | ||||
| 	// block. | ||||
| 	AggregatorFor(*metric.Descriptor) Aggregator | ||||
| 	AggregatorFor(*metric.Descriptor, ...*Aggregator) | ||||
| } | ||||
|  | ||||
| // Aggregator implements a specific aggregation behavior, e.g., a | ||||
| @@ -99,35 +106,42 @@ type AggregationSelector interface { | ||||
| // MinMaxSumCount aggregator to a Counter instrument. | ||||
| type Aggregator interface { | ||||
| 	// Update receives a new measured value and incorporates it | ||||
| 	// into the aggregation.  Update() calls may arrive | ||||
| 	// concurrently as the SDK does not provide synchronization. | ||||
| 	// into the aggregation.  Update() calls may be called | ||||
| 	// concurrently. | ||||
| 	// | ||||
| 	// Descriptor.NumberKind() should be consulted to determine | ||||
| 	// whether the provided number is an int64 or float64. | ||||
| 	// | ||||
| 	// The Context argument comes from user-level code and could be | ||||
| 	// inspected for distributed or span context. | ||||
| 	// inspected for a `correlation.Map` or `trace.SpanContext`. | ||||
| 	Update(context.Context, metric.Number, *metric.Descriptor) error | ||||
|  | ||||
| 	// Checkpoint is called during collection to finish one period | ||||
| 	// of aggregation by atomically saving the current value. | ||||
| 	// Checkpoint() is called concurrently with Update(). | ||||
| 	// Checkpoint should reset the current state to the empty | ||||
| 	// state, in order to begin computing a new delta for the next | ||||
| 	// collection period. | ||||
| 	// SynchronizedCopy is called during collection to finish one | ||||
| 	// period of aggregation by atomically saving the | ||||
| 	// currently-updating state into the argument Aggregator. | ||||
| 	// | ||||
| 	// After the checkpoint is taken, the current value may be | ||||
| 	// accessed using by converting to one a suitable interface | ||||
| 	// types in the `aggregator` sub-package. | ||||
| 	// SynchronizedCopy() is called concurrently with Update().  These | ||||
| 	// two methods must be synchronized with respect to each | ||||
| 	// other, for correctness. | ||||
| 	// | ||||
| 	// After saving a synchronized copy, the Aggregator can be converted | ||||
| 	// into one or more of the interfaces in the `aggregation` sub-package, | ||||
| 	// according to kind of Aggregator that was selected. | ||||
| 	// | ||||
| 	// This method will return an InconsistentAggregatorError if | ||||
| 	// this Aggregator cannot be copied into the destination due | ||||
| 	// to an incompatible type. | ||||
| 	// | ||||
| 	// This call has no Context argument because it is expected to | ||||
| 	// perform only computation. | ||||
| 	Checkpoint(*metric.Descriptor) | ||||
| 	SynchronizedCopy(destination Aggregator, descriptor *metric.Descriptor) error | ||||
|  | ||||
| 	// Merge combines the checkpointed state from the argument | ||||
| 	// aggregator into this aggregator's checkpointed state. | ||||
| 	// Merge() is called in a single-threaded context, no locking | ||||
| 	// is required. | ||||
| 	// Aggregator into this Aggregator.  Merge is not synchronized | ||||
| 	// with respect to Update or SynchronizedCopy. | ||||
| 	// | ||||
| 	// The owner of an Aggregator being merged is responsible for | ||||
| 	// synchronization of both Aggregator states. | ||||
| 	Merge(Aggregator, *metric.Descriptor) error | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,11 +23,11 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| ) | ||||
|  | ||||
| // NewInconsistentMergeError formats an error describing an attempt to | ||||
| // merge different-type aggregators.  The result can be unwrapped as | ||||
| // NewInconsistentAggregatorError formats an error describing an attempt to | ||||
| // Checkpoint or Merge different-type aggregators.  The result can be unwrapped as | ||||
| // an ErrInconsistentType. | ||||
| func NewInconsistentMergeError(a1, a2 export.Aggregator) error { | ||||
| 	return fmt.Errorf("cannot merge %T with %T: %w", a1, a2, aggregation.ErrInconsistentType) | ||||
| func NewInconsistentAggregatorError(a1, a2 export.Aggregator) error { | ||||
| 	return fmt.Errorf("%w: %T and %T", aggregation.ErrInconsistentType, a1, a2) | ||||
| } | ||||
|  | ||||
| // RangeTest is a commmon routine for testing for valid input values. | ||||
|   | ||||
| @@ -28,11 +28,11 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| ) | ||||
|  | ||||
| func TestInconsistentMergeErr(t *testing.T) { | ||||
| 	err := aggregator.NewInconsistentMergeError(sum.New(), lastvalue.New()) | ||||
| func TestInconsistentAggregatorErr(t *testing.T) { | ||||
| 	err := aggregator.NewInconsistentAggregatorError(&sum.New(1)[0], &lastvalue.New(1)[0]) | ||||
| 	require.Equal( | ||||
| 		t, | ||||
| 		"cannot merge *sum.Aggregator with *lastvalue.Aggregator: inconsistent aggregator types", | ||||
| 		"inconsistent aggregator types: *sum.Aggregator and *lastvalue.Aggregator", | ||||
| 		err.Error(), | ||||
| 	) | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrInconsistentType)) | ||||
|   | ||||
| @@ -31,11 +31,9 @@ type ( | ||||
| 	// Aggregator aggregates events that form a distribution, keeping | ||||
| 	// an array with the exact set of values. | ||||
| 	Aggregator struct { | ||||
| 		// ckptSum needs to be aligned for 64-bit atomic operations. | ||||
| 		ckptSum    metric.Number | ||||
| 		lock       sync.Mutex | ||||
| 		current    points | ||||
| 		checkpoint points | ||||
| 		lock   sync.Mutex | ||||
| 		sum    metric.Number | ||||
| 		points points | ||||
| 	} | ||||
|  | ||||
| 	points []metric.Number | ||||
| @@ -48,9 +46,9 @@ var _ aggregation.Points = &Aggregator{} | ||||
|  | ||||
| // New returns a new array aggregator, which aggregates recorded | ||||
| // measurements by storing them in an array.  This type uses a mutex | ||||
| // for Update() and Checkpoint() concurrency. | ||||
| func New() *Aggregator { | ||||
| 	return &Aggregator{} | ||||
| // for Update() and SynchronizedCopy() concurrency. | ||||
| func New(cnt int) []Aggregator { | ||||
| 	return make([]Aggregator, cnt) | ||||
| } | ||||
|  | ||||
| // Kind returns aggregation.ExactKind. | ||||
| @@ -60,64 +58,65 @@ func (c *Aggregator) Kind() aggregation.Kind { | ||||
|  | ||||
| // Sum returns the sum of values in the checkpoint. | ||||
| func (c *Aggregator) Sum() (metric.Number, error) { | ||||
| 	return c.ckptSum, nil | ||||
| 	return c.sum, nil | ||||
| } | ||||
|  | ||||
| // Count returns the number of values in the checkpoint. | ||||
| func (c *Aggregator) Count() (int64, error) { | ||||
| 	return int64(len(c.checkpoint)), nil | ||||
| 	return int64(len(c.points)), nil | ||||
| } | ||||
|  | ||||
| // Max returns the maximum value in the checkpoint. | ||||
| func (c *Aggregator) Max() (metric.Number, error) { | ||||
| 	return c.checkpoint.Quantile(1) | ||||
| 	return c.points.Quantile(1) | ||||
| } | ||||
|  | ||||
| // Min returns the mininum value in the checkpoint. | ||||
| func (c *Aggregator) Min() (metric.Number, error) { | ||||
| 	return c.checkpoint.Quantile(0) | ||||
| 	return c.points.Quantile(0) | ||||
| } | ||||
|  | ||||
| // Quantile returns the estimated quantile of data in the checkpoint. | ||||
| // It is an error if `q` is less than 0 or greated than 1. | ||||
| func (c *Aggregator) Quantile(q float64) (metric.Number, error) { | ||||
| 	return c.checkpoint.Quantile(q) | ||||
| 	return c.points.Quantile(q) | ||||
| } | ||||
|  | ||||
| // Points returns access to the raw data set. | ||||
| func (c *Aggregator) Points() ([]metric.Number, error) { | ||||
| 	return c.checkpoint, nil | ||||
| 	return c.points, nil | ||||
| } | ||||
|  | ||||
| // Checkpoint saves the current state and resets the current state to | ||||
| // SynchronizedCopy saves the current state to oa and resets the current state to | ||||
| // the empty set, taking a lock to prevent concurrent Update() calls. | ||||
| func (c *Aggregator) Checkpoint(desc *metric.Descriptor) { | ||||
| 	c.lock.Lock() | ||||
| 	c.checkpoint, c.current = c.current, nil | ||||
| 	c.lock.Unlock() | ||||
| func (c *Aggregator) SynchronizedCopy(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	kind := desc.NumberKind() | ||||
| 	c.lock.Lock() | ||||
| 	o.points, c.points = c.points, nil | ||||
| 	o.sum, c.sum = c.sum, 0 | ||||
| 	c.lock.Unlock() | ||||
|  | ||||
| 	// TODO: This sort should be done lazily, only when quantiles | ||||
| 	// are requested.  The SDK specification says you can use this | ||||
| 	// aggregator to simply list values in the order they were | ||||
| 	// received as an alternative to requesting quantile information. | ||||
| 	c.sort(kind) | ||||
|  | ||||
| 	c.ckptSum = metric.Number(0) | ||||
|  | ||||
| 	for _, v := range c.checkpoint { | ||||
| 		c.ckptSum.AddNumber(kind, v) | ||||
| 	} | ||||
| 	o.sort(desc.NumberKind()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update adds the recorded measurement to the current data set. | ||||
| // Update takes a lock to prevent concurrent Update() and Checkpoint() | ||||
| // Update takes a lock to prevent concurrent Update() and SynchronizedCopy() | ||||
| // calls. | ||||
| func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metric.Descriptor) error { | ||||
| 	c.lock.Lock() | ||||
| 	c.current = append(c.current, number) | ||||
| 	c.points = append(c.points, number) | ||||
| 	c.sum.AddNumber(desc.NumberKind(), number) | ||||
| 	c.lock.Unlock() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -125,21 +124,24 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| func (c *Aggregator) Merge(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentMergeError(c, oa) | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	c.ckptSum.AddNumber(desc.NumberKind(), o.ckptSum) | ||||
| 	c.checkpoint = combine(c.checkpoint, o.checkpoint, desc.NumberKind()) | ||||
| 	// Note: Current assumption is that `o` was checkpointed, | ||||
| 	// therefore is already sorted.  See the TODO above, since | ||||
| 	// this is an open question. | ||||
| 	c.sum.AddNumber(desc.NumberKind(), o.sum) | ||||
| 	c.points = combine(c.points, o.points, desc.NumberKind()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *Aggregator) sort(kind metric.NumberKind) { | ||||
| 	switch kind { | ||||
| 	case metric.Float64NumberKind: | ||||
| 		sort.Float64s(*(*[]float64)(unsafe.Pointer(&c.checkpoint))) | ||||
| 		sort.Float64s(*(*[]float64)(unsafe.Pointer(&c.points))) | ||||
|  | ||||
| 	case metric.Int64NumberKind: | ||||
| 		sort.Sort(&c.checkpoint) | ||||
| 		sort.Sort(&c.points) | ||||
|  | ||||
| 	default: | ||||
| 		// NOTE: This can't happen because the SDK doesn't | ||||
| @@ -185,11 +187,11 @@ func (p *points) Swap(i, j int) { | ||||
| // of a quantile. | ||||
| func (p *points) Quantile(q float64) (metric.Number, error) { | ||||
| 	if len(*p) == 0 { | ||||
| 		return metric.Number(0), aggregation.ErrNoData | ||||
| 		return 0, aggregation.ErrNoData | ||||
| 	} | ||||
|  | ||||
| 	if q < 0 || q > 1 { | ||||
| 		return metric.Number(0), aggregation.ErrInvalidQuantile | ||||
| 		return 0, aggregation.ErrInvalidQuantile | ||||
| 	} | ||||
|  | ||||
| 	if q == 0 || len(*p) == 1 { | ||||
|   | ||||
| @@ -15,43 +15,55 @@ | ||||
| package array | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/test" | ||||
| ) | ||||
|  | ||||
| // Ensure struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "Aggregator.ckptSum", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.ckptSum), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| type updateTest struct { | ||||
| 	count int | ||||
| } | ||||
|  | ||||
| func checkZero(t *testing.T, agg *Aggregator, desc *metric.Descriptor) { | ||||
| 	kind := desc.NumberKind() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, kind.Zero(), sum) | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, int64(0), count) | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), max) | ||||
|  | ||||
| 	min, err := agg.Min() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), min) | ||||
| } | ||||
|  | ||||
| func new2() (_, _ *Aggregator) { | ||||
| 	alloc := New(2) | ||||
| 	return &alloc[0], &alloc[1] | ||||
| } | ||||
|  | ||||
| func new4() (_, _, _, _ *Aggregator) { | ||||
| 	alloc := New(4) | ||||
| 	return &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
| } | ||||
|  | ||||
| func (ut *updateTest) run(t *testing.T, profile test.Profile) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 	agg := New() | ||||
| 	agg, ckpt := new2() | ||||
|  | ||||
| 	all := test.NewNumbers(profile.NumberKind) | ||||
|  | ||||
| @@ -65,11 +77,14 @@ func (ut *updateTest) run(t *testing.T, profile test.Profile) { | ||||
| 		test.CheckedUpdate(t, agg, y, descriptor) | ||||
| 	} | ||||
|  | ||||
| 	agg.Checkpoint(descriptor) | ||||
| 	err := agg.SynchronizedCopy(ckpt, descriptor) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	checkZero(t, agg, descriptor) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	sum, err := ckpt.Sum() | ||||
| 	require.Nil(t, err) | ||||
| 	allSum := all.Sum() | ||||
| 	require.InEpsilon(t, | ||||
| @@ -77,19 +92,19 @@ func (ut *updateTest) run(t *testing.T, profile test.Profile) { | ||||
| 		sum.CoerceToFloat64(profile.NumberKind), | ||||
| 		0.0000001, | ||||
| 		"Same sum") | ||||
| 	count, err := agg.Count() | ||||
| 	count, err := ckpt.Count() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Count(), count, "Same count") | ||||
|  | ||||
| 	min, err := agg.Min() | ||||
| 	min, err := ckpt.Min() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Min(), min, "Same min") | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	max, err := ckpt.Max() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Max(), max, "Same max") | ||||
|  | ||||
| 	qx, err := agg.Quantile(0.5) | ||||
| 	qx, err := ckpt.Quantile(0.5) | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Median(), qx, "Same median") | ||||
| } | ||||
| @@ -115,9 +130,7 @@ type mergeTest struct { | ||||
|  | ||||
| func (mt *mergeTest) run(t *testing.T, profile test.Profile) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 	agg1 := New() | ||||
| 	agg2 := New() | ||||
| 	agg1, agg2, ckpt1, ckpt2 := new4() | ||||
|  | ||||
| 	all := test.NewNumbers(profile.NumberKind) | ||||
|  | ||||
| @@ -141,14 +154,17 @@ func (mt *mergeTest) run(t *testing.T, profile test.Profile) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	agg1.Checkpoint(descriptor) | ||||
| 	agg2.Checkpoint(descriptor) | ||||
| 	require.NoError(t, agg1.SynchronizedCopy(ckpt1, descriptor)) | ||||
| 	require.NoError(t, agg2.SynchronizedCopy(ckpt2, descriptor)) | ||||
|  | ||||
| 	test.CheckedMerge(t, agg1, agg2, descriptor) | ||||
| 	checkZero(t, agg1, descriptor) | ||||
| 	checkZero(t, agg2, descriptor) | ||||
|  | ||||
| 	test.CheckedMerge(t, ckpt1, ckpt2, descriptor) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	sum, err := agg1.Sum() | ||||
| 	sum, err := ckpt1.Sum() | ||||
| 	require.Nil(t, err) | ||||
| 	allSum := all.Sum() | ||||
| 	require.InEpsilon(t, | ||||
| @@ -156,19 +172,19 @@ func (mt *mergeTest) run(t *testing.T, profile test.Profile) { | ||||
| 		sum.CoerceToFloat64(profile.NumberKind), | ||||
| 		0.0000001, | ||||
| 		"Same sum - absolute") | ||||
| 	count, err := agg1.Count() | ||||
| 	count, err := ckpt1.Count() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Count(), count, "Same count - absolute") | ||||
|  | ||||
| 	min, err := agg1.Min() | ||||
| 	min, err := ckpt1.Min() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Min(), min, "Same min - absolute") | ||||
|  | ||||
| 	max, err := agg1.Max() | ||||
| 	max, err := ckpt1.Max() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Max(), max, "Same max - absolute") | ||||
|  | ||||
| 	qx, err := agg1.Quantile(0.5) | ||||
| 	qx, err := ckpt1.Quantile(0.5) | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Median(), qx, "Same median - absolute") | ||||
| } | ||||
| @@ -195,17 +211,17 @@ func TestArrayMerge(t *testing.T) { | ||||
|  | ||||
| func TestArrayErrors(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		agg := New() | ||||
| 		agg, ckpt := new2() | ||||
|  | ||||
| 		_, err := agg.Max() | ||||
| 		_, err := ckpt.Max() | ||||
| 		require.Error(t, err) | ||||
| 		require.Equal(t, err, aggregation.ErrNoData) | ||||
|  | ||||
| 		_, err = agg.Min() | ||||
| 		_, err = ckpt.Min() | ||||
| 		require.Error(t, err) | ||||
| 		require.Equal(t, err, aggregation.ErrNoData) | ||||
|  | ||||
| 		_, err = agg.Quantile(0.1) | ||||
| 		_, err = ckpt.Quantile(0.1) | ||||
| 		require.Error(t, err) | ||||
| 		require.Equal(t, err, aggregation.ErrNoData) | ||||
|  | ||||
| @@ -216,23 +232,23 @@ func TestArrayErrors(t *testing.T) { | ||||
| 		if profile.NumberKind == metric.Float64NumberKind { | ||||
| 			test.CheckedUpdate(t, agg, metric.NewFloat64Number(math.NaN()), descriptor) | ||||
| 		} | ||||
| 		agg.Checkpoint(descriptor) | ||||
| 		require.NoError(t, agg.SynchronizedCopy(ckpt, descriptor)) | ||||
|  | ||||
| 		count, err := agg.Count() | ||||
| 		count, err := ckpt.Count() | ||||
| 		require.Equal(t, int64(1), count, "NaN value was not counted") | ||||
| 		require.Nil(t, err) | ||||
|  | ||||
| 		num, err := agg.Quantile(0) | ||||
| 		num, err := ckpt.Quantile(0) | ||||
| 		require.Nil(t, err) | ||||
| 		require.Equal(t, num, metric.Number(0)) | ||||
|  | ||||
| 		_, err = agg.Quantile(-0.0001) | ||||
| 		_, err = ckpt.Quantile(-0.0001) | ||||
| 		require.Error(t, err) | ||||
| 		require.Equal(t, err, aggregation.ErrInvalidQuantile) | ||||
| 		require.True(t, errors.Is(err, aggregation.ErrInvalidQuantile)) | ||||
|  | ||||
| 		_, err = agg.Quantile(1.0001) | ||||
| 		require.Error(t, err) | ||||
| 		require.Equal(t, err, aggregation.ErrInvalidQuantile) | ||||
| 		require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @@ -269,7 +285,7 @@ func TestArrayFloat64(t *testing.T) { | ||||
|  | ||||
| 	all := test.NewNumbers(metric.Float64NumberKind) | ||||
|  | ||||
| 	agg := New() | ||||
| 	agg, ckpt := new2() | ||||
|  | ||||
| 	for _, f := range fpsf(1) { | ||||
| 		all.Append(metric.NewFloat64Number(f)) | ||||
| @@ -281,32 +297,32 @@ func TestArrayFloat64(t *testing.T) { | ||||
| 		test.CheckedUpdate(t, agg, metric.NewFloat64Number(f), descriptor) | ||||
| 	} | ||||
|  | ||||
| 	agg.Checkpoint(descriptor) | ||||
| 	require.NoError(t, agg.SynchronizedCopy(ckpt, descriptor)) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	sum, err := ckpt.Sum() | ||||
| 	require.Nil(t, err) | ||||
| 	allSum := all.Sum() | ||||
| 	require.InEpsilon(t, (&allSum).AsFloat64(), sum.AsFloat64(), 0.0000001, "Same sum") | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	count, err := ckpt.Count() | ||||
| 	require.Equal(t, all.Count(), count, "Same count") | ||||
| 	require.Nil(t, err) | ||||
|  | ||||
| 	min, err := agg.Min() | ||||
| 	min, err := ckpt.Min() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Min(), min, "Same min") | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	max, err := ckpt.Max() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Max(), max, "Same max") | ||||
|  | ||||
| 	qx, err := agg.Quantile(0.5) | ||||
| 	qx, err := ckpt.Quantile(0.5) | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Median(), qx, "Same median") | ||||
|  | ||||
| 	po, err := agg.Points() | ||||
| 	po, err := ckpt.Points() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, all.Len(), len(po), "Points() must have same length of updates") | ||||
| 	for i := 0; i < len(po); i++ { | ||||
|   | ||||
| @@ -31,11 +31,10 @@ type Config = sdk.Config | ||||
|  | ||||
| // Aggregator aggregates events into a distribution. | ||||
| type Aggregator struct { | ||||
| 	lock       sync.Mutex | ||||
| 	cfg        *Config | ||||
| 	kind       metric.NumberKind | ||||
| 	current    *sdk.DDSketch | ||||
| 	checkpoint *sdk.DDSketch | ||||
| 	lock   sync.Mutex | ||||
| 	cfg    *Config | ||||
| 	kind   metric.NumberKind | ||||
| 	sketch *sdk.DDSketch | ||||
| } | ||||
|  | ||||
| var _ export.Aggregator = &Aggregator{} | ||||
| @@ -43,13 +42,16 @@ var _ aggregation.MinMaxSumCount = &Aggregator{} | ||||
| var _ aggregation.Distribution = &Aggregator{} | ||||
|  | ||||
| // New returns a new DDSketch aggregator. | ||||
| func New(desc *metric.Descriptor, cfg *Config) *Aggregator { | ||||
| 	return &Aggregator{ | ||||
| 		cfg:        cfg, | ||||
| 		kind:       desc.NumberKind(), | ||||
| 		current:    sdk.NewDDSketch(cfg), | ||||
| 		checkpoint: sdk.NewDDSketch(cfg), | ||||
| func New(cnt int, desc *metric.Descriptor, cfg *Config) []Aggregator { | ||||
| 	aggs := make([]Aggregator, cnt) | ||||
| 	for i := range aggs { | ||||
| 		aggs[i] = Aggregator{ | ||||
| 			cfg:    cfg, | ||||
| 			kind:   desc.NumberKind(), | ||||
| 			sketch: sdk.NewDDSketch(cfg), | ||||
| 		} | ||||
| 	} | ||||
| 	return aggs | ||||
| } | ||||
|  | ||||
| // Kind returns aggregation.SketchKind. | ||||
| @@ -58,22 +60,18 @@ func (c *Aggregator) Kind() aggregation.Kind { | ||||
| } | ||||
|  | ||||
| // NewDefaultConfig returns a new, default DDSketch config. | ||||
| // | ||||
| // TODO: Should the Config constructor set minValue to -Inf to | ||||
| // when the descriptor has absolute=false?  This requires providing | ||||
| // values for alpha and maxNumBins, apparently. | ||||
| func NewDefaultConfig() *Config { | ||||
| 	return sdk.NewDefaultConfig() | ||||
| } | ||||
|  | ||||
| // Sum returns the sum of values in the checkpoint. | ||||
| func (c *Aggregator) Sum() (metric.Number, error) { | ||||
| 	return c.toNumber(c.checkpoint.Sum()), nil | ||||
| 	return c.toNumber(c.sketch.Sum()), nil | ||||
| } | ||||
|  | ||||
| // Count returns the number of values in the checkpoint. | ||||
| func (c *Aggregator) Count() (int64, error) { | ||||
| 	return c.checkpoint.Count(), nil | ||||
| 	return c.sketch.Count(), nil | ||||
| } | ||||
|  | ||||
| // Max returns the maximum value in the checkpoint. | ||||
| @@ -89,12 +87,12 @@ func (c *Aggregator) Min() (metric.Number, error) { | ||||
| // Quantile returns the estimated quantile of data in the checkpoint. | ||||
| // It is an error if `q` is less than 0 or greated than 1. | ||||
| func (c *Aggregator) Quantile(q float64) (metric.Number, error) { | ||||
| 	if c.checkpoint.Count() == 0 { | ||||
| 		return metric.Number(0), aggregation.ErrNoData | ||||
| 	if c.sketch.Count() == 0 { | ||||
| 		return 0, aggregation.ErrNoData | ||||
| 	} | ||||
| 	f := c.checkpoint.Quantile(q) | ||||
| 	f := c.sketch.Quantile(q) | ||||
| 	if math.IsNaN(f) { | ||||
| 		return metric.Number(0), aggregation.ErrInvalidQuantile | ||||
| 		return 0, aggregation.ErrInvalidQuantile | ||||
| 	} | ||||
| 	return c.toNumber(f), nil | ||||
| } | ||||
| @@ -106,24 +104,29 @@ func (c *Aggregator) toNumber(f float64) metric.Number { | ||||
| 	return metric.NewInt64Number(int64(f)) | ||||
| } | ||||
|  | ||||
| // Checkpoint saves the current state and resets the current state to | ||||
| // the empty set, taking a lock to prevent concurrent Update() calls. | ||||
| func (c *Aggregator) Checkpoint(*metric.Descriptor) { | ||||
| // SynchronizedCopy saves the current state into oa and resets the current state to | ||||
| // a new sketch, taking a lock to prevent concurrent Update() calls. | ||||
| func (c *Aggregator) SynchronizedCopy(oa export.Aggregator, _ *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
| 	replace := sdk.NewDDSketch(c.cfg) | ||||
|  | ||||
| 	c.lock.Lock() | ||||
| 	c.checkpoint = c.current | ||||
| 	c.current = replace | ||||
| 	o.sketch, c.sketch = c.sketch, replace | ||||
| 	c.lock.Unlock() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update adds the recorded measurement to the current data set. | ||||
| // Update takes a lock to prevent concurrent Update() and Checkpoint() | ||||
| // Update takes a lock to prevent concurrent Update() and SynchronizedCopy() | ||||
| // calls. | ||||
| func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metric.Descriptor) error { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	c.current.Add(number.CoerceToFloat64(desc.NumberKind())) | ||||
| 	c.sketch.Add(number.CoerceToFloat64(desc.NumberKind())) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -131,9 +134,9 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| func (c *Aggregator) Merge(oa export.Aggregator, d *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentMergeError(c, oa) | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	c.checkpoint.Merge(o.checkpoint) | ||||
| 	c.sketch.Merge(o.sketch) | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -15,12 +15,14 @@ | ||||
| package ddsketch | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/test" | ||||
| ) | ||||
|  | ||||
| @@ -29,9 +31,43 @@ const count = 1000 | ||||
| type updateTest struct { | ||||
| } | ||||
|  | ||||
| func new2(desc *metric.Descriptor) (_, _ *Aggregator) { | ||||
| 	alloc := New(2, desc, NewDefaultConfig()) | ||||
| 	return &alloc[0], &alloc[1] | ||||
| } | ||||
|  | ||||
| func new4(desc *metric.Descriptor) (_, _, _, _ *Aggregator) { | ||||
| 	alloc := New(4, desc, NewDefaultConfig()) | ||||
| 	return &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
| } | ||||
|  | ||||
| func checkZero(t *testing.T, agg *Aggregator, desc *metric.Descriptor) { | ||||
| 	kind := desc.NumberKind() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, kind.Zero(), sum) | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, int64(0), count) | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), max) | ||||
|  | ||||
| 	median, err := agg.Quantile(0.5) | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), median) | ||||
|  | ||||
| 	min, err := agg.Min() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), min) | ||||
| } | ||||
|  | ||||
| func (ut *updateTest) run(t *testing.T, profile test.Profile) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
| 	agg := New(descriptor, NewDefaultConfig()) | ||||
| 	agg, ckpt := new2(descriptor) | ||||
|  | ||||
| 	all := test.NewNumbers(profile.NumberKind) | ||||
| 	for i := 0; i < count; i++ { | ||||
| @@ -44,11 +80,14 @@ func (ut *updateTest) run(t *testing.T, profile test.Profile) { | ||||
| 		test.CheckedUpdate(t, agg, y, descriptor) | ||||
| 	} | ||||
|  | ||||
| 	agg.Checkpoint(descriptor) | ||||
| 	err := agg.SynchronizedCopy(ckpt, descriptor) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	checkZero(t, agg, descriptor) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	sum, err := ckpt.Sum() | ||||
| 	require.Nil(t, err) | ||||
| 	allSum := all.Sum() | ||||
| 	require.InDelta(t, | ||||
| @@ -57,18 +96,18 @@ func (ut *updateTest) run(t *testing.T, profile test.Profile) { | ||||
| 		1, | ||||
| 		"Same sum") | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	count, err := ckpt.Count() | ||||
| 	require.Equal(t, all.Count(), count, "Same count") | ||||
| 	require.Nil(t, err) | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	max, err := ckpt.Max() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, | ||||
| 		all.Max(), | ||||
| 		max, | ||||
| 		"Same max") | ||||
|  | ||||
| 	median, err := agg.Quantile(0.5) | ||||
| 	median, err := ckpt.Quantile(0.5) | ||||
| 	require.Nil(t, err) | ||||
| 	allMedian := all.Median() | ||||
| 	require.InDelta(t, | ||||
| @@ -90,8 +129,7 @@ type mergeTest struct { | ||||
| func (mt *mergeTest) run(t *testing.T, profile test.Profile) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 	agg1 := New(descriptor, NewDefaultConfig()) | ||||
| 	agg2 := New(descriptor, NewDefaultConfig()) | ||||
| 	agg1, agg2, ckpt1, ckpt2 := new4(descriptor) | ||||
|  | ||||
| 	all := test.NewNumbers(profile.NumberKind) | ||||
| 	for i := 0; i < count; i++ { | ||||
| @@ -118,14 +156,17 @@ func (mt *mergeTest) run(t *testing.T, profile test.Profile) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	agg1.Checkpoint(descriptor) | ||||
| 	agg2.Checkpoint(descriptor) | ||||
| 	require.NoError(t, agg1.SynchronizedCopy(ckpt1, descriptor)) | ||||
| 	require.NoError(t, agg2.SynchronizedCopy(ckpt2, descriptor)) | ||||
|  | ||||
| 	test.CheckedMerge(t, agg1, agg2, descriptor) | ||||
| 	checkZero(t, agg1, descriptor) | ||||
| 	checkZero(t, agg1, descriptor) | ||||
|  | ||||
| 	test.CheckedMerge(t, ckpt1, ckpt2, descriptor) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	aggSum, err := agg1.Sum() | ||||
| 	aggSum, err := ckpt1.Sum() | ||||
| 	require.Nil(t, err) | ||||
| 	allSum := all.Sum() | ||||
| 	require.InDelta(t, | ||||
| @@ -134,18 +175,18 @@ func (mt *mergeTest) run(t *testing.T, profile test.Profile) { | ||||
| 		1, | ||||
| 		"Same sum") | ||||
|  | ||||
| 	count, err := agg1.Count() | ||||
| 	count, err := ckpt1.Count() | ||||
| 	require.Equal(t, all.Count(), count, "Same count") | ||||
| 	require.Nil(t, err) | ||||
|  | ||||
| 	max, err := agg1.Max() | ||||
| 	max, err := ckpt1.Max() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, | ||||
| 		all.Max(), | ||||
| 		max, | ||||
| 		"Same max") | ||||
|  | ||||
| 	median, err := agg1.Quantile(0.5) | ||||
| 	median, err := ckpt1.Quantile(0.5) | ||||
| 	require.Nil(t, err) | ||||
| 	allMedian := all.Median() | ||||
| 	require.InDelta(t, | ||||
|   | ||||
| @@ -38,7 +38,7 @@ func benchmarkHistogramSearchFloat64(b *testing.B, size int) { | ||||
| 		values[i] = rand.Float64() * inputRange | ||||
| 	} | ||||
| 	desc := test.NewAggregatorTest(metric.ValueRecorderKind, metric.Float64NumberKind) | ||||
| 	agg := histogram.New(desc, boundaries) | ||||
| 	agg := &histogram.New(1, desc, boundaries)[0] | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	b.ReportAllocs() | ||||
| @@ -89,7 +89,7 @@ func benchmarkHistogramSearchInt64(b *testing.B, size int) { | ||||
| 		values[i] = int64(rand.Float64() * inputRange) | ||||
| 	} | ||||
| 	desc := test.NewAggregatorTest(metric.ValueRecorderKind, metric.Int64NumberKind) | ||||
| 	agg := histogram.New(desc, boundaries) | ||||
| 	agg := &histogram.New(1, desc, boundaries)[0] | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	b.ReportAllocs() | ||||
|   | ||||
| @@ -35,10 +35,9 @@ type ( | ||||
| 	// It also calculates the sum and count of all events. | ||||
| 	Aggregator struct { | ||||
| 		lock       sync.Mutex | ||||
| 		current    state | ||||
| 		checkpoint state | ||||
| 		boundaries []float64 | ||||
| 		kind       metric.NumberKind | ||||
| 		state      state | ||||
| 	} | ||||
|  | ||||
| 	// state represents the state of a histogram, consisting of | ||||
| @@ -64,7 +63,9 @@ var _ aggregation.Histogram = &Aggregator{} | ||||
| // Note that this aggregator maintains each value using independent | ||||
| // atomic operations, which introduces the possibility that | ||||
| // checkpoints are inconsistent. | ||||
| func New(desc *metric.Descriptor, boundaries []float64) *Aggregator { | ||||
| func New(cnt int, desc *metric.Descriptor, boundaries []float64) []Aggregator { | ||||
| 	aggs := make([]Aggregator, cnt) | ||||
|  | ||||
| 	// Boundaries MUST be ordered otherwise the histogram could not | ||||
| 	// be properly computed. | ||||
| 	sortedBoundaries := make([]float64, len(boundaries)) | ||||
| @@ -72,12 +73,14 @@ func New(desc *metric.Descriptor, boundaries []float64) *Aggregator { | ||||
| 	copy(sortedBoundaries, boundaries) | ||||
| 	sort.Float64s(sortedBoundaries) | ||||
|  | ||||
| 	return &Aggregator{ | ||||
| 		kind:       desc.NumberKind(), | ||||
| 		boundaries: sortedBoundaries, | ||||
| 		current:    emptyState(sortedBoundaries), | ||||
| 		checkpoint: emptyState(sortedBoundaries), | ||||
| 	for i := range aggs { | ||||
| 		aggs[i] = Aggregator{ | ||||
| 			kind:       desc.NumberKind(), | ||||
| 			boundaries: sortedBoundaries, | ||||
| 			state:      emptyState(sortedBoundaries), | ||||
| 		} | ||||
| 	} | ||||
| 	return aggs | ||||
| } | ||||
|  | ||||
| // Kind returns aggregation.HistogramKind. | ||||
| @@ -87,36 +90,36 @@ func (c *Aggregator) Kind() aggregation.Kind { | ||||
|  | ||||
| // Sum returns the sum of all values in the checkpoint. | ||||
| func (c *Aggregator) Sum() (metric.Number, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	return c.checkpoint.sum, nil | ||||
| 	return c.state.sum, nil | ||||
| } | ||||
|  | ||||
| // Count returns the number of values in the checkpoint. | ||||
| func (c *Aggregator) Count() (int64, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	return int64(c.checkpoint.count), nil | ||||
| 	return int64(c.state.count), nil | ||||
| } | ||||
|  | ||||
| // Histogram returns the count of events in pre-determined buckets. | ||||
| func (c *Aggregator) Histogram() (aggregation.Buckets, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	return aggregation.Buckets{ | ||||
| 		Boundaries: c.boundaries, | ||||
| 		Counts:     c.checkpoint.bucketCounts, | ||||
| 		Counts:     c.state.bucketCounts, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Checkpoint saves the current state and resets the current state to | ||||
| // SynchronizedCopy saves the current state into oa and resets the current state to | ||||
| // the empty set.  Since no locks are taken, there is a chance that | ||||
| // the independent Sum, Count and Bucket Count are not consistent with each | ||||
| // other. | ||||
| func (c *Aggregator) Checkpoint(desc *metric.Descriptor) { | ||||
| func (c *Aggregator) SynchronizedCopy(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	c.lock.Lock() | ||||
| 	c.checkpoint, c.current = c.current, emptyState(c.boundaries) | ||||
| 	o.state, c.state = c.state, emptyState(c.boundaries) | ||||
| 	c.lock.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func emptyState(boundaries []float64) state { | ||||
| @@ -152,9 +155,9 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
|  | ||||
| 	c.current.count.AddInt64(1) | ||||
| 	c.current.sum.AddNumber(kind, number) | ||||
| 	c.current.bucketCounts[bucketID]++ | ||||
| 	c.state.count.AddInt64(1) | ||||
| 	c.state.sum.AddNumber(kind, number) | ||||
| 	c.state.bucketCounts[bucketID]++ | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @@ -163,14 +166,14 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| func (c *Aggregator) Merge(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentMergeError(c, oa) | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	c.checkpoint.sum.AddNumber(desc.NumberKind(), o.checkpoint.sum) | ||||
| 	c.checkpoint.count.AddNumber(metric.Uint64NumberKind, o.checkpoint.count) | ||||
| 	c.state.sum.AddNumber(desc.NumberKind(), o.state.sum) | ||||
| 	c.state.count.AddNumber(metric.Uint64NumberKind, o.state.count) | ||||
|  | ||||
| 	for i := 0; i < len(c.checkpoint.bucketCounts); i++ { | ||||
| 		c.checkpoint.bucketCounts[i] += o.checkpoint.bucketCounts[i] | ||||
| 	for i := 0; i < len(c.state.bucketCounts); i++ { | ||||
| 		c.state.bucketCounts[i] += o.state.bucketCounts[i] | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -60,6 +60,35 @@ var ( | ||||
| 	boundaries = []float64{500, 250, 750} | ||||
| ) | ||||
|  | ||||
| func new2(desc *metric.Descriptor) (_, _ *histogram.Aggregator) { | ||||
| 	alloc := histogram.New(2, desc, boundaries) | ||||
| 	return &alloc[0], &alloc[1] | ||||
| } | ||||
|  | ||||
| func new4(desc *metric.Descriptor) (_, _, _, _ *histogram.Aggregator) { | ||||
| 	alloc := histogram.New(4, desc, boundaries) | ||||
| 	return &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
| } | ||||
|  | ||||
| func checkZero(t *testing.T, agg *histogram.Aggregator, desc *metric.Descriptor) { | ||||
| 	asum, err := agg.Sum() | ||||
| 	require.Equal(t, metric.Number(0), asum, "Empty checkpoint sum = 0") | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	require.Equal(t, int64(0), count, "Empty checkpoint count = 0") | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	buckets, err := agg.Histogram() | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	require.Equal(t, len(buckets.Counts), len(boundaries)+1, "There should be b + 1 counts, where b is the number of boundaries") | ||||
| 	for i, bCount := range buckets.Counts { | ||||
| 		require.Equal(t, uint64(0), uint64(bCount), "Bucket #%d must have 0 observed values", i) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestHistogramAbsolute(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		testHistogram(t, profile, positiveOnly) | ||||
| @@ -82,7 +111,7 @@ func TestHistogramPositiveAndNegative(t *testing.T) { | ||||
| func testHistogram(t *testing.T, profile test.Profile, policy policy) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 	agg := histogram.New(descriptor, boundaries) | ||||
| 	agg, ckpt := new2(descriptor) | ||||
|  | ||||
| 	all := test.NewNumbers(profile.NumberKind) | ||||
|  | ||||
| @@ -92,11 +121,13 @@ func testHistogram(t *testing.T, profile test.Profile, policy policy) { | ||||
| 		test.CheckedUpdate(t, agg, x, descriptor) | ||||
| 	} | ||||
|  | ||||
| 	agg.Checkpoint(descriptor) | ||||
| 	require.NoError(t, agg.SynchronizedCopy(ckpt, descriptor)) | ||||
|  | ||||
| 	checkZero(t, agg, descriptor) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	asum, err := agg.Sum() | ||||
| 	asum, err := ckpt.Sum() | ||||
| 	sum := all.Sum() | ||||
| 	require.InEpsilon(t, | ||||
| 		sum.CoerceToFloat64(profile.NumberKind), | ||||
| @@ -105,11 +136,11 @@ func testHistogram(t *testing.T, profile test.Profile, policy policy) { | ||||
| 		"Same sum - "+policy.name) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	count, err := ckpt.Count() | ||||
| 	require.Equal(t, all.Count(), count, "Same count -"+policy.name) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	buckets, err := agg.Histogram() | ||||
| 	buckets, err := ckpt.Histogram() | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	require.Equal(t, len(buckets.Counts), len(boundaries)+1, "There should be b + 1 counts, where b is the number of boundaries") | ||||
| @@ -125,7 +156,7 @@ func TestHistogramInitial(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 		agg := histogram.New(descriptor, boundaries) | ||||
| 		agg := &histogram.New(1, descriptor, boundaries)[0] | ||||
| 		buckets, err := agg.Histogram() | ||||
|  | ||||
| 		require.NoError(t, err) | ||||
| @@ -138,8 +169,7 @@ func TestHistogramMerge(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 		agg1 := histogram.New(descriptor, boundaries) | ||||
| 		agg2 := histogram.New(descriptor, boundaries) | ||||
| 		agg1, agg2, ckpt1, ckpt2 := new4(descriptor) | ||||
|  | ||||
| 		all := test.NewNumbers(profile.NumberKind) | ||||
|  | ||||
| @@ -154,14 +184,14 @@ func TestHistogramMerge(t *testing.T) { | ||||
| 			test.CheckedUpdate(t, agg2, x, descriptor) | ||||
| 		} | ||||
|  | ||||
| 		agg1.Checkpoint(descriptor) | ||||
| 		agg2.Checkpoint(descriptor) | ||||
| 		require.NoError(t, agg1.SynchronizedCopy(ckpt1, descriptor)) | ||||
| 		require.NoError(t, agg2.SynchronizedCopy(ckpt2, descriptor)) | ||||
|  | ||||
| 		test.CheckedMerge(t, agg1, agg2, descriptor) | ||||
| 		test.CheckedMerge(t, ckpt1, ckpt2, descriptor) | ||||
|  | ||||
| 		all.Sort() | ||||
|  | ||||
| 		asum, err := agg1.Sum() | ||||
| 		asum, err := ckpt1.Sum() | ||||
| 		sum := all.Sum() | ||||
| 		require.InEpsilon(t, | ||||
| 			sum.CoerceToFloat64(profile.NumberKind), | ||||
| @@ -170,11 +200,11 @@ func TestHistogramMerge(t *testing.T) { | ||||
| 			"Same sum - absolute") | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		count, err := agg1.Count() | ||||
| 		count, err := ckpt1.Count() | ||||
| 		require.Equal(t, all.Count(), count, "Same count - absolute") | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		buckets, err := agg1.Histogram() | ||||
| 		buckets, err := ckpt1.Histogram() | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		require.Equal(t, len(buckets.Counts), len(boundaries)+1, "There should be b + 1 counts, where b is the number of boundaries") | ||||
| @@ -191,24 +221,13 @@ func TestHistogramNotSet(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 		agg := histogram.New(descriptor, boundaries) | ||||
| 		agg.Checkpoint(descriptor) | ||||
| 		agg, ckpt := new2(descriptor) | ||||
|  | ||||
| 		asum, err := agg.Sum() | ||||
| 		require.Equal(t, metric.Number(0), asum, "Empty checkpoint sum = 0") | ||||
| 		err := agg.SynchronizedCopy(ckpt, descriptor) | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		count, err := agg.Count() | ||||
| 		require.Equal(t, int64(0), count, "Empty checkpoint count = 0") | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		buckets, err := agg.Histogram() | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		require.Equal(t, len(buckets.Counts), len(boundaries)+1, "There should be b + 1 counts, where b is the number of boundaries") | ||||
| 		for i, bCount := range buckets.Counts { | ||||
| 			require.Equal(t, uint64(0), uint64(bCount), "Bucket #%d must have 0 observed values", i) | ||||
| 		} | ||||
| 		checkZero(t, agg, descriptor) | ||||
| 		checkZero(t, ckpt, descriptor) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -30,11 +30,8 @@ type ( | ||||
|  | ||||
| 	// Aggregator aggregates lastValue events. | ||||
| 	Aggregator struct { | ||||
| 		// current is an atomic pointer to *lastValueData.  It is never nil. | ||||
| 		current unsafe.Pointer | ||||
|  | ||||
| 		// checkpoint is a copy of the current value taken in Checkpoint() | ||||
| 		checkpoint unsafe.Pointer | ||||
| 		// value is an atomic pointer to *lastValueData.  It is never nil. | ||||
| 		value unsafe.Pointer | ||||
| 	} | ||||
|  | ||||
| 	// lastValueData stores the current value of a lastValue along with | ||||
| @@ -61,11 +58,14 @@ var unsetLastValue = &lastValueData{} | ||||
|  | ||||
| // New returns a new lastValue aggregator.  This aggregator retains the | ||||
| // last value and timestamp that were recorded. | ||||
| func New() *Aggregator { | ||||
| 	return &Aggregator{ | ||||
| 		current:    unsafe.Pointer(unsetLastValue), | ||||
| 		checkpoint: unsafe.Pointer(unsetLastValue), | ||||
| func New(cnt int) []Aggregator { | ||||
| 	aggs := make([]Aggregator, cnt) | ||||
| 	for i := range aggs { | ||||
| 		aggs[i] = Aggregator{ | ||||
| 			value: unsafe.Pointer(unsetLastValue), | ||||
| 		} | ||||
| 	} | ||||
| 	return aggs | ||||
| } | ||||
|  | ||||
| // Kind returns aggregation.LastValueKind. | ||||
| @@ -78,16 +78,21 @@ func (g *Aggregator) Kind() aggregation.Kind { | ||||
| // will be returned if (due to a race condition) the checkpoint was | ||||
| // computed before the first value was set. | ||||
| func (g *Aggregator) LastValue() (metric.Number, time.Time, error) { | ||||
| 	gd := (*lastValueData)(g.checkpoint) | ||||
| 	gd := (*lastValueData)(g.value) | ||||
| 	if gd == unsetLastValue { | ||||
| 		return metric.Number(0), time.Time{}, aggregation.ErrNoData | ||||
| 		return 0, time.Time{}, aggregation.ErrNoData | ||||
| 	} | ||||
| 	return gd.value.AsNumber(), gd.timestamp, nil | ||||
| } | ||||
|  | ||||
| // Checkpoint atomically saves the current value. | ||||
| func (g *Aggregator) Checkpoint(*metric.Descriptor) { | ||||
| 	g.checkpoint = atomic.LoadPointer(&g.current) | ||||
| // SynchronizedCopy atomically saves the current value. | ||||
| func (g *Aggregator) SynchronizedCopy(oa export.Aggregator, _ *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentAggregatorError(g, oa) | ||||
| 	} | ||||
| 	o.value = atomic.SwapPointer(&g.value, unsafe.Pointer(unsetLastValue)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update atomically sets the current "last" value. | ||||
| @@ -96,7 +101,7 @@ func (g *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| 		value:     number, | ||||
| 		timestamp: time.Now(), | ||||
| 	} | ||||
| 	atomic.StorePointer(&g.current, unsafe.Pointer(ngd)) | ||||
| 	atomic.StorePointer(&g.value, unsafe.Pointer(ngd)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -105,16 +110,16 @@ func (g *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| func (g *Aggregator) Merge(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentMergeError(g, oa) | ||||
| 		return aggregator.NewInconsistentAggregatorError(g, oa) | ||||
| 	} | ||||
|  | ||||
| 	ggd := (*lastValueData)(atomic.LoadPointer(&g.checkpoint)) | ||||
| 	ogd := (*lastValueData)(atomic.LoadPointer(&o.checkpoint)) | ||||
| 	ggd := (*lastValueData)(atomic.LoadPointer(&g.value)) | ||||
| 	ogd := (*lastValueData)(atomic.LoadPointer(&o.value)) | ||||
|  | ||||
| 	if ggd.timestamp.After(ogd.timestamp) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	g.checkpoint = unsafe.Pointer(ogd) | ||||
| 	g.value = unsafe.Pointer(ogd) | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -15,9 +15,11 @@ | ||||
| package lastvalue | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| @@ -48,9 +50,26 @@ func TestMain(m *testing.M) { | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| func new2() (_, _ *Aggregator) { | ||||
| 	alloc := New(2) | ||||
| 	return &alloc[0], &alloc[1] | ||||
| } | ||||
|  | ||||
| func new4() (_, _, _, _ *Aggregator) { | ||||
| 	alloc := New(4) | ||||
| 	return &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
| } | ||||
|  | ||||
| func checkZero(t *testing.T, agg *Aggregator) { | ||||
| 	lv, ts, err := agg.LastValue() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, time.Time{}, ts) | ||||
| 	require.Equal(t, metric.Number(0), lv) | ||||
| } | ||||
|  | ||||
| func TestLastValueUpdate(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		agg := New() | ||||
| 		agg, ckpt := new2() | ||||
|  | ||||
| 		record := test.NewAggregatorTest(metric.ValueObserverKind, profile.NumberKind) | ||||
|  | ||||
| @@ -61,9 +80,10 @@ func TestLastValueUpdate(t *testing.T) { | ||||
| 			test.CheckedUpdate(t, agg, x, record) | ||||
| 		} | ||||
|  | ||||
| 		agg.Checkpoint(record) | ||||
| 		err := agg.SynchronizedCopy(ckpt, record) | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		lv, _, err := agg.LastValue() | ||||
| 		lv, _, err := ckpt.LastValue() | ||||
| 		require.Equal(t, last, lv, "Same last value - non-monotonic") | ||||
| 		require.Nil(t, err) | ||||
| 	}) | ||||
| @@ -71,8 +91,7 @@ func TestLastValueUpdate(t *testing.T) { | ||||
|  | ||||
| func TestLastValueMerge(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		agg1 := New() | ||||
| 		agg2 := New() | ||||
| 		agg1, agg2, ckpt1, ckpt2 := new4() | ||||
|  | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueObserverKind, profile.NumberKind) | ||||
|  | ||||
| @@ -83,18 +102,21 @@ func TestLastValueMerge(t *testing.T) { | ||||
| 		test.CheckedUpdate(t, agg1, first1, descriptor) | ||||
| 		test.CheckedUpdate(t, agg2, first2, descriptor) | ||||
|  | ||||
| 		agg1.Checkpoint(descriptor) | ||||
| 		agg2.Checkpoint(descriptor) | ||||
| 		require.NoError(t, agg1.SynchronizedCopy(ckpt1, descriptor)) | ||||
| 		require.NoError(t, agg2.SynchronizedCopy(ckpt2, descriptor)) | ||||
|  | ||||
| 		_, t1, err := agg1.LastValue() | ||||
| 		checkZero(t, agg1) | ||||
| 		checkZero(t, agg2) | ||||
|  | ||||
| 		_, t1, err := ckpt1.LastValue() | ||||
| 		require.Nil(t, err) | ||||
| 		_, t2, err := agg2.LastValue() | ||||
| 		_, t2, err := ckpt2.LastValue() | ||||
| 		require.Nil(t, err) | ||||
| 		require.True(t, t1.Before(t2)) | ||||
|  | ||||
| 		test.CheckedMerge(t, agg1, agg2, descriptor) | ||||
| 		test.CheckedMerge(t, ckpt1, ckpt2, descriptor) | ||||
|  | ||||
| 		lv, ts, err := agg1.LastValue() | ||||
| 		lv, ts, err := ckpt1.LastValue() | ||||
| 		require.Nil(t, err) | ||||
| 		require.Equal(t, t2, ts, "Merged timestamp - non-monotonic") | ||||
| 		require.Equal(t, first2, lv, "Merged value - non-monotonic") | ||||
| @@ -104,11 +126,8 @@ func TestLastValueMerge(t *testing.T) { | ||||
| func TestLastValueNotSet(t *testing.T) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueObserverKind, metric.Int64NumberKind) | ||||
|  | ||||
| 	g := New() | ||||
| 	g.Checkpoint(descriptor) | ||||
| 	g, ckpt := new2() | ||||
| 	require.NoError(t, g.SynchronizedCopy(ckpt, descriptor)) | ||||
|  | ||||
| 	value, timestamp, err := g.LastValue() | ||||
| 	require.Equal(t, aggregation.ErrNoData, err) | ||||
| 	require.True(t, timestamp.IsZero()) | ||||
| 	require.Equal(t, metric.Number(0), value) | ||||
| 	checkZero(t, g) | ||||
| } | ||||
|   | ||||
| @@ -28,17 +28,16 @@ type ( | ||||
| 	// Aggregator aggregates events that form a distribution, | ||||
| 	// keeping only the min, max, sum, and count. | ||||
| 	Aggregator struct { | ||||
| 		lock       sync.Mutex | ||||
| 		current    state | ||||
| 		checkpoint state | ||||
| 		kind       metric.NumberKind | ||||
| 		lock sync.Mutex | ||||
| 		kind metric.NumberKind | ||||
| 		state | ||||
| 	} | ||||
|  | ||||
| 	state struct { | ||||
| 		count metric.Number | ||||
| 		sum   metric.Number | ||||
| 		min   metric.Number | ||||
| 		max   metric.Number | ||||
| 		count int64 | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| @@ -49,18 +48,17 @@ var _ aggregation.MinMaxSumCount = &Aggregator{} | ||||
| // count.  It does not compute quantile information other than Min and | ||||
| // Max. | ||||
| // | ||||
| // This type uses a mutex for Update() and Checkpoint() concurrency. | ||||
| func New(desc *metric.Descriptor) *Aggregator { | ||||
| // This type uses a mutex for Update() and SynchronizedCopy() concurrency. | ||||
| func New(cnt int, desc *metric.Descriptor) []Aggregator { | ||||
| 	kind := desc.NumberKind() | ||||
| 	return &Aggregator{ | ||||
| 		kind: kind, | ||||
| 		current: state{ | ||||
| 			count: metric.NewUint64Number(0), | ||||
| 			sum:   kind.Zero(), | ||||
| 			min:   kind.Maximum(), | ||||
| 			max:   kind.Minimum(), | ||||
| 		}, | ||||
| 	aggs := make([]Aggregator, cnt) | ||||
| 	for i := range aggs { | ||||
| 		aggs[i] = Aggregator{ | ||||
| 			kind:  kind, | ||||
| 			state: emptyState(kind), | ||||
| 		} | ||||
| 	} | ||||
| 	return aggs | ||||
| } | ||||
|  | ||||
| // Kind returns aggregation.MinMaxSumCountKind. | ||||
| @@ -70,55 +68,56 @@ func (c *Aggregator) Kind() aggregation.Kind { | ||||
|  | ||||
| // Sum returns the sum of values in the checkpoint. | ||||
| func (c *Aggregator) Sum() (metric.Number, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	return c.checkpoint.sum, nil | ||||
| 	return c.sum, nil | ||||
| } | ||||
|  | ||||
| // Count returns the number of values in the checkpoint. | ||||
| func (c *Aggregator) Count() (int64, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	return c.checkpoint.count.CoerceToInt64(metric.Uint64NumberKind), nil | ||||
| 	return c.count, nil | ||||
| } | ||||
|  | ||||
| // Min returns the minimum value in the checkpoint. | ||||
| // The error value aggregation.ErrNoData will be returned | ||||
| // if there were no measurements recorded during the checkpoint. | ||||
| func (c *Aggregator) Min() (metric.Number, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	if c.checkpoint.count.IsZero(metric.Uint64NumberKind) { | ||||
| 		return c.kind.Zero(), aggregation.ErrNoData | ||||
| 	if c.count == 0 { | ||||
| 		return 0, aggregation.ErrNoData | ||||
| 	} | ||||
| 	return c.checkpoint.min, nil | ||||
| 	return c.min, nil | ||||
| } | ||||
|  | ||||
| // Max returns the maximum value in the checkpoint. | ||||
| // The error value aggregation.ErrNoData will be returned | ||||
| // if there were no measurements recorded during the checkpoint. | ||||
| func (c *Aggregator) Max() (metric.Number, error) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	if c.checkpoint.count.IsZero(metric.Uint64NumberKind) { | ||||
| 		return c.kind.Zero(), aggregation.ErrNoData | ||||
| 	if c.count == 0 { | ||||
| 		return 0, aggregation.ErrNoData | ||||
| 	} | ||||
| 	return c.checkpoint.max, nil | ||||
| 	return c.max, nil | ||||
| } | ||||
|  | ||||
| // Checkpoint saves the current state and resets the current state to | ||||
| // SynchronizedCopy saves the current state into oa and resets the current state to | ||||
| // the empty set. | ||||
| func (c *Aggregator) Checkpoint(desc *metric.Descriptor) { | ||||
| func (c *Aggregator) SynchronizedCopy(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	// TODO: It is incorrect to use an Aggregator of different | ||||
| 	// kind. Should we test that o.kind == c.kind?  (The same question | ||||
| 	// occurs for several of the other aggregators in ../*.) | ||||
| 	c.lock.Lock() | ||||
| 	c.checkpoint, c.current = c.current, c.emptyState() | ||||
| 	o.state, c.state = c.state, emptyState(c.kind) | ||||
| 	c.lock.Unlock() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *Aggregator) emptyState() state { | ||||
| 	kind := c.kind | ||||
| func emptyState(kind metric.NumberKind) state { | ||||
| 	return state{ | ||||
| 		count: metric.NewUint64Number(0), | ||||
| 		sum:   kind.Zero(), | ||||
| 		count: 0, | ||||
| 		sum:   0, | ||||
| 		min:   kind.Maximum(), | ||||
| 		max:   kind.Minimum(), | ||||
| 	} | ||||
| @@ -130,13 +129,13 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
|  | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	c.current.count.AddInt64(1) | ||||
| 	c.current.sum.AddNumber(kind, number) | ||||
| 	if number.CompareNumber(kind, c.current.min) < 0 { | ||||
| 		c.current.min = number | ||||
| 	c.count++ | ||||
| 	c.sum.AddNumber(kind, number) | ||||
| 	if number.CompareNumber(kind, c.min) < 0 { | ||||
| 		c.min = number | ||||
| 	} | ||||
| 	if number.CompareNumber(kind, c.current.max) > 0 { | ||||
| 		c.current.max = number | ||||
| 	if number.CompareNumber(kind, c.max) > 0 { | ||||
| 		c.max = number | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -145,17 +144,17 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| func (c *Aggregator) Merge(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentMergeError(c, oa) | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
|  | ||||
| 	c.checkpoint.count.AddNumber(metric.Uint64NumberKind, o.checkpoint.count) | ||||
| 	c.checkpoint.sum.AddNumber(desc.NumberKind(), o.checkpoint.sum) | ||||
| 	c.count += o.count | ||||
| 	c.sum.AddNumber(desc.NumberKind(), o.sum) | ||||
|  | ||||
| 	if c.checkpoint.min.CompareNumber(desc.NumberKind(), o.checkpoint.min) > 0 { | ||||
| 		c.checkpoint.min.SetNumber(o.checkpoint.min) | ||||
| 	if c.min.CompareNumber(desc.NumberKind(), o.min) > 0 { | ||||
| 		c.min.SetNumber(o.min) | ||||
| 	} | ||||
| 	if c.checkpoint.max.CompareNumber(desc.NumberKind(), o.checkpoint.max) < 0 { | ||||
| 		c.checkpoint.max.SetNumber(o.checkpoint.max) | ||||
| 	if c.max.CompareNumber(desc.NumberKind(), o.max) < 0 { | ||||
| 		c.max.SetNumber(o.max) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
| package minmaxsumcount | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 	"testing" | ||||
| @@ -75,11 +76,41 @@ func TestMinMaxSumCountPositiveAndNegative(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func new2(desc *metric.Descriptor) (_, _ *Aggregator) { | ||||
| 	alloc := New(2, desc) | ||||
| 	return &alloc[0], &alloc[1] | ||||
| } | ||||
|  | ||||
| func new4(desc *metric.Descriptor) (_, _, _, _ *Aggregator) { | ||||
| 	alloc := New(4, desc) | ||||
| 	return &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
| } | ||||
|  | ||||
| func checkZero(t *testing.T, agg *Aggregator, desc *metric.Descriptor) { | ||||
| 	kind := desc.NumberKind() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, kind.Zero(), sum) | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, int64(0), count) | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), max) | ||||
|  | ||||
| 	min, err := agg.Min() | ||||
| 	require.True(t, errors.Is(err, aggregation.ErrNoData)) | ||||
| 	require.Equal(t, kind.Zero(), min) | ||||
| } | ||||
|  | ||||
| // Validates min, max, sum and count for a given profile and policy | ||||
| func minMaxSumCount(t *testing.T, profile test.Profile, policy policy) { | ||||
| 	descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 	agg := New(descriptor) | ||||
| 	agg, ckpt := new2(descriptor) | ||||
|  | ||||
| 	all := test.NewNumbers(profile.NumberKind) | ||||
|  | ||||
| @@ -89,11 +120,13 @@ func minMaxSumCount(t *testing.T, profile test.Profile, policy policy) { | ||||
| 		test.CheckedUpdate(t, agg, x, descriptor) | ||||
| 	} | ||||
|  | ||||
| 	agg.Checkpoint(descriptor) | ||||
| 	require.NoError(t, agg.SynchronizedCopy(ckpt, descriptor)) | ||||
|  | ||||
| 	checkZero(t, agg, descriptor) | ||||
|  | ||||
| 	all.Sort() | ||||
|  | ||||
| 	aggSum, err := agg.Sum() | ||||
| 	aggSum, err := ckpt.Sum() | ||||
| 	require.Nil(t, err) | ||||
| 	allSum := all.Sum() | ||||
| 	require.InEpsilon(t, | ||||
| @@ -102,18 +135,18 @@ func minMaxSumCount(t *testing.T, profile test.Profile, policy policy) { | ||||
| 		0.000000001, | ||||
| 		"Same sum - "+policy.name) | ||||
|  | ||||
| 	count, err := agg.Count() | ||||
| 	count, err := ckpt.Count() | ||||
| 	require.Equal(t, all.Count(), count, "Same count -"+policy.name) | ||||
| 	require.Nil(t, err) | ||||
|  | ||||
| 	min, err := agg.Min() | ||||
| 	min, err := ckpt.Min() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, | ||||
| 		all.Min(), | ||||
| 		min, | ||||
| 		"Same min -"+policy.name) | ||||
|  | ||||
| 	max, err := agg.Max() | ||||
| 	max, err := ckpt.Max() | ||||
| 	require.Nil(t, err) | ||||
| 	require.Equal(t, | ||||
| 		all.Max(), | ||||
| @@ -125,8 +158,7 @@ func TestMinMaxSumCountMerge(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 		agg1 := New(descriptor) | ||||
| 		agg2 := New(descriptor) | ||||
| 		agg1, agg2, ckpt1, ckpt2 := new4(descriptor) | ||||
|  | ||||
| 		all := test.NewNumbers(profile.NumberKind) | ||||
|  | ||||
| @@ -141,14 +173,17 @@ func TestMinMaxSumCountMerge(t *testing.T) { | ||||
| 			test.CheckedUpdate(t, agg2, x, descriptor) | ||||
| 		} | ||||
|  | ||||
| 		agg1.Checkpoint(descriptor) | ||||
| 		agg2.Checkpoint(descriptor) | ||||
| 		require.NoError(t, agg1.SynchronizedCopy(ckpt1, descriptor)) | ||||
| 		require.NoError(t, agg2.SynchronizedCopy(ckpt2, descriptor)) | ||||
|  | ||||
| 		test.CheckedMerge(t, agg1, agg2, descriptor) | ||||
| 		checkZero(t, agg1, descriptor) | ||||
| 		checkZero(t, agg2, descriptor) | ||||
|  | ||||
| 		test.CheckedMerge(t, ckpt1, ckpt2, descriptor) | ||||
|  | ||||
| 		all.Sort() | ||||
|  | ||||
| 		aggSum, err := agg1.Sum() | ||||
| 		aggSum, err := ckpt1.Sum() | ||||
| 		require.Nil(t, err) | ||||
| 		allSum := all.Sum() | ||||
| 		require.InEpsilon(t, | ||||
| @@ -157,18 +192,18 @@ func TestMinMaxSumCountMerge(t *testing.T) { | ||||
| 			0.000000001, | ||||
| 			"Same sum - absolute") | ||||
|  | ||||
| 		count, err := agg1.Count() | ||||
| 		count, err := ckpt1.Count() | ||||
| 		require.Equal(t, all.Count(), count, "Same count - absolute") | ||||
| 		require.Nil(t, err) | ||||
|  | ||||
| 		min, err := agg1.Min() | ||||
| 		min, err := ckpt1.Min() | ||||
| 		require.Nil(t, err) | ||||
| 		require.Equal(t, | ||||
| 			all.Min(), | ||||
| 			min, | ||||
| 			"Same min - absolute") | ||||
|  | ||||
| 		max, err := agg1.Max() | ||||
| 		max, err := ckpt1.Max() | ||||
| 		require.Nil(t, err) | ||||
| 		require.Equal(t, | ||||
| 			all.Max(), | ||||
| @@ -181,18 +216,20 @@ func TestMaxSumCountNotSet(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| 		agg := New(descriptor) | ||||
| 		agg.Checkpoint(descriptor) | ||||
| 		alloc := New(2, descriptor) | ||||
| 		agg, ckpt := &alloc[0], &alloc[1] | ||||
|  | ||||
| 		asum, err := agg.Sum() | ||||
| 		require.NoError(t, agg.SynchronizedCopy(ckpt, descriptor)) | ||||
|  | ||||
| 		asum, err := ckpt.Sum() | ||||
| 		require.Equal(t, metric.Number(0), asum, "Empty checkpoint sum = 0") | ||||
| 		require.Nil(t, err) | ||||
|  | ||||
| 		count, err := agg.Count() | ||||
| 		count, err := ckpt.Count() | ||||
| 		require.Equal(t, int64(0), count, "Empty checkpoint count = 0") | ||||
| 		require.Nil(t, err) | ||||
|  | ||||
| 		max, err := agg.Max() | ||||
| 		max, err := ckpt.Max() | ||||
| 		require.Equal(t, aggregation.ErrNoData, err) | ||||
| 		require.Equal(t, metric.Number(0), max) | ||||
| 	}) | ||||
|   | ||||
| @@ -27,11 +27,7 @@ import ( | ||||
| type Aggregator struct { | ||||
| 	// current holds current increments to this counter record | ||||
| 	// current needs to be aligned for 64-bit atomic operations. | ||||
| 	current metric.Number | ||||
|  | ||||
| 	// checkpoint is a temporary used during Checkpoint() | ||||
| 	// checkpoint needs to be aligned for 64-bit atomic operations. | ||||
| 	checkpoint metric.Number | ||||
| 	value metric.Number | ||||
| } | ||||
|  | ||||
| var _ export.Aggregator = &Aggregator{} | ||||
| @@ -40,8 +36,8 @@ var _ aggregation.Sum = &Aggregator{} | ||||
| // New returns a new counter aggregator implemented by atomic | ||||
| // operations.  This aggregator implements the aggregation.Sum | ||||
| // export interface. | ||||
| func New() *Aggregator { | ||||
| 	return &Aggregator{} | ||||
| func New(cnt int) []Aggregator { | ||||
| 	return make([]Aggregator, cnt) | ||||
| } | ||||
|  | ||||
| // Kind returns aggregation.SumKind. | ||||
| @@ -52,18 +48,23 @@ func (c *Aggregator) Kind() aggregation.Kind { | ||||
| // Sum returns the last-checkpointed sum.  This will never return an | ||||
| // error. | ||||
| func (c *Aggregator) Sum() (metric.Number, error) { | ||||
| 	return c.checkpoint, nil | ||||
| 	return c.value, nil | ||||
| } | ||||
|  | ||||
| // Checkpoint atomically saves the current value and resets the | ||||
| // SynchronizedCopy atomically saves the current value into oa and resets the | ||||
| // current sum to zero. | ||||
| func (c *Aggregator) Checkpoint(*metric.Descriptor) { | ||||
| 	c.checkpoint = c.current.SwapNumberAtomic(metric.Number(0)) | ||||
| func (c *Aggregator) SynchronizedCopy(oa export.Aggregator, _ *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
| 	o.value = c.value.SwapNumberAtomic(metric.Number(0)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update atomically adds to the current value. | ||||
| func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metric.Descriptor) error { | ||||
| 	c.current.AddNumberAtomic(desc.NumberKind(), number) | ||||
| 	c.value.AddNumberAtomic(desc.NumberKind(), number) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -71,8 +72,8 @@ func (c *Aggregator) Update(_ context.Context, number metric.Number, desc *metri | ||||
| func (c *Aggregator) Merge(oa export.Aggregator, desc *metric.Descriptor) error { | ||||
| 	o, _ := oa.(*Aggregator) | ||||
| 	if o == nil { | ||||
| 		return aggregator.NewInconsistentMergeError(c, oa) | ||||
| 		return aggregator.NewInconsistentAggregatorError(c, oa) | ||||
| 	} | ||||
| 	c.checkpoint.AddNumber(desc.NumberKind(), o.checkpoint) | ||||
| 	c.value.AddNumber(desc.NumberKind(), o.value) | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -32,12 +32,8 @@ const count = 100 | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "Aggregator.current", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.current), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "Aggregator.checkpoint", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.checkpoint), | ||||
| 			Name:   "Aggregator.value", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.value), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| @@ -47,9 +43,27 @@ func TestMain(m *testing.M) { | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| func new2() (_, _ *Aggregator) { | ||||
| 	alloc := New(2) | ||||
| 	return &alloc[0], &alloc[1] | ||||
| } | ||||
|  | ||||
| func new4() (_, _, _, _ *Aggregator) { | ||||
| 	alloc := New(4) | ||||
| 	return &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
| } | ||||
|  | ||||
| func checkZero(t *testing.T, agg *Aggregator, desc *metric.Descriptor) { | ||||
| 	kind := desc.NumberKind() | ||||
|  | ||||
| 	sum, err := agg.Sum() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, kind.Zero(), sum) | ||||
| } | ||||
|  | ||||
| func TestCounterSum(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		agg := New() | ||||
| 		agg, ckpt := new2() | ||||
|  | ||||
| 		descriptor := test.NewAggregatorTest(metric.CounterKind, profile.NumberKind) | ||||
|  | ||||
| @@ -60,9 +74,12 @@ func TestCounterSum(t *testing.T) { | ||||
| 			test.CheckedUpdate(t, agg, x, descriptor) | ||||
| 		} | ||||
|  | ||||
| 		agg.Checkpoint(descriptor) | ||||
| 		err := agg.SynchronizedCopy(ckpt, descriptor) | ||||
| 		require.NoError(t, err) | ||||
|  | ||||
| 		asum, err := agg.Sum() | ||||
| 		checkZero(t, agg, descriptor) | ||||
|  | ||||
| 		asum, err := ckpt.Sum() | ||||
| 		require.Equal(t, sum, asum, "Same sum - monotonic") | ||||
| 		require.Nil(t, err) | ||||
| 	}) | ||||
| @@ -70,7 +87,7 @@ func TestCounterSum(t *testing.T) { | ||||
|  | ||||
| func TestValueRecorderSum(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		agg := New() | ||||
| 		agg, ckpt := new2() | ||||
|  | ||||
| 		descriptor := test.NewAggregatorTest(metric.ValueRecorderKind, profile.NumberKind) | ||||
|  | ||||
| @@ -85,9 +102,10 @@ func TestValueRecorderSum(t *testing.T) { | ||||
| 			sum.AddNumber(profile.NumberKind, r2) | ||||
| 		} | ||||
|  | ||||
| 		agg.Checkpoint(descriptor) | ||||
| 		require.NoError(t, agg.SynchronizedCopy(ckpt, descriptor)) | ||||
| 		checkZero(t, agg, descriptor) | ||||
|  | ||||
| 		asum, err := agg.Sum() | ||||
| 		asum, err := ckpt.Sum() | ||||
| 		require.Equal(t, sum, asum, "Same sum - monotonic") | ||||
| 		require.Nil(t, err) | ||||
| 	}) | ||||
| @@ -95,8 +113,7 @@ func TestValueRecorderSum(t *testing.T) { | ||||
|  | ||||
| func TestCounterMerge(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		agg1 := New() | ||||
| 		agg2 := New() | ||||
| 		agg1, agg2, ckpt1, ckpt2 := new4() | ||||
|  | ||||
| 		descriptor := test.NewAggregatorTest(metric.CounterKind, profile.NumberKind) | ||||
|  | ||||
| @@ -108,14 +125,17 @@ func TestCounterMerge(t *testing.T) { | ||||
| 			test.CheckedUpdate(t, agg2, x, descriptor) | ||||
| 		} | ||||
|  | ||||
| 		agg1.Checkpoint(descriptor) | ||||
| 		agg2.Checkpoint(descriptor) | ||||
| 		require.NoError(t, agg1.SynchronizedCopy(ckpt1, descriptor)) | ||||
| 		require.NoError(t, agg2.SynchronizedCopy(ckpt2, descriptor)) | ||||
|  | ||||
| 		test.CheckedMerge(t, agg1, agg2, descriptor) | ||||
| 		checkZero(t, agg1, descriptor) | ||||
| 		checkZero(t, agg2, descriptor) | ||||
|  | ||||
| 		test.CheckedMerge(t, ckpt1, ckpt2, descriptor) | ||||
|  | ||||
| 		sum.AddNumber(descriptor.NumberKind(), sum) | ||||
|  | ||||
| 		asum, err := agg1.Sum() | ||||
| 		asum, err := ckpt1.Sum() | ||||
| 		require.Equal(t, sum, asum, "Same sum - monotonic") | ||||
| 		require.Nil(t, err) | ||||
| 	}) | ||||
|   | ||||
| @@ -18,7 +18,6 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| @@ -26,22 +25,21 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	sdk "go.opentelemetry.io/otel/sdk/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| ) | ||||
|  | ||||
| type benchFixture struct { | ||||
| 	meter       metric.MeterMust | ||||
| 	accumulator *sdk.Accumulator | ||||
| 	B           *testing.B | ||||
| 	export.AggregationSelector | ||||
| } | ||||
|  | ||||
| func newFixture(b *testing.B) *benchFixture { | ||||
| 	b.ReportAllocs() | ||||
| 	bf := &benchFixture{ | ||||
| 		B: b, | ||||
| 		B:                   b, | ||||
| 		AggregationSelector: test.AggregationSelector(), | ||||
| 	} | ||||
|  | ||||
| 	bf.accumulator = sdk.NewAccumulator(bf) | ||||
| @@ -49,25 +47,6 @@ func newFixture(b *testing.B) *benchFixture { | ||||
| 	return bf | ||||
| } | ||||
|  | ||||
| func (*benchFixture) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| 	name := descriptor.Name() | ||||
| 	switch { | ||||
| 	case strings.HasSuffix(name, "counter"): | ||||
| 		return sum.New() | ||||
| 	case strings.HasSuffix(name, "lastvalue"): | ||||
| 		return lastvalue.New() | ||||
| 	default: | ||||
| 		if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") { | ||||
| 			return minmaxsumcount.New(descriptor) | ||||
| 		} else if strings.HasSuffix(descriptor.Name(), "ddsketch") { | ||||
| 			return ddsketch.New(descriptor, ddsketch.NewDefaultConfig()) | ||||
| 		} else if strings.HasSuffix(descriptor.Name(), "array") { | ||||
| 			return ddsketch.New(descriptor, ddsketch.NewDefaultConfig()) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *benchFixture) Process(rec export.Record) error { | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -28,12 +28,13 @@ import ( | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| 	"go.opentelemetry.io/otel/api/label" | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	"go.opentelemetry.io/otel/exporters/metric/test" | ||||
| 	exporterTest "go.opentelemetry.io/otel/exporters/metric/test" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/controller/push" | ||||
| 	controllerTest "go.opentelemetry.io/otel/sdk/metric/controller/test" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| 	integratorTest "go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| 	"go.opentelemetry.io/otel/sdk/resource" | ||||
| ) | ||||
|  | ||||
| @@ -74,14 +75,12 @@ type testExporter struct { | ||||
| } | ||||
|  | ||||
| type testFixture struct { | ||||
| 	checkpointSet *test.CheckpointSet | ||||
| 	checkpointSet *exporterTest.CheckpointSet | ||||
| 	exporter      *testExporter | ||||
| } | ||||
|  | ||||
| type testSelector struct{} | ||||
|  | ||||
| func newFixture(t *testing.T) testFixture { | ||||
| 	checkpointSet := test.NewCheckpointSet(testResource) | ||||
| 	checkpointSet := exporterTest.NewCheckpointSet(testResource) | ||||
|  | ||||
| 	exporter := &testExporter{ | ||||
| 		t: t, | ||||
| @@ -92,10 +91,6 @@ func newFixture(t *testing.T) testFixture { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (testSelector) AggregatorFor(*metric.Descriptor) export.Aggregator { | ||||
| 	return sum.New() | ||||
| } | ||||
|  | ||||
| func (e *testExporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error { | ||||
| 	e.lock.Lock() | ||||
| 	defer e.lock.Unlock() | ||||
| @@ -126,7 +121,7 @@ func (e *testExporter) resetRecords() ([]export.Record, int) { | ||||
|  | ||||
| func TestPushDoubleStop(t *testing.T) { | ||||
| 	fix := newFixture(t) | ||||
| 	p := push.New(testSelector{}, fix.exporter) | ||||
| 	p := push.New(integratorTest.AggregationSelector(), fix.exporter) | ||||
| 	p.Start() | ||||
| 	p.Stop() | ||||
| 	p.Stop() | ||||
| @@ -134,7 +129,7 @@ func TestPushDoubleStop(t *testing.T) { | ||||
|  | ||||
| func TestPushDoubleStart(t *testing.T) { | ||||
| 	fix := newFixture(t) | ||||
| 	p := push.New(testSelector{}, fix.exporter) | ||||
| 	p := push.New(test.AggregationSelector(), fix.exporter) | ||||
| 	p.Start() | ||||
| 	p.Start() | ||||
| 	p.Stop() | ||||
| @@ -144,7 +139,7 @@ func TestPushTicker(t *testing.T) { | ||||
| 	fix := newFixture(t) | ||||
|  | ||||
| 	p := push.New( | ||||
| 		testSelector{}, | ||||
| 		test.AggregationSelector(), | ||||
| 		fix.exporter, | ||||
| 		push.WithPeriod(time.Second), | ||||
| 		push.WithResource(testResource), | ||||
| @@ -156,7 +151,7 @@ func TestPushTicker(t *testing.T) { | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	counter := metric.Must(meter).NewInt64Counter("counter") | ||||
| 	counter := metric.Must(meter).NewInt64Counter("counter.sum") | ||||
|  | ||||
| 	p.Start() | ||||
|  | ||||
| @@ -172,7 +167,7 @@ func TestPushTicker(t *testing.T) { | ||||
| 	records, exports = fix.exporter.resetRecords() | ||||
| 	require.Equal(t, 1, exports) | ||||
| 	require.Equal(t, 1, len(records)) | ||||
| 	require.Equal(t, "counter", records[0].Descriptor().Name()) | ||||
| 	require.Equal(t, "counter.sum", records[0].Descriptor().Name()) | ||||
| 	require.Equal(t, "R=V", records[0].Resource().Encoded(label.DefaultEncoder())) | ||||
|  | ||||
| 	sum, err := records[0].Aggregator().(aggregation.Sum).Sum() | ||||
| @@ -189,7 +184,7 @@ func TestPushTicker(t *testing.T) { | ||||
| 	records, exports = fix.exporter.resetRecords() | ||||
| 	require.Equal(t, 2, exports) | ||||
| 	require.Equal(t, 1, len(records)) | ||||
| 	require.Equal(t, "counter", records[0].Descriptor().Name()) | ||||
| 	require.Equal(t, "counter.sum", records[0].Descriptor().Name()) | ||||
| 	require.Equal(t, "R=V", records[0].Resource().Encoded(label.DefaultEncoder())) | ||||
|  | ||||
| 	sum, err = records[0].Aggregator().(aggregation.Sum).Sum() | ||||
| @@ -215,17 +210,17 @@ func TestPushExportError(t *testing.T) { | ||||
| 		expectedDescriptors []string | ||||
| 		expectedError       error | ||||
| 	}{ | ||||
| 		{"errNone", nil, []string{"counter1{R=V,X=Y}", "counter2{R=V,}"}, nil}, | ||||
| 		{"errNoData", aggregation.ErrNoData, []string{"counter2{R=V,}"}, nil}, | ||||
| 		{"errNone", nil, []string{"counter1.sum{R=V,X=Y}", "counter2.sum{R=V,}"}, nil}, | ||||
| 		{"errNoData", aggregation.ErrNoData, []string{"counter2.sum{R=V,}"}, nil}, | ||||
| 		{"errUnexpected", errAggregator, []string{}, errAggregator}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			fix := newFixture(t) | ||||
| 			fix.exporter.injectErr = injector("counter1", tt.injectedError) | ||||
| 			fix.exporter.injectErr = injector("counter1.sum", tt.injectedError) | ||||
|  | ||||
| 			p := push.New( | ||||
| 				testSelector{}, | ||||
| 				test.AggregationSelector(), | ||||
| 				fix.exporter, | ||||
| 				push.WithPeriod(time.Second), | ||||
| 				push.WithResource(testResource), | ||||
| @@ -237,8 +232,8 @@ func TestPushExportError(t *testing.T) { | ||||
| 			ctx := context.Background() | ||||
|  | ||||
| 			meter := p.Provider().Meter("name") | ||||
| 			counter1 := metric.Must(meter).NewInt64Counter("counter1") | ||||
| 			counter2 := metric.Must(meter).NewInt64Counter("counter2") | ||||
| 			counter1 := metric.Must(meter).NewInt64Counter("counter1.sum") | ||||
| 			counter2 := metric.Must(meter).NewInt64Counter("counter2.sum") | ||||
|  | ||||
| 			p.Start() | ||||
| 			runtime.Gosched() | ||||
|   | ||||
| @@ -18,9 +18,7 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| @@ -32,8 +30,7 @@ import ( | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	metricsdk "go.opentelemetry.io/otel/sdk/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/array" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| 	batchTest "go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| 	"go.opentelemetry.io/otel/sdk/resource" | ||||
| ) | ||||
| @@ -74,17 +71,27 @@ func init() { | ||||
| } | ||||
|  | ||||
| type correctnessIntegrator struct { | ||||
| 	newAggCount int64 | ||||
|  | ||||
| 	t *testing.T | ||||
| 	*testSelector | ||||
|  | ||||
| 	records []export.Record | ||||
| } | ||||
|  | ||||
| type testSelector struct { | ||||
| 	selector    export.AggregationSelector | ||||
| 	newAggCount int | ||||
| } | ||||
|  | ||||
| func (ts *testSelector) AggregatorFor(desc *metric.Descriptor, aggPtrs ...*export.Aggregator) { | ||||
| 	ts.newAggCount += len(aggPtrs) | ||||
| 	test.AggregationSelector().AggregatorFor(desc, aggPtrs...) | ||||
| } | ||||
|  | ||||
| func newSDK(t *testing.T) (metric.Meter, *metricsdk.Accumulator, *correctnessIntegrator) { | ||||
| 	testHandler.Reset() | ||||
| 	integrator := &correctnessIntegrator{ | ||||
| 		t: t, | ||||
| 		t:            t, | ||||
| 		testSelector: &testSelector{selector: test.AggregationSelector()}, | ||||
| 	} | ||||
| 	accum := metricsdk.NewAccumulator( | ||||
| 		integrator, | ||||
| @@ -94,23 +101,6 @@ func newSDK(t *testing.T) (metric.Meter, *metricsdk.Accumulator, *correctnessInt | ||||
| 	return meter, accum, integrator | ||||
| } | ||||
|  | ||||
| func (ci *correctnessIntegrator) AggregatorFor(descriptor *metric.Descriptor) (agg export.Aggregator) { | ||||
| 	name := descriptor.Name() | ||||
|  | ||||
| 	switch { | ||||
| 	case strings.HasSuffix(name, ".counter"): | ||||
| 		agg = sum.New() | ||||
| 	case strings.HasSuffix(name, ".disabled"): | ||||
| 		agg = nil | ||||
| 	default: | ||||
| 		agg = array.New() | ||||
| 	} | ||||
| 	if agg != nil { | ||||
| 		atomic.AddInt64(&ci.newAggCount, 1) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (ci *correctnessIntegrator) CheckpointSet() export.CheckpointSet { | ||||
| 	ci.t.Fatal("Should not be called") | ||||
| 	return nil | ||||
| @@ -128,7 +118,7 @@ func TestInputRangeCounter(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	counter := Must(meter).NewInt64Counter("name.counter") | ||||
| 	counter := Must(meter).NewInt64Counter("name.sum") | ||||
|  | ||||
| 	counter.Add(ctx, -1) | ||||
| 	require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) | ||||
| @@ -150,7 +140,7 @@ func TestInputRangeUpDownCounter(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	counter := Must(meter).NewInt64UpDownCounter("name.updowncounter") | ||||
| 	counter := Must(meter).NewInt64UpDownCounter("name.sum") | ||||
|  | ||||
| 	counter.Add(ctx, -1) | ||||
| 	counter.Add(ctx, -1) | ||||
| @@ -169,7 +159,7 @@ func TestInputRangeValueRecorder(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	valuerecorder := Must(meter).NewFloat64ValueRecorder("name.valuerecorder") | ||||
| 	valuerecorder := Must(meter).NewFloat64ValueRecorder("name.exact") | ||||
|  | ||||
| 	valuerecorder.Record(ctx, math.NaN()) | ||||
| 	require.Equal(t, aggregation.ErrNaNInput, testHandler.Flush()) | ||||
| @@ -207,7 +197,7 @@ func TestRecordNaN(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, _, _ := newSDK(t) | ||||
|  | ||||
| 	c := Must(meter).NewFloat64Counter("sum.name") | ||||
| 	c := Must(meter).NewFloat64Counter("name.sum") | ||||
|  | ||||
| 	require.Nil(t, testHandler.Flush()) | ||||
| 	c.Add(ctx, math.NaN()) | ||||
| @@ -218,7 +208,7 @@ func TestSDKLabelsDeduplication(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	counter := Must(meter).NewInt64Counter("counter") | ||||
| 	counter := Must(meter).NewInt64Counter("name.sum") | ||||
|  | ||||
| 	const ( | ||||
| 		maxKeys = 21 | ||||
| @@ -317,13 +307,13 @@ func TestObserverCollection(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	_ = Must(meter).NewFloat64ValueObserver("float.valueobserver", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 	_ = Must(meter).NewFloat64ValueObserver("float.valueobserver.lastvalue", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 		result.Observe(1, kv.String("A", "B")) | ||||
| 		// last value wins | ||||
| 		result.Observe(-1, kv.String("A", "B")) | ||||
| 		result.Observe(-1, kv.String("C", "D")) | ||||
| 	}) | ||||
| 	_ = Must(meter).NewInt64ValueObserver("int.valueobserver", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 	_ = Must(meter).NewInt64ValueObserver("int.valueobserver.lastvalue", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 		result.Observe(-1, kv.String("A", "B")) | ||||
| 		result.Observe(1) | ||||
| 		// last value wins | ||||
| @@ -331,12 +321,12 @@ func TestObserverCollection(t *testing.T) { | ||||
| 		result.Observe(1) | ||||
| 	}) | ||||
|  | ||||
| 	_ = Must(meter).NewFloat64SumObserver("float.sumobserver", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 	_ = Must(meter).NewFloat64SumObserver("float.sumobserver.sum", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 		result.Observe(1, kv.String("A", "B")) | ||||
| 		result.Observe(2, kv.String("A", "B")) | ||||
| 		result.Observe(1, kv.String("C", "D")) | ||||
| 	}) | ||||
| 	_ = Must(meter).NewInt64SumObserver("int.sumobserver", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 	_ = Must(meter).NewInt64SumObserver("int.sumobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 		result.Observe(2, kv.String("A", "B")) | ||||
| 		result.Observe(1) | ||||
| 		// last value wins | ||||
| @@ -344,12 +334,12 @@ func TestObserverCollection(t *testing.T) { | ||||
| 		result.Observe(1) | ||||
| 	}) | ||||
|  | ||||
| 	_ = Must(meter).NewFloat64UpDownSumObserver("float.updownsumobserver", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 	_ = Must(meter).NewFloat64UpDownSumObserver("float.updownsumobserver.sum", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 		result.Observe(1, kv.String("A", "B")) | ||||
| 		result.Observe(-2, kv.String("A", "B")) | ||||
| 		result.Observe(1, kv.String("C", "D")) | ||||
| 	}) | ||||
| 	_ = Must(meter).NewInt64UpDownSumObserver("int.updownsumobserver", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 	_ = Must(meter).NewInt64UpDownSumObserver("int.updownsumobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 		result.Observe(2, kv.String("A", "B")) | ||||
| 		result.Observe(1) | ||||
| 		// last value wins | ||||
| @@ -357,7 +347,7 @@ func TestObserverCollection(t *testing.T) { | ||||
| 		result.Observe(-1) | ||||
| 	}) | ||||
|  | ||||
| 	_ = Must(meter).NewInt64ValueObserver("empty.valueobserver", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 	_ = Must(meter).NewInt64ValueObserver("empty.valueobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 	}) | ||||
|  | ||||
| 	collected := sdk.Collect(ctx) | ||||
| @@ -369,20 +359,20 @@ func TestObserverCollection(t *testing.T) { | ||||
| 		_ = out.AddTo(rec) | ||||
| 	} | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"float.valueobserver/A=B/R=V": -1, | ||||
| 		"float.valueobserver/C=D/R=V": -1, | ||||
| 		"int.valueobserver//R=V":      1, | ||||
| 		"int.valueobserver/A=B/R=V":   1, | ||||
| 		"float.valueobserver.lastvalue/A=B/R=V": -1, | ||||
| 		"float.valueobserver.lastvalue/C=D/R=V": -1, | ||||
| 		"int.valueobserver.lastvalue//R=V":      1, | ||||
| 		"int.valueobserver.lastvalue/A=B/R=V":   1, | ||||
|  | ||||
| 		"float.sumobserver/A=B/R=V": 2, | ||||
| 		"float.sumobserver/C=D/R=V": 1, | ||||
| 		"int.sumobserver//R=V":      1, | ||||
| 		"int.sumobserver/A=B/R=V":   1, | ||||
| 		"float.sumobserver.sum/A=B/R=V": 2, | ||||
| 		"float.sumobserver.sum/C=D/R=V": 1, | ||||
| 		"int.sumobserver.sum//R=V":      1, | ||||
| 		"int.sumobserver.sum/A=B/R=V":   1, | ||||
|  | ||||
| 		"float.updownsumobserver/A=B/R=V": -2, | ||||
| 		"float.updownsumobserver/C=D/R=V": 1, | ||||
| 		"int.updownsumobserver//R=V":      -1, | ||||
| 		"int.updownsumobserver/A=B/R=V":   1, | ||||
| 		"float.updownsumobserver.sum/A=B/R=V": -2, | ||||
| 		"float.updownsumobserver.sum/C=D/R=V": 1, | ||||
| 		"int.updownsumobserver.sum//R=V":      -1, | ||||
| 		"int.updownsumobserver.sum/A=B/R=V":   1, | ||||
| 	}, out.Map) | ||||
| } | ||||
|  | ||||
| @@ -390,13 +380,14 @@ func TestSumObserverInputRange(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	_ = Must(meter).NewFloat64SumObserver("float.sumobserver", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 	// TODO: these tests are testing for negative values, not for _descending values_. Fix. | ||||
| 	_ = Must(meter).NewFloat64SumObserver("float.sumobserver.sum", func(_ context.Context, result metric.Float64ObserverResult) { | ||||
| 		result.Observe(-2, kv.String("A", "B")) | ||||
| 		require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) | ||||
| 		result.Observe(-1, kv.String("C", "D")) | ||||
| 		require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) | ||||
| 	}) | ||||
| 	_ = Must(meter).NewInt64SumObserver("int.sumobserver", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 	_ = Must(meter).NewInt64SumObserver("int.sumobserver.sum", func(_ context.Context, result metric.Int64ObserverResult) { | ||||
| 		result.Observe(-1, kv.String("A", "B")) | ||||
| 		require.Equal(t, aggregation.ErrNegativeInput, testHandler.Flush()) | ||||
| 		result.Observe(-1) | ||||
| @@ -455,12 +446,12 @@ func TestObserverBatch(t *testing.T) { | ||||
| 				intUpDownSumObs.Observation(10), | ||||
| 			) | ||||
| 		}) | ||||
| 	floatValueObs = batch.NewFloat64ValueObserver("float.valueobserver") | ||||
| 	intValueObs = batch.NewInt64ValueObserver("int.valueobserver") | ||||
| 	floatSumObs = batch.NewFloat64SumObserver("float.sumobserver") | ||||
| 	intSumObs = batch.NewInt64SumObserver("int.sumobserver") | ||||
| 	floatUpDownSumObs = batch.NewFloat64UpDownSumObserver("float.updownsumobserver") | ||||
| 	intUpDownSumObs = batch.NewInt64UpDownSumObserver("int.updownsumobserver") | ||||
| 	floatValueObs = batch.NewFloat64ValueObserver("float.valueobserver.lastvalue") | ||||
| 	intValueObs = batch.NewInt64ValueObserver("int.valueobserver.lastvalue") | ||||
| 	floatSumObs = batch.NewFloat64SumObserver("float.sumobserver.sum") | ||||
| 	intSumObs = batch.NewInt64SumObserver("int.sumobserver.sum") | ||||
| 	floatUpDownSumObs = batch.NewFloat64UpDownSumObserver("float.updownsumobserver.sum") | ||||
| 	intUpDownSumObs = batch.NewInt64UpDownSumObserver("int.updownsumobserver.sum") | ||||
|  | ||||
| 	collected := sdk.Collect(ctx) | ||||
|  | ||||
| @@ -471,20 +462,20 @@ func TestObserverBatch(t *testing.T) { | ||||
| 		_ = out.AddTo(rec) | ||||
| 	} | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"float.sumobserver//R=V":    1.1, | ||||
| 		"float.sumobserver/A=B/R=V": 1000, | ||||
| 		"int.sumobserver//R=V":      10, | ||||
| 		"int.sumobserver/A=B/R=V":   100, | ||||
| 		"float.sumobserver.sum//R=V":    1.1, | ||||
| 		"float.sumobserver.sum/A=B/R=V": 1000, | ||||
| 		"int.sumobserver.sum//R=V":      10, | ||||
| 		"int.sumobserver.sum/A=B/R=V":   100, | ||||
|  | ||||
| 		"int.updownsumobserver/A=B/R=V":   -100, | ||||
| 		"float.updownsumobserver/A=B/R=V": -1000, | ||||
| 		"int.updownsumobserver//R=V":      10, | ||||
| 		"float.updownsumobserver/C=D/R=V": -1, | ||||
| 		"int.updownsumobserver.sum/A=B/R=V":   -100, | ||||
| 		"float.updownsumobserver.sum/A=B/R=V": -1000, | ||||
| 		"int.updownsumobserver.sum//R=V":      10, | ||||
| 		"float.updownsumobserver.sum/C=D/R=V": -1, | ||||
|  | ||||
| 		"float.valueobserver/A=B/R=V": -1, | ||||
| 		"float.valueobserver/C=D/R=V": -1, | ||||
| 		"int.valueobserver//R=V":      1, | ||||
| 		"int.valueobserver/A=B/R=V":   1, | ||||
| 		"float.valueobserver.lastvalue/A=B/R=V": -1, | ||||
| 		"float.valueobserver.lastvalue/C=D/R=V": -1, | ||||
| 		"int.valueobserver.lastvalue//R=V":      1, | ||||
| 		"int.valueobserver.lastvalue/A=B/R=V":   1, | ||||
| 	}, out.Map) | ||||
| } | ||||
|  | ||||
| @@ -492,10 +483,10 @@ func TestRecordBatch(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	counter1 := Must(meter).NewInt64Counter("int64.counter") | ||||
| 	counter2 := Must(meter).NewFloat64Counter("float64.counter") | ||||
| 	valuerecorder1 := Must(meter).NewInt64ValueRecorder("int64.valuerecorder") | ||||
| 	valuerecorder2 := Must(meter).NewFloat64ValueRecorder("float64.valuerecorder") | ||||
| 	counter1 := Must(meter).NewInt64Counter("int64.sum") | ||||
| 	counter2 := Must(meter).NewFloat64Counter("float64.sum") | ||||
| 	valuerecorder1 := Must(meter).NewInt64ValueRecorder("int64.exact") | ||||
| 	valuerecorder2 := Must(meter).NewFloat64ValueRecorder("float64.exact") | ||||
|  | ||||
| 	sdk.RecordBatch( | ||||
| 		ctx, | ||||
| @@ -516,10 +507,10 @@ func TestRecordBatch(t *testing.T) { | ||||
| 		_ = out.AddTo(rec) | ||||
| 	} | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"int64.counter/A=B,C=D/R=V":         1, | ||||
| 		"float64.counter/A=B,C=D/R=V":       2, | ||||
| 		"int64.valuerecorder/A=B,C=D/R=V":   3, | ||||
| 		"float64.valuerecorder/A=B,C=D/R=V": 4, | ||||
| 		"int64.sum/A=B,C=D/R=V":     1, | ||||
| 		"float64.sum/A=B,C=D/R=V":   2, | ||||
| 		"int64.exact/A=B,C=D/R=V":   3, | ||||
| 		"float64.exact/A=B,C=D/R=V": 4, | ||||
| 	}, out.Map) | ||||
| } | ||||
|  | ||||
| @@ -530,7 +521,7 @@ func TestRecordPersistence(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	c := Must(meter).NewFloat64Counter("sum.name") | ||||
| 	c := Must(meter).NewFloat64Counter("name.sum") | ||||
| 	b := c.Bind(kv.String("bound", "true")) | ||||
| 	uk := kv.String("bound", "false") | ||||
|  | ||||
| @@ -540,7 +531,7 @@ func TestRecordPersistence(t *testing.T) { | ||||
| 		sdk.Collect(ctx) | ||||
| 	} | ||||
|  | ||||
| 	require.Equal(t, int64(2), integrator.newAggCount) | ||||
| 	require.Equal(t, 4, integrator.newAggCount) | ||||
| } | ||||
|  | ||||
| func TestIncorrectInstruments(t *testing.T) { | ||||
| @@ -564,7 +555,7 @@ func TestIncorrectInstruments(t *testing.T) { | ||||
|  | ||||
| 	// Now try with instruments from another SDK. | ||||
| 	var noopMeter metric.Meter | ||||
| 	counter = metric.Must(noopMeter).NewInt64Counter("counter") | ||||
| 	counter = metric.Must(noopMeter).NewInt64Counter("name.sum") | ||||
| 	observer = metric.Must(noopMeter).NewBatchObserver( | ||||
| 		func(context.Context, metric.BatchObserverResult) {}, | ||||
| 	).NewInt64ValueObserver("observer") | ||||
| @@ -583,8 +574,8 @@ func TestSyncInAsync(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	meter, sdk, integrator := newSDK(t) | ||||
|  | ||||
| 	counter := Must(meter).NewFloat64Counter("counter") | ||||
| 	_ = Must(meter).NewInt64ValueObserver("observer", | ||||
| 	counter := Must(meter).NewFloat64Counter("counter.sum") | ||||
| 	_ = Must(meter).NewInt64ValueObserver("observer.lastvalue", | ||||
| 		func(ctx context.Context, result metric.Int64ObserverResult) { | ||||
| 			result.Observe(10) | ||||
| 			counter.Add(ctx, 100) | ||||
| @@ -598,7 +589,7 @@ func TestSyncInAsync(t *testing.T) { | ||||
| 		_ = out.AddTo(rec) | ||||
| 	} | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"counter//R=V":  100, | ||||
| 		"observer//R=V": 10, | ||||
| 		"counter.sum//R=V":        100, | ||||
| 		"observer.lastvalue//R=V": 10, | ||||
| 	}, out.Map) | ||||
| } | ||||
|   | ||||
| @@ -20,13 +20,17 @@ import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" | ||||
| ) | ||||
|  | ||||
| func TestStressInt64Histogram(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("some_metric", metric.ValueRecorderKind, metric.Int64NumberKind) | ||||
| 	h := histogram.New(&desc, []float64{25, 50, 75}) | ||||
|  | ||||
| 	alloc := histogram.New(2, &desc, []float64{25, 50, 75}) | ||||
| 	h, ckpt := &alloc[0], &alloc[1] | ||||
|  | ||||
| 	ctx, cancelFunc := context.WithCancel(context.Background()) | ||||
| 	defer cancelFunc() | ||||
| @@ -44,10 +48,10 @@ func TestStressInt64Histogram(t *testing.T) { | ||||
|  | ||||
| 	startTime := time.Now() | ||||
| 	for time.Since(startTime) < time.Second { | ||||
| 		h.Checkpoint(&desc) | ||||
| 		require.NoError(t, h.SynchronizedCopy(ckpt, &desc)) | ||||
|  | ||||
| 		b, _ := h.Histogram() | ||||
| 		c, _ := h.Count() | ||||
| 		b, _ := ckpt.Histogram() | ||||
| 		c, _ := ckpt.Count() | ||||
|  | ||||
| 		var realCount int64 | ||||
| 		for _, c := range b.Counts { | ||||
|   | ||||
| @@ -89,7 +89,7 @@ func (b *Integrator) Process(record export.Record) error { | ||||
| 		tmp := agg | ||||
| 		// Note: the call to AggregatorFor() followed by Merge | ||||
| 		// is effectively a Clone() operation. | ||||
| 		agg = b.AggregatorFor(desc) | ||||
| 		b.AggregatorFor(desc, &agg) | ||||
| 		if err := agg.Merge(tmp, desc); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|   | ||||
| @@ -18,66 +18,129 @@ import ( | ||||
| 	"context" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| 	"go.opentelemetry.io/otel/api/label" | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/simple" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| 	"go.opentelemetry.io/otel/sdk/resource" | ||||
| ) | ||||
|  | ||||
| // These tests use the ../test label encoding. | ||||
| // Note: This var block and the helpers below will disappear in a | ||||
| // future PR (see the draft in #799).  The test has been completely | ||||
| // rewritten there, so this code will simply be dropped. | ||||
|  | ||||
| var ( | ||||
| 	// Resource is applied to all test records built in this package. | ||||
| 	Resource = resource.New(kv.String("R", "V")) | ||||
|  | ||||
| 	// LastValueADesc and LastValueBDesc group by "G" | ||||
| 	LastValueADesc = metric.NewDescriptor( | ||||
| 		"a.lastvalue", metric.ValueObserverKind, metric.Int64NumberKind) | ||||
| 	LastValueBDesc = metric.NewDescriptor( | ||||
| 		"b.lastvalue", metric.ValueObserverKind, metric.Int64NumberKind) | ||||
| 	// CounterADesc and CounterBDesc group by "C" | ||||
| 	CounterADesc = metric.NewDescriptor( | ||||
| 		"a.sum", metric.CounterKind, metric.Int64NumberKind) | ||||
| 	CounterBDesc = metric.NewDescriptor( | ||||
| 		"b.sum", metric.CounterKind, metric.Int64NumberKind) | ||||
|  | ||||
| 	// LastValue groups are (labels1), (labels2+labels3) | ||||
| 	// Counter groups are (labels1+labels2), (labels3) | ||||
|  | ||||
| 	// Labels1 has G=H and C=D | ||||
| 	Labels1 = makeLabels(kv.String("G", "H"), kv.String("C", "D")) | ||||
| 	// Labels2 has C=D and E=F | ||||
| 	Labels2 = makeLabels(kv.String("C", "D"), kv.String("E", "F")) | ||||
| 	// Labels3 is the empty set | ||||
| 	Labels3 = makeLabels() | ||||
| ) | ||||
|  | ||||
| func makeLabels(labels ...kv.KeyValue) *label.Set { | ||||
| 	s := label.NewSet(labels...) | ||||
| 	return &s | ||||
| } | ||||
|  | ||||
| // LastValueAgg returns a checkpointed lastValue aggregator w/ the specified descriptor and value. | ||||
| func LastValueAgg(desc *metric.Descriptor, v int64) export.Aggregator { | ||||
| 	ctx := context.Background() | ||||
| 	gagg := &lastvalue.New(1)[0] | ||||
| 	_ = gagg.Update(ctx, metric.NewInt64Number(v), desc) | ||||
| 	return gagg | ||||
| } | ||||
|  | ||||
| // Convenience method for building a test exported lastValue record. | ||||
| func NewLastValueRecord(desc *metric.Descriptor, labels *label.Set, value int64) export.Record { | ||||
| 	return export.NewRecord(desc, labels, Resource, LastValueAgg(desc, value)) | ||||
| } | ||||
|  | ||||
| // Convenience method for building a test exported counter record. | ||||
| func NewCounterRecord(desc *metric.Descriptor, labels *label.Set, value int64) export.Record { | ||||
| 	return export.NewRecord(desc, labels, Resource, CounterAgg(desc, value)) | ||||
| } | ||||
|  | ||||
| // CounterAgg returns a checkpointed counter aggregator w/ the specified descriptor and value. | ||||
| func CounterAgg(desc *metric.Descriptor, v int64) export.Aggregator { | ||||
| 	ctx := context.Background() | ||||
| 	cagg := &sum.New(1)[0] | ||||
| 	_ = cagg.Update(ctx, metric.NewInt64Number(v), desc) | ||||
| 	return cagg | ||||
| } | ||||
|  | ||||
| func TestSimpleStateless(t *testing.T) { | ||||
| 	b := simple.New(test.NewAggregationSelector(), false) | ||||
| 	b := simple.New(test.AggregationSelector(), false) | ||||
|  | ||||
| 	// Set initial lastValue values | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueADesc, test.Labels1, 10)) | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueADesc, test.Labels2, 20)) | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueADesc, test.Labels3, 30)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueADesc, Labels1, 10)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueADesc, Labels2, 20)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueADesc, Labels3, 30)) | ||||
|  | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueBDesc, test.Labels1, 10)) | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueBDesc, test.Labels2, 20)) | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueBDesc, test.Labels3, 30)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueBDesc, Labels1, 10)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueBDesc, Labels2, 20)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueBDesc, Labels3, 30)) | ||||
|  | ||||
| 	// Another lastValue Set for Labels1 | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueADesc, test.Labels1, 50)) | ||||
| 	_ = b.Process(test.NewLastValueRecord(&test.LastValueBDesc, test.Labels1, 50)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueADesc, Labels1, 50)) | ||||
| 	_ = b.Process(NewLastValueRecord(&LastValueBDesc, Labels1, 50)) | ||||
|  | ||||
| 	// Set initial counter values | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterADesc, test.Labels1, 10)) | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterADesc, test.Labels2, 20)) | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterADesc, test.Labels3, 40)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterADesc, Labels1, 10)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterADesc, Labels2, 20)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterADesc, Labels3, 40)) | ||||
|  | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterBDesc, test.Labels1, 10)) | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterBDesc, test.Labels2, 20)) | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterBDesc, test.Labels3, 40)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterBDesc, Labels1, 10)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterBDesc, Labels2, 20)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterBDesc, Labels3, 40)) | ||||
|  | ||||
| 	// Another counter Add for Labels1 | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterADesc, test.Labels1, 50)) | ||||
| 	_ = b.Process(test.NewCounterRecord(&test.CounterBDesc, test.Labels1, 50)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterADesc, Labels1, 50)) | ||||
| 	_ = b.Process(NewCounterRecord(&CounterBDesc, Labels1, 50)) | ||||
|  | ||||
| 	checkpointSet := b.CheckpointSet() | ||||
|  | ||||
| 	records := test.NewOutput(test.SdkEncoder) | ||||
| 	records := test.NewOutput(label.DefaultEncoder()) | ||||
| 	_ = checkpointSet.ForEach(records.AddTo) | ||||
|  | ||||
| 	// Output lastvalue should have only the "G=H" and "G=" keys. | ||||
| 	// Output counter should have only the "C=D" and "C=" keys. | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"sum.a/C~D&G~H/R~V":       60, // labels1 | ||||
| 		"sum.a/C~D&E~F/R~V":       20, // labels2 | ||||
| 		"sum.a//R~V":              40, // labels3 | ||||
| 		"sum.b/C~D&G~H/R~V":       60, // labels1 | ||||
| 		"sum.b/C~D&E~F/R~V":       20, // labels2 | ||||
| 		"sum.b//R~V":              40, // labels3 | ||||
| 		"lastvalue.a/C~D&G~H/R~V": 50, // labels1 | ||||
| 		"lastvalue.a/C~D&E~F/R~V": 20, // labels2 | ||||
| 		"lastvalue.a//R~V":        30, // labels3 | ||||
| 		"lastvalue.b/C~D&G~H/R~V": 50, // labels1 | ||||
| 		"lastvalue.b/C~D&E~F/R~V": 20, // labels2 | ||||
| 		"lastvalue.b//R~V":        30, // labels3 | ||||
| 		"a.sum/C=D,G=H/R=V":       60, // labels1 | ||||
| 		"a.sum/C=D,E=F/R=V":       20, // labels2 | ||||
| 		"a.sum//R=V":              40, // labels3 | ||||
| 		"b.sum/C=D,G=H/R=V":       60, // labels1 | ||||
| 		"b.sum/C=D,E=F/R=V":       20, // labels2 | ||||
| 		"b.sum//R=V":              40, // labels3 | ||||
| 		"a.lastvalue/C=D,G=H/R=V": 50, // labels1 | ||||
| 		"a.lastvalue/C=D,E=F/R=V": 20, // labels2 | ||||
| 		"a.lastvalue//R=V":        30, // labels3 | ||||
| 		"b.lastvalue/C=D,G=H/R=V": 50, // labels1 | ||||
| 		"b.lastvalue/C=D,E=F/R=V": 20, // labels2 | ||||
| 		"b.lastvalue//R=V":        30, // labels3 | ||||
| 	}, records.Map) | ||||
| 	b.FinishedCollection() | ||||
|  | ||||
| @@ -92,64 +155,67 @@ func TestSimpleStateless(t *testing.T) { | ||||
|  | ||||
| func TestSimpleStateful(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	b := simple.New(test.NewAggregationSelector(), true) | ||||
| 	b := simple.New(test.AggregationSelector(), true) | ||||
|  | ||||
| 	counterA := test.NewCounterRecord(&test.CounterADesc, test.Labels1, 10) | ||||
| 	caggA := counterA.Aggregator() | ||||
| 	counterA := NewCounterRecord(&CounterADesc, Labels1, 10) | ||||
| 	_ = b.Process(counterA) | ||||
|  | ||||
| 	counterB := test.NewCounterRecord(&test.CounterBDesc, test.Labels1, 10) | ||||
| 	caggB := counterB.Aggregator() | ||||
| 	counterB := NewCounterRecord(&CounterBDesc, Labels1, 10) | ||||
| 	_ = b.Process(counterB) | ||||
|  | ||||
| 	checkpointSet := b.CheckpointSet() | ||||
| 	b.FinishedCollection() | ||||
|  | ||||
| 	records1 := test.NewOutput(test.SdkEncoder) | ||||
| 	records1 := test.NewOutput(label.DefaultEncoder()) | ||||
| 	_ = checkpointSet.ForEach(records1.AddTo) | ||||
|  | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"sum.a/C~D&G~H/R~V": 10, // labels1 | ||||
| 		"sum.b/C~D&G~H/R~V": 10, // labels1 | ||||
| 		"a.sum/C=D,G=H/R=V": 10, // labels1 | ||||
| 		"b.sum/C=D,G=H/R=V": 10, // labels1 | ||||
| 	}, records1.Map) | ||||
|  | ||||
| 	alloc := sum.New(4) | ||||
| 	caggA, caggB, ckptA, ckptB := &alloc[0], &alloc[1], &alloc[2], &alloc[3] | ||||
|  | ||||
| 	// Test that state was NOT reset | ||||
| 	checkpointSet = b.CheckpointSet() | ||||
|  | ||||
| 	records2 := test.NewOutput(test.SdkEncoder) | ||||
| 	records2 := test.NewOutput(label.DefaultEncoder()) | ||||
| 	_ = checkpointSet.ForEach(records2.AddTo) | ||||
|  | ||||
| 	require.EqualValues(t, records1.Map, records2.Map) | ||||
| 	b.FinishedCollection() | ||||
|  | ||||
| 	// Update and re-checkpoint the original record. | ||||
| 	_ = caggA.Update(ctx, metric.NewInt64Number(20), &test.CounterADesc) | ||||
| 	_ = caggB.Update(ctx, metric.NewInt64Number(20), &test.CounterBDesc) | ||||
| 	caggA.Checkpoint(&test.CounterADesc) | ||||
| 	caggB.Checkpoint(&test.CounterBDesc) | ||||
| 	_ = caggA.Update(ctx, metric.NewInt64Number(20), &CounterADesc) | ||||
| 	_ = caggB.Update(ctx, metric.NewInt64Number(20), &CounterBDesc) | ||||
| 	err := caggA.SynchronizedCopy(ckptA, &CounterADesc) | ||||
| 	require.NoError(t, err) | ||||
| 	err = caggB.SynchronizedCopy(ckptB, &CounterBDesc) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	// As yet cagg has not been passed to Integrator.Process.  Should | ||||
| 	// not see an update. | ||||
| 	checkpointSet = b.CheckpointSet() | ||||
|  | ||||
| 	records3 := test.NewOutput(test.SdkEncoder) | ||||
| 	records3 := test.NewOutput(label.DefaultEncoder()) | ||||
| 	_ = checkpointSet.ForEach(records3.AddTo) | ||||
|  | ||||
| 	require.EqualValues(t, records1.Map, records3.Map) | ||||
| 	b.FinishedCollection() | ||||
|  | ||||
| 	// Now process the second update | ||||
| 	_ = b.Process(export.NewRecord(&test.CounterADesc, test.Labels1, test.Resource, caggA)) | ||||
| 	_ = b.Process(export.NewRecord(&test.CounterBDesc, test.Labels1, test.Resource, caggB)) | ||||
| 	_ = b.Process(export.NewRecord(&CounterADesc, Labels1, Resource, ckptA)) | ||||
| 	_ = b.Process(export.NewRecord(&CounterBDesc, Labels1, Resource, ckptB)) | ||||
|  | ||||
| 	checkpointSet = b.CheckpointSet() | ||||
|  | ||||
| 	records4 := test.NewOutput(test.SdkEncoder) | ||||
| 	records4 := test.NewOutput(label.DefaultEncoder()) | ||||
| 	_ = checkpointSet.ForEach(records4.AddTo) | ||||
|  | ||||
| 	require.EqualValues(t, map[string]float64{ | ||||
| 		"sum.a/C~D&G~H/R~V": 30, | ||||
| 		"sum.b/C~D&G~H/R~V": 30, | ||||
| 		"a.sum/C=D,G=H/R=V": 30, | ||||
| 		"b.sum/C=D,G=H/R=V": 30, | ||||
| 	}, records4.Map) | ||||
| 	b.FinishedCollection() | ||||
| } | ||||
|   | ||||
| @@ -15,24 +15,22 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/kv" | ||||
| 	"go.opentelemetry.io/otel/api/label" | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/array" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/resource" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	// Encoder is an alternate label encoder to validate grouping logic. | ||||
| 	Encoder struct{} | ||||
|  | ||||
| 	// Output collects distinct metric/label set outputs. | ||||
| 	Output struct { | ||||
| 		Map          map[string]float64 | ||||
| @@ -45,39 +43,6 @@ type ( | ||||
| 	testAggregationSelector struct{} | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Resource is applied to all test records built in this package. | ||||
| 	Resource = resource.New(kv.String("R", "V")) | ||||
|  | ||||
| 	// LastValueADesc and LastValueBDesc group by "G" | ||||
| 	LastValueADesc = metric.NewDescriptor( | ||||
| 		"lastvalue.a", metric.ValueObserverKind, metric.Int64NumberKind) | ||||
| 	LastValueBDesc = metric.NewDescriptor( | ||||
| 		"lastvalue.b", metric.ValueObserverKind, metric.Int64NumberKind) | ||||
| 	// CounterADesc and CounterBDesc group by "C" | ||||
| 	CounterADesc = metric.NewDescriptor( | ||||
| 		"sum.a", metric.CounterKind, metric.Int64NumberKind) | ||||
| 	CounterBDesc = metric.NewDescriptor( | ||||
| 		"sum.b", metric.CounterKind, metric.Int64NumberKind) | ||||
|  | ||||
| 	// SdkEncoder uses a non-standard encoder like K1~V1&K2~V2 | ||||
| 	SdkEncoder = &Encoder{} | ||||
| 	// GroupEncoder uses the SDK default encoder | ||||
| 	GroupEncoder = label.DefaultEncoder() | ||||
|  | ||||
| 	// LastValue groups are (labels1), (labels2+labels3) | ||||
| 	// Counter groups are (labels1+labels2), (labels3) | ||||
|  | ||||
| 	// Labels1 has G=H and C=D | ||||
| 	Labels1 = makeLabels(kv.String("G", "H"), kv.String("C", "D")) | ||||
| 	// Labels2 has C=D and E=F | ||||
| 	Labels2 = makeLabels(kv.String("C", "D"), kv.String("E", "F")) | ||||
| 	// Labels3 is the empty set | ||||
| 	Labels3 = makeLabels() | ||||
|  | ||||
| 	testLabelEncoderID = label.NewEncoderID() | ||||
| ) | ||||
|  | ||||
| func NewOutput(labelEncoder label.Encoder) Output { | ||||
| 	return Output{ | ||||
| 		Map:          make(map[string]float64), | ||||
| @@ -85,73 +50,53 @@ func NewOutput(labelEncoder label.Encoder) Output { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewAggregationSelector returns a policy that is consistent with the | ||||
| // AggregationSelector returns a policy that is consistent with the | ||||
| // test descriptors above.  I.e., it returns sum.New() for counter | ||||
| // instruments and lastvalue.New() for lastValue instruments. | ||||
| func NewAggregationSelector() export.AggregationSelector { | ||||
| 	return &testAggregationSelector{} | ||||
| func AggregationSelector() export.AggregationSelector { | ||||
| 	return testAggregationSelector{} | ||||
| } | ||||
|  | ||||
| func (*testAggregationSelector) AggregatorFor(desc *metric.Descriptor) export.Aggregator { | ||||
| 	switch desc.MetricKind() { | ||||
| 	case metric.CounterKind: | ||||
| 		return sum.New() | ||||
| 	case metric.ValueObserverKind: | ||||
| 		return lastvalue.New() | ||||
| 	default: | ||||
| 		panic("Invalid descriptor MetricKind for this test") | ||||
| 	} | ||||
| } | ||||
| func (testAggregationSelector) AggregatorFor(desc *metric.Descriptor, aggPtrs ...*export.Aggregator) { | ||||
|  | ||||
| func makeLabels(labels ...kv.KeyValue) *label.Set { | ||||
| 	s := label.NewSet(labels...) | ||||
| 	return &s | ||||
| } | ||||
|  | ||||
| func (Encoder) Encode(iter label.Iterator) string { | ||||
| 	var sb strings.Builder | ||||
| 	for iter.Next() { | ||||
| 		i, l := iter.IndexedLabel() | ||||
| 		if i > 0 { | ||||
| 			sb.WriteString("&") | ||||
| 	switch { | ||||
| 	case strings.HasSuffix(desc.Name(), ".disabled"): | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = nil | ||||
| 		} | ||||
| 		sb.WriteString(string(l.Key)) | ||||
| 		sb.WriteString("~") | ||||
| 		sb.WriteString(l.Value.Emit()) | ||||
| 	case strings.HasSuffix(desc.Name(), ".sum"): | ||||
| 		aggs := sum.New(len(aggPtrs)) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	case strings.HasSuffix(desc.Name(), ".minmaxsumcount"): | ||||
| 		aggs := minmaxsumcount.New(len(aggPtrs), desc) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	case strings.HasSuffix(desc.Name(), ".lastvalue"): | ||||
| 		aggs := lastvalue.New(len(aggPtrs)) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	case strings.HasSuffix(desc.Name(), ".sketch"): | ||||
| 		aggs := ddsketch.New(len(aggPtrs), desc, ddsketch.NewDefaultConfig()) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	case strings.HasSuffix(desc.Name(), ".histogram"): | ||||
| 		aggs := histogram.New(len(aggPtrs), desc, nil) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	case strings.HasSuffix(desc.Name(), ".exact"): | ||||
| 		aggs := array.New(len(aggPtrs)) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	default: | ||||
| 		panic(fmt.Sprint("Invalid instrument name for test AggregationSelector: ", desc.Name())) | ||||
| 	} | ||||
| 	return sb.String() | ||||
| } | ||||
|  | ||||
| func (Encoder) ID() label.EncoderID { | ||||
| 	return testLabelEncoderID | ||||
| } | ||||
|  | ||||
| // LastValueAgg returns a checkpointed lastValue aggregator w/ the specified descriptor and value. | ||||
| func LastValueAgg(desc *metric.Descriptor, v int64) export.Aggregator { | ||||
| 	ctx := context.Background() | ||||
| 	gagg := lastvalue.New() | ||||
| 	_ = gagg.Update(ctx, metric.NewInt64Number(v), desc) | ||||
| 	gagg.Checkpoint(desc) | ||||
| 	return gagg | ||||
| } | ||||
|  | ||||
| // Convenience method for building a test exported lastValue record. | ||||
| func NewLastValueRecord(desc *metric.Descriptor, labels *label.Set, value int64) export.Record { | ||||
| 	return export.NewRecord(desc, labels, Resource, LastValueAgg(desc, value)) | ||||
| } | ||||
|  | ||||
| // Convenience method for building a test exported counter record. | ||||
| func NewCounterRecord(desc *metric.Descriptor, labels *label.Set, value int64) export.Record { | ||||
| 	return export.NewRecord(desc, labels, Resource, CounterAgg(desc, value)) | ||||
| } | ||||
|  | ||||
| // CounterAgg returns a checkpointed counter aggregator w/ the specified descriptor and value. | ||||
| func CounterAgg(desc *metric.Descriptor, v int64) export.Aggregator { | ||||
| 	ctx := context.Background() | ||||
| 	cagg := sum.New() | ||||
| 	_ = cagg.Update(ctx, metric.NewInt64Number(v), desc) | ||||
| 	cagg.Checkpoint(desc) | ||||
| 	return cagg | ||||
| } | ||||
|  | ||||
| // AddTo adds a name/label-encoding entry with the lastValue or counter | ||||
|   | ||||
| @@ -26,7 +26,8 @@ import ( | ||||
|  | ||||
| func TestStressInt64MinMaxSumCount(t *testing.T) { | ||||
| 	desc := metric.NewDescriptor("some_metric", metric.ValueRecorderKind, metric.Int64NumberKind) | ||||
| 	mmsc := minmaxsumcount.New(&desc) | ||||
| 	alloc := minmaxsumcount.New(2, &desc) | ||||
| 	mmsc, ckpt := &alloc[0], &alloc[1] | ||||
|  | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	defer cancel() | ||||
| @@ -46,12 +47,12 @@ func TestStressInt64MinMaxSumCount(t *testing.T) { | ||||
|  | ||||
| 	startTime := time.Now() | ||||
| 	for time.Since(startTime) < time.Second { | ||||
| 		mmsc.Checkpoint(&desc) | ||||
| 		_ = mmsc.SynchronizedCopy(ckpt, &desc) | ||||
|  | ||||
| 		s, _ := mmsc.Sum() | ||||
| 		c, _ := mmsc.Count() | ||||
| 		min, e1 := mmsc.Min() | ||||
| 		max, e2 := mmsc.Max() | ||||
| 		s, _ := ckpt.Sum() | ||||
| 		c, _ := ckpt.Count() | ||||
| 		min, e1 := ckpt.Min() | ||||
| 		max, e2 := ckpt.Max() | ||||
| 		if c == 0 && (e1 == nil || e2 == nil || s.AsInt64() != 0) { | ||||
| 			t.Fail() | ||||
| 		} | ||||
|   | ||||
| @@ -116,10 +116,11 @@ type ( | ||||
| 		// inst is a pointer to the corresponding instrument. | ||||
| 		inst *syncInstrument | ||||
|  | ||||
| 		// recorder implements the actual RecordOne() API, | ||||
| 		// current implements the actual RecordOne() API, | ||||
| 		// depending on the type of aggregation.  If nil, the | ||||
| 		// metric was disabled by the exporter. | ||||
| 		recorder export.Aggregator | ||||
| 		current    export.Aggregator | ||||
| 		checkpoint export.Aggregator | ||||
| 	} | ||||
|  | ||||
| 	instrument struct { | ||||
| @@ -137,7 +138,7 @@ type ( | ||||
| 	labeledRecorder struct { | ||||
| 		observedEpoch int64 | ||||
| 		labels        *label.Set | ||||
| 		recorder      export.Aggregator | ||||
| 		observed      export.Aggregator | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| @@ -185,14 +186,15 @@ func (a *asyncInstrument) getRecorder(labels *label.Set) export.Aggregator { | ||||
| 		if lrec.observedEpoch == a.meter.currentEpoch { | ||||
| 			// last value wins for Observers, so if we see the same labels | ||||
| 			// in the current epoch, we replace the old recorder | ||||
| 			lrec.recorder = a.meter.integrator.AggregatorFor(&a.descriptor) | ||||
| 			a.meter.integrator.AggregatorFor(&a.descriptor, &lrec.observed) | ||||
| 		} else { | ||||
| 			lrec.observedEpoch = a.meter.currentEpoch | ||||
| 		} | ||||
| 		a.recorders[labels.Equivalent()] = lrec | ||||
| 		return lrec.recorder | ||||
| 		return lrec.observed | ||||
| 	} | ||||
| 	rec := a.meter.integrator.AggregatorFor(&a.descriptor) | ||||
| 	var rec export.Aggregator | ||||
| 	a.meter.integrator.AggregatorFor(&a.descriptor, &rec) | ||||
| 	if a.recorders == nil { | ||||
| 		a.recorders = make(map[label.Distinct]*labeledRecorder) | ||||
| 	} | ||||
| @@ -200,7 +202,7 @@ func (a *asyncInstrument) getRecorder(labels *label.Set) export.Aggregator { | ||||
| 	// asyncInstrument for the labelset for good. This is intentional, | ||||
| 	// but will be revisited later. | ||||
| 	a.recorders[labels.Equivalent()] = &labeledRecorder{ | ||||
| 		recorder:      rec, | ||||
| 		observed:      rec, | ||||
| 		labels:        labels, | ||||
| 		observedEpoch: a.meter.currentEpoch, | ||||
| 	} | ||||
| @@ -252,7 +254,8 @@ func (s *syncInstrument) acquireHandle(kvs []kv.KeyValue, labelPtr *label.Set) * | ||||
| 	} | ||||
| 	rec.refMapped = refcountMapped{value: 2} | ||||
| 	rec.inst = s | ||||
| 	rec.recorder = s.meter.integrator.AggregatorFor(&s.descriptor) | ||||
|  | ||||
| 	s.meter.integrator.AggregatorFor(&s.descriptor, &rec.current, &rec.checkpoint) | ||||
|  | ||||
| 	for { | ||||
| 		// Load/Store: there's a memory allocation to place `mk` into | ||||
| @@ -432,7 +435,21 @@ func (m *Accumulator) observeAsyncInstruments(ctx context.Context) int { | ||||
| } | ||||
|  | ||||
| func (m *Accumulator) checkpointRecord(r *record) int { | ||||
| 	return m.checkpoint(&r.inst.descriptor, r.recorder, r.labels) | ||||
| 	if r.current == nil { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	err := r.current.SynchronizedCopy(r.checkpoint, &r.inst.descriptor) | ||||
| 	if err != nil { | ||||
| 		global.Handle(err) | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	exportRecord := export.NewRecord(&r.inst.descriptor, r.labels, m.resource, r.checkpoint) | ||||
| 	err = m.integrator.Process(exportRecord) | ||||
| 	if err != nil { | ||||
| 		global.Handle(err) | ||||
| 	} | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func (m *Accumulator) checkpointAsync(a *asyncInstrument) int { | ||||
| @@ -444,7 +461,14 @@ func (m *Accumulator) checkpointAsync(a *asyncInstrument) int { | ||||
| 		lrec := lrec | ||||
| 		epochDiff := m.currentEpoch - lrec.observedEpoch | ||||
| 		if epochDiff == 0 { | ||||
| 			checkpointed += m.checkpoint(&a.descriptor, lrec.recorder, lrec.labels) | ||||
| 			if lrec.observed != nil { | ||||
| 				exportRecord := export.NewRecord(&a.descriptor, lrec.labels, m.resource, lrec.observed) | ||||
| 				err := m.integrator.Process(exportRecord) | ||||
| 				if err != nil { | ||||
| 					global.Handle(err) | ||||
| 				} | ||||
| 				checkpointed++ | ||||
| 			} | ||||
| 		} else if epochDiff > 1 { | ||||
| 			// This is second collection cycle with no | ||||
| 			// observations for this labelset. Remove the | ||||
| @@ -458,20 +482,6 @@ func (m *Accumulator) checkpointAsync(a *asyncInstrument) int { | ||||
| 	return checkpointed | ||||
| } | ||||
|  | ||||
| func (m *Accumulator) checkpoint(descriptor *metric.Descriptor, recorder export.Aggregator, labels *label.Set) int { | ||||
| 	if recorder == nil { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	recorder.Checkpoint(descriptor) | ||||
|  | ||||
| 	exportRecord := export.NewRecord(descriptor, labels, m.resource, recorder) | ||||
| 	err := m.integrator.Process(exportRecord) | ||||
| 	if err != nil { | ||||
| 		global.Handle(err) | ||||
| 	} | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| // RecordBatch enters a batch of metric events. | ||||
| func (m *Accumulator) RecordBatch(ctx context.Context, kvs []kv.KeyValue, measurements ...api.Measurement) { | ||||
| 	// Labels will be computed the first time acquireHandle is | ||||
| @@ -498,7 +508,7 @@ func (m *Accumulator) RecordBatch(ctx context.Context, kvs []kv.KeyValue, measur | ||||
|  | ||||
| // RecordOne implements api.SyncImpl. | ||||
| func (r *record) RecordOne(ctx context.Context, number api.Number) { | ||||
| 	if r.recorder == nil { | ||||
| 	if r.current == nil { | ||||
| 		// The instrument is disabled according to the AggregationSelector. | ||||
| 		return | ||||
| 	} | ||||
| @@ -506,7 +516,7 @@ func (r *record) RecordOne(ctx context.Context, number api.Number) { | ||||
| 		global.Handle(err) | ||||
| 		return | ||||
| 	} | ||||
| 	if err := r.recorder.Update(ctx, number, &r.inst.descriptor); err != nil { | ||||
| 	if err := r.current.Update(ctx, number, &r.inst.descriptor); err != nil { | ||||
| 		global.Handle(err) | ||||
| 		return | ||||
| 	} | ||||
|   | ||||
| @@ -79,38 +79,57 @@ func NewWithHistogramDistribution(boundaries []float64) export.AggregationSelect | ||||
| 	return selectorHistogram{boundaries: boundaries} | ||||
| } | ||||
|  | ||||
| func (selectorInexpensive) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| 	switch descriptor.MetricKind() { | ||||
| 	case metric.ValueObserverKind, metric.ValueRecorderKind: | ||||
| 		return minmaxsumcount.New(descriptor) | ||||
| 	default: | ||||
| 		return sum.New() | ||||
| func sumAggs(aggPtrs []*export.Aggregator) { | ||||
| 	aggs := sum.New(len(aggPtrs)) | ||||
| 	for i := range aggPtrs { | ||||
| 		*aggPtrs[i] = &aggs[i] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s selectorSketch) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| func (selectorInexpensive) AggregatorFor(descriptor *metric.Descriptor, aggPtrs ...*export.Aggregator) { | ||||
| 	switch descriptor.MetricKind() { | ||||
| 	case metric.ValueObserverKind, metric.ValueRecorderKind: | ||||
| 		return ddsketch.New(descriptor, s.config) | ||||
| 		aggs := minmaxsumcount.New(len(aggPtrs), descriptor) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	default: | ||||
| 		return sum.New() | ||||
| 		sumAggs(aggPtrs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (selectorExact) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| func (s selectorSketch) AggregatorFor(descriptor *metric.Descriptor, aggPtrs ...*export.Aggregator) { | ||||
| 	switch descriptor.MetricKind() { | ||||
| 	case metric.ValueObserverKind, metric.ValueRecorderKind: | ||||
| 		return array.New() | ||||
| 		aggs := ddsketch.New(len(aggPtrs), descriptor, s.config) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	default: | ||||
| 		return sum.New() | ||||
| 		sumAggs(aggPtrs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s selectorHistogram) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| func (selectorExact) AggregatorFor(descriptor *metric.Descriptor, aggPtrs ...*export.Aggregator) { | ||||
| 	switch descriptor.MetricKind() { | ||||
| 	case metric.ValueObserverKind, metric.ValueRecorderKind: | ||||
| 		return histogram.New(descriptor, s.boundaries) | ||||
| 		aggs := array.New(len(aggPtrs)) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	default: | ||||
| 		return sum.New() | ||||
| 		sumAggs(aggPtrs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s selectorHistogram) AggregatorFor(descriptor *metric.Descriptor, aggPtrs ...*export.Aggregator) { | ||||
| 	switch descriptor.MetricKind() { | ||||
| 	case metric.ValueObserverKind, metric.ValueRecorderKind: | ||||
| 		aggs := histogram.New(len(aggPtrs), descriptor, s.boundaries) | ||||
| 		for i := range aggPtrs { | ||||
| 			*aggPtrs[i] = &aggs[i] | ||||
| 		} | ||||
| 	default: | ||||
| 		sumAggs(aggPtrs) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import ( | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/metric" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/array" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" | ||||
| @@ -34,30 +35,36 @@ var ( | ||||
| 	testValueObserverDesc = metric.NewDescriptor("valueobserver", metric.ValueObserverKind, metric.Int64NumberKind) | ||||
| ) | ||||
|  | ||||
| func oneAgg(sel export.AggregationSelector, desc *metric.Descriptor) export.Aggregator { | ||||
| 	var agg export.Aggregator | ||||
| 	sel.AggregatorFor(desc, &agg) | ||||
| 	return agg | ||||
| } | ||||
|  | ||||
| func TestInexpensiveDistribution(t *testing.T) { | ||||
| 	inex := simple.NewWithInexpensiveDistribution() | ||||
| 	require.NotPanics(t, func() { _ = inex.AggregatorFor(&testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = inex.AggregatorFor(&testValueRecorderDesc).(*minmaxsumcount.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = inex.AggregatorFor(&testValueObserverDesc).(*minmaxsumcount.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(inex, &testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(inex, &testValueRecorderDesc).(*minmaxsumcount.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(inex, &testValueObserverDesc).(*minmaxsumcount.Aggregator) }) | ||||
| } | ||||
|  | ||||
| func TestSketchDistribution(t *testing.T) { | ||||
| 	sk := simple.NewWithSketchDistribution(ddsketch.NewDefaultConfig()) | ||||
| 	require.NotPanics(t, func() { _ = sk.AggregatorFor(&testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = sk.AggregatorFor(&testValueRecorderDesc).(*ddsketch.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = sk.AggregatorFor(&testValueObserverDesc).(*ddsketch.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(sk, &testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(sk, &testValueRecorderDesc).(*ddsketch.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(sk, &testValueObserverDesc).(*ddsketch.Aggregator) }) | ||||
| } | ||||
|  | ||||
| func TestExactDistribution(t *testing.T) { | ||||
| 	ex := simple.NewWithExactDistribution() | ||||
| 	require.NotPanics(t, func() { _ = ex.AggregatorFor(&testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = ex.AggregatorFor(&testValueRecorderDesc).(*array.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = ex.AggregatorFor(&testValueObserverDesc).(*array.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(ex, &testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(ex, &testValueRecorderDesc).(*array.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(ex, &testValueObserverDesc).(*array.Aggregator) }) | ||||
| } | ||||
|  | ||||
| func TestHistogramDistribution(t *testing.T) { | ||||
| 	ex := simple.NewWithHistogramDistribution(nil) | ||||
| 	require.NotPanics(t, func() { _ = ex.AggregatorFor(&testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = ex.AggregatorFor(&testValueRecorderDesc).(*histogram.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = ex.AggregatorFor(&testValueObserverDesc).(*histogram.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(ex, &testCounterDesc).(*sum.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(ex, &testValueRecorderDesc).(*histogram.Aggregator) }) | ||||
| 	require.NotPanics(t, func() { _ = oneAgg(ex, &testValueObserverDesc).(*histogram.Aggregator) }) | ||||
| } | ||||
|   | ||||
| @@ -36,8 +36,7 @@ import ( | ||||
| 	api "go.opentelemetry.io/otel/api/metric" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregation" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/sum" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/integrator/test" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -59,6 +58,8 @@ type ( | ||||
| 		impl     testImpl | ||||
| 		T        *testing.T | ||||
|  | ||||
| 		export.AggregationSelector | ||||
|  | ||||
| 		lock  sync.Mutex | ||||
| 		lused map[string]bool | ||||
|  | ||||
| @@ -244,18 +245,6 @@ func (f *testFixture) preCollect() { | ||||
| 	f.dupCheck = map[testKey]int{} | ||||
| } | ||||
|  | ||||
| func (*testFixture) AggregatorFor(descriptor *metric.Descriptor) export.Aggregator { | ||||
| 	name := descriptor.Name() | ||||
| 	switch { | ||||
| 	case strings.HasSuffix(name, "counter"): | ||||
| 		return sum.New() | ||||
| 	case strings.HasSuffix(name, "lastvalue"): | ||||
| 		return lastvalue.New() | ||||
| 	default: | ||||
| 		panic("Not implemented for this test") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*testFixture) CheckpointSet() export.CheckpointSet { | ||||
| 	return nil | ||||
| } | ||||
| @@ -301,9 +290,10 @@ func stressTest(t *testing.T, impl testImpl) { | ||||
| 	ctx := context.Background() | ||||
| 	t.Parallel() | ||||
| 	fixture := &testFixture{ | ||||
| 		T:     t, | ||||
| 		impl:  impl, | ||||
| 		lused: map[string]bool{}, | ||||
| 		T:                   t, | ||||
| 		impl:                impl, | ||||
| 		lused:               map[string]bool{}, | ||||
| 		AggregationSelector: test.AggregationSelector(), | ||||
| 	} | ||||
| 	cc := concurrency() | ||||
| 	sdk := NewAccumulator(fixture) | ||||
| @@ -353,7 +343,7 @@ func float64sEqual(a, b api.Number) bool { | ||||
| func intCounterTestImpl() testImpl { | ||||
| 	return testImpl{ | ||||
| 		newInstrument: func(meter api.Meter, name string) SyncImpler { | ||||
| 			return Must(meter).NewInt64Counter(name + ".counter") | ||||
| 			return Must(meter).NewInt64Counter(name + ".sum") | ||||
| 		}, | ||||
| 		getUpdateValue: func() api.Number { | ||||
| 			for { | ||||
| @@ -391,7 +381,7 @@ func TestStressInt64Counter(t *testing.T) { | ||||
| func floatCounterTestImpl() testImpl { | ||||
| 	return testImpl{ | ||||
| 		newInstrument: func(meter api.Meter, name string) SyncImpler { | ||||
| 			return Must(meter).NewFloat64Counter(name + ".counter") | ||||
| 			return Must(meter).NewFloat64Counter(name + ".sum") | ||||
| 		}, | ||||
| 		getUpdateValue: func() api.Number { | ||||
| 			for { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user