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