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

Exponential histogram aggregator & export support #2393

Closed
wants to merge 64 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
9f610ba
Working draft of expo histogram with variable scale
jmacd Sep 24, 2021
c6070fb
test circular buffer forward
jmacd Sep 25, 2021
23c7129
up to downscale logic
jmacd Sep 26, 2021
7823119
partial downscale
jmacd Sep 26, 2021
2e4e8b5
rotate, downscale, moveBucket
jmacd Sep 27, 2021
3fb9afb
comment and re-order implementation methods
jmacd Sep 27, 2021
ec33c8e
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Sep 27, 2021
af40924
Update NewDescriptor calls.
jmacd Sep 27, 2021
a03c191
improve test
jmacd Sep 28, 2021
0a3a472
add a test
jmacd Sep 29, 2021
7044ef2
typo
jmacd Sep 29, 2021
83df720
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Oct 5, 2021
06629d3
remove histo
jmacd Oct 5, 2021
b05fe9f
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Oct 8, 2021
c489c48
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Nov 9, 2021
96cffe5
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Nov 9, 2021
8438c54
add a test for aggregation kind
jmacd Nov 9, 2021
c93ede0
import from contrib, it's difficult to keep the exporter and impl in …
jmacd Nov 10, 2021
d47e56f
update test
jmacd Nov 10, 2021
3f4b34c
add merge test
jmacd Nov 10, 2021
3db13d4
pass test
jmacd Nov 15, 2021
1a4cd0d
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Nov 16, 2021
afb52e4
add logarithm test
jmacd Nov 16, 2021
49bd8f6
fix benchmarks
jmacd Nov 16, 2021
28c0a23
tests with UpdateByIncr()
jmacd Nov 17, 2021
d1de2ea
more test coverage
jmacd Nov 18, 2021
7aaffef
move internal
jmacd Nov 18, 2021
cbae649
remove scalb
jmacd Nov 19, 2021
60ea13d
test logarithm boundary cases
jmacd Nov 19, 2021
2782ae3
explain min/max values
jmacd Nov 19, 2021
ab4044d
remove kind field
jmacd Nov 20, 2021
ad23cd5
add MinScale to exponent mapping
jmacd Dec 1, 2021
8123fbc
test logarithm mapping underflow
jmacd Dec 1, 2021
b4d9afa
test fixed scale logic
jmacd Dec 1, 2021
45ce7f6
fix broken tests related to sum a number.Number
jmacd Dec 1, 2021
d8e9870
test integer merge
jmacd Dec 1, 2021
17385de
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Dec 1, 2021
bbe1c1a
fix build
jmacd Dec 2, 2021
e6d9346
lint
jmacd Dec 3, 2021
9d0ec85
test expohisto to OTLP
jmacd Dec 3, 2021
b82c9a0
test integer expohisto logic
jmacd Dec 3, 2021
3cfa061
lint
jmacd Dec 3, 2021
b5c599f
Add a README
jmacd Dec 4, 2021
41bae95
simple selector
jmacd Dec 7, 2021
ff33a6e
hard-code the last boundary value because the formulas misbehave near…
jmacd Dec 7, 2021
dd5e722
map negative scale to correct subnormals in _reverse_ lookup
jmacd Dec 7, 2021
4c38c21
full range test
jmacd Dec 8, 2021
63b78d0
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Dec 8, 2021
c612753
changelog
jmacd Dec 8, 2021
e37e5d8
document the tests, a bit more testing
jmacd Dec 8, 2021
bbf190e
Add comments, benchmarks
jmacd Dec 8, 2021
1e7ff6e
Lint
jmacd Dec 8, 2021
ca5b38d
Comment on subnormals
jmacd Dec 9, 2021
8318ba7
edit readme
jmacd Dec 9, 2021
64f7dea
explain max scale
jmacd Dec 9, 2021
224ad8f
comments & more readme
jmacd Dec 10, 2021
a02bf9b
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Jan 5, 2022
a1dc12f
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
jmacd Jan 6, 2022
b2989ea
remove new test from legacy sdk/export/metric
jmacd Jan 6, 2022
e1399d9
lint
jmacd Jan 6, 2022
ef19ae0
vanity
jmacd Jan 6, 2022
62a88db
markdown lint
jmacd Jan 6, 2022
9f9de49
move the benchmark
jmacd Jan 7, 2022
77d01c9
comments
jmacd Jan 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Support `OTEL_EXPORTER_ZIPKIN_ENDPOINT` env to specify zipkin collector endpoint (#2490)
- Add an exponential histogram aggregator, a corresponding aggregation.Kind, and OTLP exporter support. (TODO)

### Changed

Expand Down
79 changes: 79 additions & 0 deletions exporters/otlp/otlpmetric/internal/metrictransform/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,13 @@ func Record(temporalitySelector aggregation.TemporalitySelector, r export.Record
}
return histogramPoint(r, temporalitySelector.TemporalityFor(r.Descriptor(), aggregation.HistogramKind), h)

case aggregation.ExponentialHistogramKind:
h, ok := agg.(aggregation.ExponentialHistogram)
if !ok {
return nil, fmt.Errorf("%w: %T", ErrIncompatibleAgg, agg)
}
return exponentialHistogramPoint(r, temporalitySelector.TemporalityFor(r.Descriptor(), aggregation.ExponentialHistogramKind), h)

case aggregation.SumKind:
s, ok := agg.(aggregation.Sum)
if !ok {
Expand Down Expand Up @@ -438,3 +445,75 @@ func histogramPoint(record export.Record, temporality aggregation.Temporality, a
}
return m, nil
}

// exponentialHistogramPoint transforms an ExponentialHistogram Aggregator into an OTLP Metric.
func exponentialHistogramPoint(record export.Record, temporality aggregation.Temporality, a aggregation.ExponentialHistogram) (*metricpb.Metric, error) {
desc := record.Descriptor()
labels := record.Labels()

count, err := a.Count()
if err != nil {
return nil, err
}

sum, err := a.Sum()
if err != nil {
return nil, err
}

zeros, err := a.ZeroCount()
if err != nil {
return nil, err
}

scale, err := a.Scale()
if err != nil {
return nil, err
}

point := &metricpb.ExponentialHistogramDataPoint{
Attributes: Iterator(labels.Iter()),
StartTimeUnixNano: toNanos(record.StartTime()),
TimeUnixNano: toNanos(record.EndTime()),
Count: count,
Sum: sum.CoerceToFloat64(desc.NumberKind()),
ZeroCount: zeros,
Scale: scale, // Note: will be zero if all zeros.
}

if pos, err := a.Positive(); pos != nil && err == nil && pos.Len() != 0 {
point.Positive = exponentialHistogramBucket(pos)
} else if err != nil {
return nil, err
}

if neg, err := a.Negative(); neg != nil && err == nil && neg.Len() != 0 {
point.Negative = exponentialHistogramBucket(neg)
} else if err != nil {
return nil, err
}

m := &metricpb.Metric{
Name: desc.Name(),
Description: desc.Description(),
Unit: string(desc.Unit()),
Data: &metricpb.Metric_ExponentialHistogram{
ExponentialHistogram: &metricpb.ExponentialHistogram{
AggregationTemporality: sdkTemporalityToTemporality(temporality),
DataPoints: []*metricpb.ExponentialHistogramDataPoint{point},
},
},
}
return m, nil
}

func exponentialHistogramBucket(b aggregation.ExponentialBuckets) *metricpb.ExponentialHistogramDataPoint_Buckets {
cnts := make([]uint64, b.Len())
for i := range cnts {
cnts[i] = b.At(uint32(i))
}
return &metricpb.ExponentialHistogramDataPoint_Buckets{
Offset: b.Offset(),
BucketCounts: cnts,
}
}
165 changes: 165 additions & 0 deletions exporters/otlp/otlpmetric/internal/metrictransform/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"go.opentelemetry.io/otel/metric/number"
"go.opentelemetry.io/otel/metric/sdkapi"
"go.opentelemetry.io/otel/sdk/metric/aggregator"
"go.opentelemetry.io/otel/sdk/metric/aggregator/exponential"
"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
"go.opentelemetry.io/otel/sdk/metric/aggregator/sum"
"go.opentelemetry.io/otel/sdk/metric/export"
Expand Down Expand Up @@ -321,3 +322,167 @@ func TestRecordAggregatorUnexpectedErrors(t *testing.T) {
require.Nil(t, mpb)
require.True(t, errors.Is(err, errEx))
}

func TestExponentialHistogramDataPoints(t *testing.T) {
type testCase struct {
name string
values []float64
temporality aggregation.Temporality
numberKind number.Kind
expect *metricpb.ExponentialHistogram
}
useAttrs := []attribute.KeyValue{
attribute.String("one", "1"),
}
expectAttrs := []*commonpb.KeyValue{
{Key: "one", Value: &commonpb.AnyValue{Value: &commonpb.AnyValue_StringValue{StringValue: "1"}}},
}

for _, test := range []testCase{
{
"empty",
[]float64{},
aggregation.DeltaTemporality,
number.Float64Kind,
&metricpb.ExponentialHistogram{
AggregationTemporality: otelDelta,
DataPoints: []*metricpb.ExponentialHistogramDataPoint{{
Attributes: expectAttrs,
StartTimeUnixNano: uint64(intervalStart.UnixNano()),
TimeUnixNano: uint64(intervalEnd.UnixNano()),
Count: 0,
ZeroCount: 0,
Sum: 0,
}},
},
},
{
"positive",
[]float64{1, 2, 4, 8},
aggregation.DeltaTemporality,
number.Float64Kind,
&metricpb.ExponentialHistogram{
AggregationTemporality: otelDelta,
DataPoints: []*metricpb.ExponentialHistogramDataPoint{{
Attributes: expectAttrs,
StartTimeUnixNano: uint64(intervalStart.UnixNano()),
TimeUnixNano: uint64(intervalEnd.UnixNano()),

// 1..8 spans 3 orders of magnitide, max-size 2, thus scale=-1
Scale: -1,
Count: 4,
ZeroCount: 0,
Sum: 15,
Positive: &metricpb.ExponentialHistogramDataPoint_Buckets{
Offset: 0,
BucketCounts: []uint64{2, 2},
},
}},
},
},
{
"positive_and_negative",
[]float64{2, 3, -100},
aggregation.DeltaTemporality,
number.Float64Kind,
&metricpb.ExponentialHistogram{
AggregationTemporality: otelDelta,
DataPoints: []*metricpb.ExponentialHistogramDataPoint{{
Attributes: expectAttrs,
StartTimeUnixNano: uint64(intervalStart.UnixNano()),
TimeUnixNano: uint64(intervalEnd.UnixNano()),

// Scale 1 has boundaries at 1, sqrt(2), 2, 2*sqrt(2), ...
Scale: 1,
Count: 3,
ZeroCount: 0,
Sum: -95,
Positive: &metricpb.ExponentialHistogramDataPoint_Buckets{
// Index 2 => 2, Index 3 => 2*sqrt(2)
Offset: 2,
BucketCounts: []uint64{1, 1},
},
Negative: &metricpb.ExponentialHistogramDataPoint_Buckets{
// Index 13 = 2^floor(13/2) * sqrt(2) ~= 90
Offset: 13,
BucketCounts: []uint64{1},
},
}},
},
},
{
// Note: (2**(2**-10))**-100 = 0.9345
// Note: (2**(2**-10))**-101 = 0.9339
// Note: (2**(2**-10))**-102 = 0.9333
"negative and zero",
[]float64{-0.9343, -0.9342, -0.9341, -0.9338, -0.9337, -0.9336, 0, 0, 0, 0},
aggregation.DeltaTemporality,
number.Float64Kind,
&metricpb.ExponentialHistogram{
AggregationTemporality: otelDelta,
DataPoints: []*metricpb.ExponentialHistogramDataPoint{{
Attributes: expectAttrs,
StartTimeUnixNano: uint64(intervalStart.UnixNano()),
TimeUnixNano: uint64(intervalEnd.UnixNano()),

// Scale 1 has boundaries at 1, sqrt(2), 2, 2*sqrt(2), ...
Scale: 10,
Count: 10,
ZeroCount: 4,
Sum: -0.9343 + -0.9342 + -0.9341 + -0.9338 + -0.9337 + -0.9336,
Negative: &metricpb.ExponentialHistogramDataPoint_Buckets{
Offset: -102,
BucketCounts: []uint64{3, 3},
},
}},
},
},
{
"integers cumulative",
// Scale=-1 has base 4,
// index 0 holds values [1, 4), has count 2
// index 1 holds values [4, 15), has count 12
[]float64{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
aggregation.CumulativeTemporality,
number.Float64Kind,
&metricpb.ExponentialHistogram{
AggregationTemporality: otelCumulative,
DataPoints: []*metricpb.ExponentialHistogramDataPoint{{
Attributes: expectAttrs,
StartTimeUnixNano: uint64(intervalStart.UnixNano()),
TimeUnixNano: uint64(intervalEnd.UnixNano()),
Count: 14,
ZeroCount: 0,
Sum: 119,
Scale: -1,
Positive: &metricpb.ExponentialHistogramDataPoint_Buckets{
Offset: 0,
BucketCounts: []uint64{2, 12},
},
}},
},
},
} {
t.Run(test.name, func(t *testing.T) {
desc := metrictest.NewDescriptor("ignore", sdkapi.HistogramInstrumentKind, test.numberKind)
labels := attribute.NewSet(useAttrs...)
agg := &exponential.New(1, &desc, exponential.WithMaxSize(2))[0]

for _, value := range test.values {
var num number.Number
if test.numberKind == number.Float64Kind {
num = number.NewFloat64Number(value)
} else {
num = number.NewInt64Number(int64(value))
}
assert.NoError(t, agg.Update(context.Background(), num, &desc))
}

record := export.NewRecord(&desc, &labels, agg, intervalStart, intervalEnd)

if m, err := exponentialHistogramPoint(record, test.temporality, agg); assert.NoError(t, err) {
assert.Equal(t, test.expect, m.GetExponentialHistogram())
}
})
}
}
27 changes: 27 additions & 0 deletions sdk/export/metric/aggregation/aggregation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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 aggregation

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestAggregationKind(t *testing.T) {
require.Equal(t, "Sum", SumKind.String())
require.Equal(t, "Lastvalue", LastValueKind.String())
require.Equal(t, "Histogram", HistogramKind.String())
}
1 change: 1 addition & 0 deletions sdk/export/metric/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../../metric
replace go.opentelemetry.io/otel/trace => ../../../trace

require (
github.com/stretchr/testify v1.7.0
go.opentelemetry.io/otel v1.3.0
go.opentelemetry.io/otel/metric v0.26.0
go.opentelemetry.io/otel/sdk/metric v0.0.0-00010101000000-000000000000
Expand Down
1 change: 1 addition & 0 deletions sdk/export/metric/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading