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 minmaxsumcount // import "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
16
17import (
18	"context"
19	"sync"
20
21	"go.opentelemetry.io/otel/metric"
22	"go.opentelemetry.io/otel/metric/number"
23	export "go.opentelemetry.io/otel/sdk/export/metric"
24	"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
25	"go.opentelemetry.io/otel/sdk/metric/aggregator"
26)
27
28type (
29	// Aggregator aggregates events that form a distribution,
30	// keeping only the min, max, sum, and count.
31	Aggregator struct {
32		lock sync.Mutex
33		kind number.Kind
34		state
35	}
36
37	state struct {
38		sum   number.Number
39		min   number.Number
40		max   number.Number
41		count uint64
42	}
43)
44
45var _ export.Aggregator = &Aggregator{}
46var _ aggregation.MinMaxSumCount = &Aggregator{}
47
48// New returns a new aggregator for computing the min, max, sum, and
49// count.
50//
51// This type uses a mutex for Update() and SynchronizedMove() concurrency.
52func New(cnt int, desc *metric.Descriptor) []Aggregator {
53	kind := desc.NumberKind()
54	aggs := make([]Aggregator, cnt)
55	for i := range aggs {
56		aggs[i] = Aggregator{
57			kind:  kind,
58			state: emptyState(kind),
59		}
60	}
61	return aggs
62}
63
64// Aggregation returns an interface for reading the state of this aggregator.
65func (c *Aggregator) Aggregation() aggregation.Aggregation {
66	return c
67}
68
69// Kind returns aggregation.MinMaxSumCountKind.
70func (c *Aggregator) Kind() aggregation.Kind {
71	return aggregation.MinMaxSumCountKind
72}
73
74// Sum returns the sum of values in the checkpoint.
75func (c *Aggregator) Sum() (number.Number, error) {
76	return c.sum, nil
77}
78
79// Count returns the number of values in the checkpoint.
80func (c *Aggregator) Count() (uint64, error) {
81	return c.count, nil
82}
83
84// Min returns the minimum value in the checkpoint.
85// The error value aggregation.ErrNoData will be returned
86// if there were no measurements recorded during the checkpoint.
87func (c *Aggregator) Min() (number.Number, error) {
88	if c.count == 0 {
89		return 0, aggregation.ErrNoData
90	}
91	return c.min, nil
92}
93
94// Max returns the maximum value in the checkpoint.
95// The error value aggregation.ErrNoData will be returned
96// if there were no measurements recorded during the checkpoint.
97func (c *Aggregator) Max() (number.Number, error) {
98	if c.count == 0 {
99		return 0, aggregation.ErrNoData
100	}
101	return c.max, nil
102}
103
104// SynchronizedMove saves the current state into oa and resets the current state to
105// the empty set.
106func (c *Aggregator) SynchronizedMove(oa export.Aggregator, desc *metric.Descriptor) error {
107	o, _ := oa.(*Aggregator)
108
109	if oa != nil && o == nil {
110		return aggregator.NewInconsistentAggregatorError(c, oa)
111	}
112	c.lock.Lock()
113	if o != nil {
114		o.state = c.state
115	}
116	c.state = emptyState(c.kind)
117	c.lock.Unlock()
118
119	return nil
120}
121
122func emptyState(kind number.Kind) state {
123	return state{
124		count: 0,
125		sum:   0,
126		min:   kind.Maximum(),
127		max:   kind.Minimum(),
128	}
129}
130
131// Update adds the recorded measurement to the current data set.
132func (c *Aggregator) Update(_ context.Context, number number.Number, desc *metric.Descriptor) error {
133	kind := desc.NumberKind()
134
135	c.lock.Lock()
136	defer c.lock.Unlock()
137	c.count++
138	c.sum.AddNumber(kind, number)
139	if number.CompareNumber(kind, c.min) < 0 {
140		c.min = number
141	}
142	if number.CompareNumber(kind, c.max) > 0 {
143		c.max = number
144	}
145	return nil
146}
147
148// Merge combines two data sets into one.
149func (c *Aggregator) Merge(oa export.Aggregator, desc *metric.Descriptor) error {
150	o, _ := oa.(*Aggregator)
151	if o == nil {
152		return aggregator.NewInconsistentAggregatorError(c, oa)
153	}
154
155	c.count += o.count
156	c.sum.AddNumber(desc.NumberKind(), o.sum)
157
158	if c.min.CompareNumber(desc.NumberKind(), o.min) > 0 {
159		c.min.SetNumber(o.min)
160	}
161	if c.max.CompareNumber(desc.NumberKind(), o.max) < 0 {
162		c.max.SetNumber(o.max)
163	}
164	return nil
165}
166