1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-10-31 00:07:40 +02:00
Files
opentelemetry-go/sdk/metric/integrator/simple/simple.go
Joshua MacDonald 4e4271791f Add export timestamps; distinguish Accumulation vs. Record (#835)
* Introduce Accumulation

* Refactor export structs

* FTB exporters

* Test timestamps

* Test no-start case

* From feedback

* Apply suggestions from code review

(Thanks @MrAlias!)

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* Comments in sdk/metric/integrator/test

* Fix build

* Comments and feedback

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
2020-06-18 10:16:33 -07:00

169 lines
4.3 KiB
Go

// 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 simple // import "go.opentelemetry.io/otel/sdk/metric/integrator/simple"
import (
"errors"
"fmt"
"sync"
"time"
"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/resource"
)
type (
Integrator struct {
export.AggregationSelector
stateful bool
batch
}
batchKey struct {
descriptor *metric.Descriptor
distinct label.Distinct
resource label.Distinct
}
batchValue struct {
aggregator export.Aggregator
labels *label.Set
resource *resource.Resource
}
batch struct {
// RWMutex implements locking for the `CheckpointSet` interface.
sync.RWMutex
values map[batchKey]batchValue
// Note: the timestamp logic currently assumes all
// exports are deltas.
intervalStart time.Time
intervalEnd time.Time
// startedCollection and finishedCollection are the
// number of StartCollection() and FinishCollection()
// calls, used to ensure that the sequence of starts
// and finishes are correctly balanced.
startedCollection int64
finishedCollection int64
}
)
var _ export.Integrator = &Integrator{}
var _ export.CheckpointSet = &batch{}
var ErrInconsistentState = fmt.Errorf("inconsistent integrator state")
func New(selector export.AggregationSelector, stateful bool) *Integrator {
return &Integrator{
AggregationSelector: selector,
stateful: stateful,
batch: batch{
values: map[batchKey]batchValue{},
intervalStart: time.Now(),
},
}
}
func (b *Integrator) Process(accumulation export.Accumulation) error {
if b.startedCollection != b.finishedCollection+1 {
return ErrInconsistentState
}
desc := accumulation.Descriptor()
key := batchKey{
descriptor: desc,
distinct: accumulation.Labels().Equivalent(),
resource: accumulation.Resource().Equivalent(),
}
agg := accumulation.Aggregator()
value, ok := b.batch.values[key]
if ok {
// Note: The call to Merge here combines only
// identical accumulations. It is required even for a
// stateless Integrator because such identical accumulations
// may arise in the Meter implementation due to race
// conditions.
return value.aggregator.Merge(agg, desc)
}
// If this integrator is stateful, create a copy of the
// Aggregator for long-term storage. Otherwise the
// Meter implementation will checkpoint the aggregator
// again, overwriting the long-lived state.
if b.stateful {
tmp := agg
// Note: the call to AggregatorFor() followed by Merge
// is effectively a Clone() operation.
b.AggregatorFor(desc, &agg)
if err := agg.Merge(tmp, desc); err != nil {
return err
}
}
b.batch.values[key] = batchValue{
aggregator: agg,
labels: accumulation.Labels(),
resource: accumulation.Resource(),
}
return nil
}
func (b *Integrator) CheckpointSet() export.CheckpointSet {
return &b.batch
}
func (b *Integrator) StartCollection() {
if b.startedCollection != 0 {
b.intervalStart = b.intervalEnd
}
b.startedCollection++
if !b.stateful {
b.batch.values = map[batchKey]batchValue{}
}
}
func (b *Integrator) FinishCollection() error {
b.finishedCollection++
b.intervalEnd = time.Now()
if b.startedCollection != b.finishedCollection {
return ErrInconsistentState
}
return nil
}
func (b *batch) ForEach(f func(export.Record) error) error {
if b.startedCollection != b.finishedCollection {
return ErrInconsistentState
}
for key, value := range b.values {
if err := f(export.NewRecord(
key.descriptor,
value.labels,
value.resource,
value.aggregator.Aggregation(),
b.intervalStart,
b.intervalEnd,
)); err != nil && !errors.Is(err, aggregation.ErrNoData) {
return err
}
}
return nil
}