Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the Exponential Histogram and other aggregators for review LS-29757 #174

Merged
merged 10 commits into from
Jun 9, 2022
128 changes: 128 additions & 0 deletions lightstep/sdk/metric/aggregator/aggregation/aggregation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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.

//go:generate stringer -type=Category,Kind

package aggregation // import "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator/aggregation"

import (
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/number"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/sdkinstrument"
)

// These interfaces describe the various ways to access state from an
// Aggregation.

type (
// Aggregation is an interface returned by the Aggregator
// containing an interval of metric data.
Aggregation interface {
Kind() Kind
}

// HasASum
HasASum interface {
Sum() number.Number
}

// Sum returns an aggregated sum.
Sum interface {
// Review NOTE: Should this be Total() or Value()?
HasASum
IsMonotonic() bool
}

// Gauge returns the latest value that was aggregated.
Gauge interface {
Aggregation

// Review NOTE: Should this be LastValue() or Value()?
Gauge() number.Number
}

// ExponentialHistogram returns the count of events in
jmacd marked this conversation as resolved.
Show resolved Hide resolved
// exponential-scale buckets defined as a function of a
// scale parameter. See a detailed explanation in the
// OpenTelemetry metrics data model:
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#exponentialhistogram
Histogram interface {
Aggregation
Count() uint64
HasASum
Scale() int32
ZeroCount() uint64
Positive() Buckets
Negative() Buckets
}

// ExponentialBuckets describes a range of consecutive
jmacd marked this conversation as resolved.
Show resolved Hide resolved
// buckets, starting at Offset(). This type is used to encode
// either the positive or negative ranges of an ExponentialHistogram.
Buckets interface {
Offset() int32
Len() uint32
At(uint32) uint64
}
)

// Category constants describe semantic kind. For the histogram
// category there are multiple implementations, for those distinctions
// as well as Drop, use Kind.
type Category int

const (
UndefinedCategory Category = iota
MonotonicSumCategory
NonMonotonicSumCategory
GaugeCategory
HistogramCategory
)

type Kind int

const (
UndefinedKind Kind = iota
DropKind
AnySumKind
MonotonicSumKind
NonMonotonicSumKind
GaugeKind
HistogramKind
)

func (k Kind) Category(ik sdkinstrument.Kind) Category {
switch k {
case AnySumKind:
switch ik {
case sdkinstrument.HistogramKind, sdkinstrument.CounterKind, sdkinstrument.CounterObserverKind:
return MonotonicSumCategory
case sdkinstrument.UpDownCounterKind, sdkinstrument.UpDownCounterObserverKind:
return NonMonotonicSumCategory
}
return UndefinedCategory
case MonotonicSumKind:
return MonotonicSumCategory
case NonMonotonicSumKind:
return NonMonotonicSumCategory
case GaugeKind:
return GaugeCategory
case HistogramKind:
return HistogramCategory
default:
return UndefinedCategory
}
}

// KindSelector is a per-instrument-kind Kind choice.
type KindSelector func(sdkinstrument.Kind) Kind
50 changes: 50 additions & 0 deletions lightstep/sdk/metric/aggregator/aggregation/category_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions lightstep/sdk/metric/aggregator/aggregation/temporality.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.

//go:generate stringer -type=Temporality

package aggregation // import "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator/aggregation"

import "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/sdkinstrument"

type Temporality uint8

const (
// UndefinedTemporality indicates that temporality is not defined.
UndefinedTemporality Temporality = 0

// CumulativeTemporality indicates that an Exporter expects a
// Cumulative Aggregation.
CumulativeTemporality Temporality = 1

// DeltaTemporality indicates that an Exporter expects a
// Delta Aggregation.
DeltaTemporality Temporality = 2
)

type TemporalitySelector func(sdkinstrument.Kind) Temporality

type TemporalityTrait interface {
Temporality() Temporality
}

type DeltaTemporalityTrait struct{}
type CumulativeTemporalityTrait struct{}

func (DeltaTemporalityTrait) Temporality() Temporality {
return DeltaTemporality
}

func (CumulativeTemporalityTrait) Temporality() Temporality {
return CumulativeTemporality
}
25 changes: 25 additions & 0 deletions lightstep/sdk/metric/aggregator/aggregation/temporality_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 113 additions & 0 deletions lightstep/sdk/metric/aggregator/aggregator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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 aggregator // import "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator"

import (
"fmt"

"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator/aggregation"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/number"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/sdkinstrument"
"go.opentelemetry.io/otel"
)

// Sentinel errors for Aggregator interface.
var (
ErrNegativeInput = fmt.Errorf("negative value is out of range for this instrument")
ErrNaNInput = fmt.Errorf("NaN value is an invalid input")
ErrInfInput = fmt.Errorf("±Inf value is an invalid input")
)

// RangeTest is a common routine for testing for valid input values.
// This rejects NaN and Inf values. This rejects negative values when the
// aggregation does not support negative values, including
// monotonic counter metrics and Histogram metrics.
func RangeTest[N number.Any, Traits number.Traits[N]](num N, kind aggregation.Category) bool {
var traits Traits

if traits.IsInf(num) {
otel.Handle(ErrInfInput)
return false
}

if traits.IsNaN(num) {
otel.Handle(ErrNaNInput)
return false
}

// Check for negative values
switch kind {
case aggregation.MonotonicSumCategory,
aggregation.HistogramCategory:
if num < 0 {
otel.Handle(ErrNegativeInput)
return false
}
}
return true
}

type HistogramConfig struct {
MaxSize int32
}

type Config struct {
Histogram HistogramConfig
}

// Methods implements a specific aggregation behavior. Methods
// are parameterized by the type of the number (int64, flot64),
jmacd marked this conversation as resolved.
Show resolved Hide resolved
// the Storage (generally an `Storage` struct in the same package),
// and the Config (generally a `Config` struct in the same package).
type Methods[N number.Any, Storage any] interface {
// Init initializes the storage with its configuration.
Init(ptr *Storage, cfg Config)

// Update modifies the aggregator concurrently with respect to
// SynchronizedMove() for single new measurement.
Update(ptr *Storage, number N)

// SynchronizedMove concurrently copies and resets the
// `inputIsReset` aggregator.
SynchronizedMove(inputIsReset, output *Storage)

// Simply reset the storage.
jmacd marked this conversation as resolved.
Show resolved Hide resolved
Reset(ptr *Storage)

// Merge adds the contents of `input` to `output`.
Merge(output, input *Storage)

// SubtractSwap removes the contents of `operand` from `valueToModify`
jmacd marked this conversation as resolved.
Show resolved Hide resolved
SubtractSwap(valueToModify, operandToModify *Storage)

// ToAggregation returns an exporter-ready value.
ToAggregation(ptr *Storage) aggregation.Aggregation

// ToStorage returns the underlying storage of an existing Aggregation.
ToStorage(aggregation.Aggregation) (*Storage, bool)

// Kind returns the Kind of aggregator.
Kind() aggregation.Kind

// HasChange returns true if there have been any (discernible)
// Updates. This tests whether an aggregation has zero sum,
// zero count, or zero difference, depending on the
// aggregation. If the instrument is asynchronous, this will
// be used after subtraction.
HasChange(ptr *Storage) bool
}

// ConfigSelector is a per-instrument-kind, per-number-kind Config choice.
type ConfigSelector func(sdkinstrument.Kind) (int64Config, float64Config Config)