1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package metrictest // import "go.opentelemetry.io/otel/sdk/export/metric/metrictest" 16 17import ( 18 "context" 19 "errors" 20 "reflect" 21 "sync" 22 "time" 23 24 "go.opentelemetry.io/otel/attribute" 25 "go.opentelemetry.io/otel/metric" 26 "go.opentelemetry.io/otel/metric/number" 27 export "go.opentelemetry.io/otel/sdk/export/metric" 28 "go.opentelemetry.io/otel/sdk/export/metric/aggregation" 29 "go.opentelemetry.io/otel/sdk/resource" 30) 31 32type mapkey struct { 33 desc *metric.Descriptor 34 distinct attribute.Distinct 35} 36 37// CheckpointSet is useful for testing Exporters. 38// TODO(#872): Uses of this can be replaced by processortest.Output. 39type CheckpointSet struct { 40 sync.RWMutex 41 records map[mapkey]export.Record 42 updates []export.Record 43 resource *resource.Resource 44} 45 46// NoopAggregator is useful for testing Exporters. 47type NoopAggregator struct{} 48 49var _ export.Aggregator = (*NoopAggregator)(nil) 50 51// Update implements export.Aggregator. 52func (NoopAggregator) Update(context.Context, number.Number, *metric.Descriptor) error { 53 return nil 54} 55 56// SynchronizedMove implements export.Aggregator. 57func (NoopAggregator) SynchronizedMove(export.Aggregator, *metric.Descriptor) error { 58 return nil 59} 60 61// Merge implements export.Aggregator. 62func (NoopAggregator) Merge(export.Aggregator, *metric.Descriptor) error { 63 return nil 64} 65 66// Aggregation returns an interface for reading the state of this aggregator. 67func (NoopAggregator) Aggregation() aggregation.Aggregation { 68 return NoopAggregator{} 69} 70 71// Kind implements aggregation.Aggregation. 72func (NoopAggregator) Kind() aggregation.Kind { 73 return aggregation.Kind("Noop") 74} 75 76// NewCheckpointSet returns a test CheckpointSet that new records could be added. 77// Records are grouped by their encoded labels. 78func NewCheckpointSet(resource *resource.Resource) *CheckpointSet { 79 return &CheckpointSet{ 80 records: make(map[mapkey]export.Record), 81 resource: resource, 82 } 83} 84 85// Reset clears the Aggregator state. 86func (p *CheckpointSet) Reset() { 87 p.records = make(map[mapkey]export.Record) 88 p.updates = nil 89} 90 91// Add a new record to a CheckpointSet. 92// 93// If there is an existing record with the same descriptor and labels, 94// the stored aggregator will be returned and should be merged. 95func (p *CheckpointSet) Add(desc *metric.Descriptor, newAgg export.Aggregator, labels ...attribute.KeyValue) (agg export.Aggregator, added bool) { 96 elabels := attribute.NewSet(labels...) 97 98 key := mapkey{ 99 desc: desc, 100 distinct: elabels.Equivalent(), 101 } 102 if record, ok := p.records[key]; ok { 103 return record.Aggregation().(export.Aggregator), false 104 } 105 106 rec := export.NewRecord(desc, &elabels, p.resource, newAgg.Aggregation(), time.Time{}, time.Time{}) 107 p.updates = append(p.updates, rec) 108 p.records[key] = rec 109 return newAgg, true 110} 111 112// ForEach does not use ExportKindSelected: use a real Processor to 113// test ExportKind functionality. 114func (p *CheckpointSet) ForEach(_ export.ExportKindSelector, f func(export.Record) error) error { 115 for _, r := range p.updates { 116 if err := f(r); err != nil && !errors.Is(err, aggregation.ErrNoData) { 117 return err 118 } 119 } 120 return nil 121} 122 123// Takes a slice of []some.Aggregator and returns a slice of []export.Aggregator 124func Unslice2(sl interface{}) (one, two export.Aggregator) { 125 slv := reflect.ValueOf(sl) 126 if slv.Type().Kind() != reflect.Slice { 127 panic("Invalid Unslice2") 128 } 129 if slv.Len() != 2 { 130 panic("Invalid Unslice2: length > 2") 131 } 132 one = slv.Index(0).Addr().Interface().(export.Aggregator) 133 two = slv.Index(1).Addr().Interface().(export.Aggregator) 134 return 135} 136