Skip to content

Commit

Permalink
Add the Exponential Histogram Aggregator. (#4245)
Browse files Browse the repository at this point in the history
* Adds Exponential Histograms aggregator

* Added aggregation to the pipeline.

Adjust to new bucket

* Add no allocation if cap is available.

* Expand tests

* Fix lint

* Fix 64 bit math on 386 platform.

* Fix tests to work in go 1.19.
Fix spelling error

* fix codespell

* Add example

* Update sdk/metric/aggregation/aggregation.go

Co-authored-by: Robert Pająk <pellared@hotmail.com>

* Update sdk/metric/aggregation/aggregation.go

* Update sdk/metric/aggregation/aggregation.go

* Changelog

* Fix move

* Address feedback from the PR.

* Update expo histo to new aggregator format.

* Fix lint

* Remove Zero Threshold from config of expo histograms

* Remove DefaultExponentialHistogram()

* Refactor GetBin, and address PR Feedback

* Address PR feedback

* Fix comment in wrong location

* Fix misapplied PR feedback

* Fix codespell

---------

Co-authored-by: Robert Pająk <pellared@hotmail.com>
Co-authored-by: Chester Cheung <cheung.zhy.csu@gmail.com>
  • Loading branch information
3 people committed Aug 4, 2023
1 parent f67ecb3 commit 248413d
Show file tree
Hide file tree
Showing 8 changed files with 1,551 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Add `ManualReader` struct in `go.opentelemetry.io/otel/sdk/metric`. (#4244)
- Add `PeriodicReader` struct in `go.opentelemetry.io/otel/sdk/metric`. (#4244)
- Add support for exponential histogram aggregations.
A histogram can be configured as an exponential histogram using a view with `go.opentelemetry.io/otel/sdk/metric/aggregation.ExponentialHistogram` as the aggregation. (#4245)
- Add `Exporter` struct in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#4272)
- Add `Exporter` struct in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4272)
- OTLP Metrics Exporter now supports the `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` environment variable. (#4287)
Expand Down
55 changes: 55 additions & 0 deletions sdk/metric/aggregation/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,58 @@ func (h ExplicitBucketHistogram) Copy() Aggregation {
NoMinMax: h.NoMinMax,
}
}

// Base2ExponentialHistogram is an aggregation that summarizes a set of
// measurements as an histogram with bucket widths that grow exponentially.
type Base2ExponentialHistogram struct {
// MaxSize is the maximum number of buckets to use for the histogram.
MaxSize int32
// MaxScale is the maximum resolution scale to use for the histogram.
//
// MaxScale has a maximum value of 20. Using a value of 20 means the
// maximum number of buckets that can fit within the range of a
// signed 32-bit integer index could be used.
//
// MaxScale has a minimum value of -10. Using a value of -10 means only
// two buckets will be use.
MaxScale int32

// NoMinMax indicates whether to not record the min and max of the
// distribution. By default, these extrema are recorded.
//
// Recording these extrema for cumulative data is expected to have little
// value, they will represent the entire life of the instrument instead of
// just the current collection cycle. It is recommended to set this to true
// for that type of data to avoid computing the low-value extrema.
NoMinMax bool
}

var _ Aggregation = Base2ExponentialHistogram{}

// private attempts to ensure no user-defined Aggregation is allowed. The
// OTel specification does not allow user-defined Aggregation currently.
func (e Base2ExponentialHistogram) private() {}

// Copy returns a deep copy of the Aggregation.
func (e Base2ExponentialHistogram) Copy() Aggregation {
return e
}

const (
expoMaxScale = 20
expoMinScale = -10
)

// errExpoHist is returned by misconfigured Base2ExponentialBucketHistograms.
var errExpoHist = fmt.Errorf("%w: exponential histogram", errAgg)

// Err returns an error for any misconfigured Aggregation.
func (e Base2ExponentialHistogram) Err() error {
if e.MaxScale > expoMaxScale {
return fmt.Errorf("%w: max size %d is greater than maximum scale %d", errExpoHist, e.MaxSize, expoMaxScale)
}
if e.MaxSize <= 0 {
return fmt.Errorf("%w: max size %d is less than or equal to zero", errExpoHist, e.MaxSize)
}
return nil
}
28 changes: 28 additions & 0 deletions sdk/metric/aggregation/aggregation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ func TestAggregationErr(t *testing.T) {
Boundaries: []float64{0, 1, 2, 1, 3, 4},
}.Err(), errAgg)
})

t.Run("ExponentialHistogramOperation", func(t *testing.T) {
assert.NoError(t, Base2ExponentialHistogram{
MaxSize: 160,
MaxScale: 20,
}.Err())

assert.NoError(t, Base2ExponentialHistogram{
MaxSize: 1,
NoMinMax: true,
}.Err())

assert.NoError(t, Base2ExponentialHistogram{
MaxSize: 1024,
MaxScale: -3,
}.Err())
})

t.Run("InvalidExponentialHistogramOperation", func(t *testing.T) {
// MazSize must be greater than 0
assert.ErrorIs(t, Base2ExponentialHistogram{}.Err(), errAgg)

// MaxScale Must be <=20
assert.ErrorIs(t, Base2ExponentialHistogram{
MaxSize: 1,
MaxScale: 30,
}.Err(), errAgg)
})
}

func TestExplicitBucketHistogramDeepCopy(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions sdk/metric/internal/aggregate/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ func (b Builder[N]) ExplicitBucketHistogram(cfg aggregation.ExplicitBucketHistog
}
}

// ExponentialBucketHistogram returns a histogram aggregate function input and
// output.
func (b Builder[N]) ExponentialBucketHistogram(cfg aggregation.Base2ExponentialHistogram, noSum bool) (Measure[N], ComputeAggregation) {
h := newExponentialHistogram[N](cfg, noSum)
switch b.Temporality {
case metricdata.DeltaTemporality:
return b.filter(h.measure), h.delta
default:
return b.filter(h.measure), h.cumulative
}
}

// reset ensures s has capacity and sets it length. If the capacity of s too
// small, a new slice is returned with the specified capacity and length.
func reset[T any](s []T, length, capacity int) []T {
Expand Down

0 comments on commit 248413d

Please sign in to comment.