From 420953fd941dbc54ef8f4d69a9672b4d0f07fe91 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Tue, 27 Aug 2024 20:19:29 +0000 Subject: [PATCH 01/14] move sdk/metric/internal/exemplar to sdk/metric/exemplar --- sdk/metric/{internal => }/exemplar/doc.go | 0 sdk/metric/{internal => }/exemplar/drop.go | 0 sdk/metric/{internal => }/exemplar/drop_test.go | 0 sdk/metric/{internal => }/exemplar/exemplar.go | 0 sdk/metric/{internal => }/exemplar/filter.go | 0 sdk/metric/{internal => }/exemplar/filter_test.go | 0 sdk/metric/{internal => }/exemplar/filtered_reservoir.go | 0 sdk/metric/{internal => }/exemplar/hist.go | 0 sdk/metric/{internal => }/exemplar/hist_test.go | 0 sdk/metric/{internal => }/exemplar/rand.go | 0 sdk/metric/{internal => }/exemplar/rand_test.go | 0 sdk/metric/{internal => }/exemplar/reservoir.go | 0 sdk/metric/{internal => }/exemplar/reservoir_test.go | 0 sdk/metric/{internal => }/exemplar/storage.go | 0 sdk/metric/{internal => }/exemplar/value.go | 0 sdk/metric/{internal => }/exemplar/value_test.go | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename sdk/metric/{internal => }/exemplar/doc.go (100%) rename sdk/metric/{internal => }/exemplar/drop.go (100%) rename sdk/metric/{internal => }/exemplar/drop_test.go (100%) rename sdk/metric/{internal => }/exemplar/exemplar.go (100%) rename sdk/metric/{internal => }/exemplar/filter.go (100%) rename sdk/metric/{internal => }/exemplar/filter_test.go (100%) rename sdk/metric/{internal => }/exemplar/filtered_reservoir.go (100%) rename sdk/metric/{internal => }/exemplar/hist.go (100%) rename sdk/metric/{internal => }/exemplar/hist_test.go (100%) rename sdk/metric/{internal => }/exemplar/rand.go (100%) rename sdk/metric/{internal => }/exemplar/rand_test.go (100%) rename sdk/metric/{internal => }/exemplar/reservoir.go (100%) rename sdk/metric/{internal => }/exemplar/reservoir_test.go (100%) rename sdk/metric/{internal => }/exemplar/storage.go (100%) rename sdk/metric/{internal => }/exemplar/value.go (100%) rename sdk/metric/{internal => }/exemplar/value_test.go (100%) diff --git a/sdk/metric/internal/exemplar/doc.go b/sdk/metric/exemplar/doc.go similarity index 100% rename from sdk/metric/internal/exemplar/doc.go rename to sdk/metric/exemplar/doc.go diff --git a/sdk/metric/internal/exemplar/drop.go b/sdk/metric/exemplar/drop.go similarity index 100% rename from sdk/metric/internal/exemplar/drop.go rename to sdk/metric/exemplar/drop.go diff --git a/sdk/metric/internal/exemplar/drop_test.go b/sdk/metric/exemplar/drop_test.go similarity index 100% rename from sdk/metric/internal/exemplar/drop_test.go rename to sdk/metric/exemplar/drop_test.go diff --git a/sdk/metric/internal/exemplar/exemplar.go b/sdk/metric/exemplar/exemplar.go similarity index 100% rename from sdk/metric/internal/exemplar/exemplar.go rename to sdk/metric/exemplar/exemplar.go diff --git a/sdk/metric/internal/exemplar/filter.go b/sdk/metric/exemplar/filter.go similarity index 100% rename from sdk/metric/internal/exemplar/filter.go rename to sdk/metric/exemplar/filter.go diff --git a/sdk/metric/internal/exemplar/filter_test.go b/sdk/metric/exemplar/filter_test.go similarity index 100% rename from sdk/metric/internal/exemplar/filter_test.go rename to sdk/metric/exemplar/filter_test.go diff --git a/sdk/metric/internal/exemplar/filtered_reservoir.go b/sdk/metric/exemplar/filtered_reservoir.go similarity index 100% rename from sdk/metric/internal/exemplar/filtered_reservoir.go rename to sdk/metric/exemplar/filtered_reservoir.go diff --git a/sdk/metric/internal/exemplar/hist.go b/sdk/metric/exemplar/hist.go similarity index 100% rename from sdk/metric/internal/exemplar/hist.go rename to sdk/metric/exemplar/hist.go diff --git a/sdk/metric/internal/exemplar/hist_test.go b/sdk/metric/exemplar/hist_test.go similarity index 100% rename from sdk/metric/internal/exemplar/hist_test.go rename to sdk/metric/exemplar/hist_test.go diff --git a/sdk/metric/internal/exemplar/rand.go b/sdk/metric/exemplar/rand.go similarity index 100% rename from sdk/metric/internal/exemplar/rand.go rename to sdk/metric/exemplar/rand.go diff --git a/sdk/metric/internal/exemplar/rand_test.go b/sdk/metric/exemplar/rand_test.go similarity index 100% rename from sdk/metric/internal/exemplar/rand_test.go rename to sdk/metric/exemplar/rand_test.go diff --git a/sdk/metric/internal/exemplar/reservoir.go b/sdk/metric/exemplar/reservoir.go similarity index 100% rename from sdk/metric/internal/exemplar/reservoir.go rename to sdk/metric/exemplar/reservoir.go diff --git a/sdk/metric/internal/exemplar/reservoir_test.go b/sdk/metric/exemplar/reservoir_test.go similarity index 100% rename from sdk/metric/internal/exemplar/reservoir_test.go rename to sdk/metric/exemplar/reservoir_test.go diff --git a/sdk/metric/internal/exemplar/storage.go b/sdk/metric/exemplar/storage.go similarity index 100% rename from sdk/metric/internal/exemplar/storage.go rename to sdk/metric/exemplar/storage.go diff --git a/sdk/metric/internal/exemplar/value.go b/sdk/metric/exemplar/value.go similarity index 100% rename from sdk/metric/internal/exemplar/value.go rename to sdk/metric/exemplar/value.go diff --git a/sdk/metric/internal/exemplar/value_test.go b/sdk/metric/exemplar/value_test.go similarity index 100% rename from sdk/metric/internal/exemplar/value_test.go rename to sdk/metric/exemplar/value_test.go From 6261cd2bbd698b4db49230638e37c72fdceeea90 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Tue, 27 Aug 2024 20:22:44 +0000 Subject: [PATCH 02/14] update references --- sdk/metric/exemplar.go | 2 +- sdk/metric/exemplar/doc.go | 2 +- sdk/metric/exemplar/drop.go | 2 +- sdk/metric/exemplar/exemplar.go | 2 +- sdk/metric/exemplar/filter.go | 2 +- sdk/metric/exemplar/filter_test.go | 2 +- sdk/metric/exemplar/filtered_reservoir.go | 2 +- sdk/metric/exemplar/hist.go | 2 +- sdk/metric/exemplar/rand.go | 2 +- sdk/metric/exemplar/reservoir.go | 2 +- sdk/metric/exemplar/storage.go | 2 +- sdk/metric/exemplar/value.go | 2 +- sdk/metric/internal/aggregate/aggregate.go | 2 +- sdk/metric/internal/aggregate/aggregate_test.go | 2 +- sdk/metric/internal/aggregate/exemplar.go | 2 +- sdk/metric/internal/aggregate/exemplar_test.go | 2 +- sdk/metric/internal/aggregate/exponential_histogram.go | 2 +- sdk/metric/internal/aggregate/histogram.go | 2 +- sdk/metric/internal/aggregate/lastvalue.go | 2 +- sdk/metric/internal/aggregate/sum.go | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/sdk/metric/exemplar.go b/sdk/metric/exemplar.go index 8a05d881d2d..5fe4d96e5bf 100644 --- a/sdk/metric/exemplar.go +++ b/sdk/metric/exemplar.go @@ -8,7 +8,7 @@ import ( "runtime" "slices" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" ) // reservoirFunc returns the appropriately configured exemplar reservoir diff --git a/sdk/metric/exemplar/doc.go b/sdk/metric/exemplar/doc.go index 5394f48e0df..9f238937688 100644 --- a/sdk/metric/exemplar/doc.go +++ b/sdk/metric/exemplar/doc.go @@ -3,4 +3,4 @@ // Package exemplar provides an implementation of the OpenTelemetry exemplar // reservoir to be used in metric collection pipelines. -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" diff --git a/sdk/metric/exemplar/drop.go b/sdk/metric/exemplar/drop.go index 5a0f39ae147..33713c45670 100644 --- a/sdk/metric/exemplar/drop.go +++ b/sdk/metric/exemplar/drop.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/exemplar.go b/sdk/metric/exemplar/exemplar.go index fcaa6a4697c..1ab69467868 100644 --- a/sdk/metric/exemplar/exemplar.go +++ b/sdk/metric/exemplar/exemplar.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "time" diff --git a/sdk/metric/exemplar/filter.go b/sdk/metric/exemplar/filter.go index 152a069a09e..f0da8a049b4 100644 --- a/sdk/metric/exemplar/filter.go +++ b/sdk/metric/exemplar/filter.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/filter_test.go b/sdk/metric/exemplar/filter_test.go index d6827d5b2f9..a0e8dd60ffb 100644 --- a/sdk/metric/exemplar/filter_test.go +++ b/sdk/metric/exemplar/filter_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/filtered_reservoir.go b/sdk/metric/exemplar/filtered_reservoir.go index 9a047cfa091..4506510f0ee 100644 --- a/sdk/metric/exemplar/filtered_reservoir.go +++ b/sdk/metric/exemplar/filtered_reservoir.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/hist.go b/sdk/metric/exemplar/hist.go index a6ff86d0271..4fdbb269fc2 100644 --- a/sdk/metric/exemplar/hist.go +++ b/sdk/metric/exemplar/hist.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/rand.go b/sdk/metric/exemplar/rand.go index fd10b8ea19c..8215d7020f4 100644 --- a/sdk/metric/exemplar/rand.go +++ b/sdk/metric/exemplar/rand.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/reservoir.go b/sdk/metric/exemplar/reservoir.go index 80fa59554f2..055ce5bc8ec 100644 --- a/sdk/metric/exemplar/reservoir.go +++ b/sdk/metric/exemplar/reservoir.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/storage.go b/sdk/metric/exemplar/storage.go index 10b2976f796..ecf4cc857ae 100644 --- a/sdk/metric/exemplar/storage.go +++ b/sdk/metric/exemplar/storage.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import ( "context" diff --git a/sdk/metric/exemplar/value.go b/sdk/metric/exemplar/value.go index b1f8637819f..590b089a806 100644 --- a/sdk/metric/exemplar/value.go +++ b/sdk/metric/exemplar/value.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" +package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" import "math" diff --git a/sdk/metric/internal/aggregate/aggregate.go b/sdk/metric/internal/aggregate/aggregate.go index b18ee719bd1..1c4c3dc9ad0 100644 --- a/sdk/metric/internal/aggregate/aggregate.go +++ b/sdk/metric/internal/aggregate/aggregate.go @@ -8,7 +8,7 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) diff --git a/sdk/metric/internal/aggregate/aggregate_test.go b/sdk/metric/internal/aggregate/aggregate_test.go index df795022621..413070e6d3d 100644 --- a/sdk/metric/internal/aggregate/aggregate_test.go +++ b/sdk/metric/internal/aggregate/aggregate_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) diff --git a/sdk/metric/internal/aggregate/exemplar.go b/sdk/metric/internal/aggregate/exemplar.go index 170ae8e58e2..dcb899d6267 100644 --- a/sdk/metric/internal/aggregate/exemplar.go +++ b/sdk/metric/internal/aggregate/exemplar.go @@ -6,7 +6,7 @@ package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggreg import ( "sync" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) diff --git a/sdk/metric/internal/aggregate/exemplar_test.go b/sdk/metric/internal/aggregate/exemplar_test.go index df1d125fa66..d07d794fa75 100644 --- a/sdk/metric/internal/aggregate/exemplar_test.go +++ b/sdk/metric/internal/aggregate/exemplar_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) diff --git a/sdk/metric/internal/aggregate/exponential_histogram.go b/sdk/metric/internal/aggregate/exponential_histogram.go index 707342408ac..50a2f6e1897 100644 --- a/sdk/metric/internal/aggregate/exponential_histogram.go +++ b/sdk/metric/internal/aggregate/exponential_histogram.go @@ -12,7 +12,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) diff --git a/sdk/metric/internal/aggregate/histogram.go b/sdk/metric/internal/aggregate/histogram.go index ade0941f5f5..f36178db4d2 100644 --- a/sdk/metric/internal/aggregate/histogram.go +++ b/sdk/metric/internal/aggregate/histogram.go @@ -11,7 +11,7 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) diff --git a/sdk/metric/internal/aggregate/lastvalue.go b/sdk/metric/internal/aggregate/lastvalue.go index c359368403e..1194593a482 100644 --- a/sdk/metric/internal/aggregate/lastvalue.go +++ b/sdk/metric/internal/aggregate/lastvalue.go @@ -9,7 +9,7 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) diff --git a/sdk/metric/internal/aggregate/sum.go b/sdk/metric/internal/aggregate/sum.go index 5647c1db5d7..768b87b2a75 100644 --- a/sdk/metric/internal/aggregate/sum.go +++ b/sdk/metric/internal/aggregate/sum.go @@ -9,7 +9,7 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) From 3679e70bd4cabac30db5a986efc6763729dd964b Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Tue, 27 Aug 2024 20:26:58 +0000 Subject: [PATCH 03/14] make measurement.Exemplar private --- sdk/metric/exemplar/storage.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/metric/exemplar/storage.go b/sdk/metric/exemplar/storage.go index ecf4cc857ae..0e2e26dfb18 100644 --- a/sdk/metric/exemplar/storage.go +++ b/sdk/metric/exemplar/storage.go @@ -35,7 +35,7 @@ func (r *storage) Collect(dest *[]Exemplar) { continue } - m.Exemplar(&(*dest)[n]) + m.exemplar(&(*dest)[n]) n++ } *dest = (*dest)[:n] @@ -66,8 +66,8 @@ func newMeasurement(ctx context.Context, ts time.Time, v Value, droppedAttr []at } } -// Exemplar returns m as an [Exemplar]. -func (m measurement) Exemplar(dest *Exemplar) { +// exemplar returns m as an [Exemplar]. +func (m measurement) exemplar(dest *Exemplar) { dest.FilteredAttributes = m.FilteredAttributes dest.Time = m.Time dest.Value = m.Value From 63c1c498622955e42609cdf1c53b53e73e68c1b6 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Tue, 27 Aug 2024 20:35:56 +0000 Subject: [PATCH 04/14] move drop and filtered reservoir types to internal/aggregate --- CHANGELOG.md | 7 +++ sdk/metric/exemplar.go | 13 ++--- sdk/metric/exemplar/filtered_reservoir.go | 49 ------------------ sdk/metric/internal/aggregate/aggregate.go | 9 ++-- .../internal/aggregate/aggregate_test.go | 5 +- .../{exemplar => internal/aggregate}/drop.go | 9 ++-- .../aggregate}/drop_test.go | 8 +-- .../aggregate/exponential_histogram.go | 7 ++- .../internal/aggregate/filtered_reservoir.go | 50 +++++++++++++++++++ sdk/metric/internal/aggregate/histogram.go | 9 ++-- sdk/metric/internal/aggregate/lastvalue.go | 9 ++-- sdk/metric/internal/aggregate/sum.go | 11 ++-- 12 files changed, 96 insertions(+), 90 deletions(-) delete mode 100644 sdk/metric/exemplar/filtered_reservoir.go rename sdk/metric/{exemplar => internal/aggregate}/drop.go (51%) rename sdk/metric/{exemplar => internal/aggregate}/drop_test.go (76%) create mode 100644 sdk/metric/internal/aggregate/filtered_reservoir.go diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2f5962056..ff7c1acbc5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- Add `go.opentelemetry.io/otel/sdk/metric/exemplar` package which includes + `Exemplar`, `Filter`, `SampledFilter`, `AlwaysOnFilter`, `Histogram`, + `FixedSize`, `Reservoir`, `Value` and `ValueType` types. These will be used + for configuring the exemplar reservoir for the metrics sdk. (#5747) + ### Changed - Enable exemplars by default in `go.opentelemetry.io/otel/sdk/metric`. Exemplars can be disabled by setting `OTEL_METRICS_EXEMPLAR_FILTER=always_off` (#5778) diff --git a/sdk/metric/exemplar.go b/sdk/metric/exemplar.go index 5fe4d96e5bf..941b6034924 100644 --- a/sdk/metric/exemplar.go +++ b/sdk/metric/exemplar.go @@ -9,6 +9,7 @@ import ( "slices" "go.opentelemetry.io/otel/sdk/metric/exemplar" + "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" ) // reservoirFunc returns the appropriately configured exemplar reservoir @@ -18,7 +19,7 @@ import ( // Note: This will only return non-nil values when the experimental exemplar // feature is enabled and the OTEL_METRICS_EXEMPLAR_FILTER environment variable // is not set to always_off. -func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.FilteredReservoir[N] { +func reservoirFunc[N int64 | float64](agg Aggregation) func() aggregate.FilteredExemplarReservoir[N] { // https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/configuration/sdk-environment-variables.md#exemplar const filterEnvKey = "OTEL_METRICS_EXEMPLAR_FILTER" @@ -28,7 +29,7 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.FilteredR case "always_on": filter = exemplar.AlwaysOnFilter case "always_off": - return exemplar.Drop + return aggregate.DropReservoir case "trace_based": fallthrough default: @@ -41,9 +42,9 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.FilteredR a, ok := agg.(AggregationExplicitBucketHistogram) if ok && len(a.Boundaries) > 0 { cp := slices.Clone(a.Boundaries) - return func() exemplar.FilteredReservoir[N] { + return func() aggregate.FilteredExemplarReservoir[N] { bounds := cp - return exemplar.NewFilteredReservoir[N](filter, exemplar.Histogram(bounds)) + return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.Histogram(bounds)) } } @@ -71,7 +72,7 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.FilteredR } } - return func() exemplar.FilteredReservoir[N] { - return exemplar.NewFilteredReservoir[N](filter, exemplar.FixedSize(n)) + return func() aggregate.FilteredExemplarReservoir[N] { + return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.FixedSize(n)) } } diff --git a/sdk/metric/exemplar/filtered_reservoir.go b/sdk/metric/exemplar/filtered_reservoir.go deleted file mode 100644 index 4506510f0ee..00000000000 --- a/sdk/metric/exemplar/filtered_reservoir.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" - -import ( - "context" - "time" - - "go.opentelemetry.io/otel/attribute" -) - -// FilteredReservoir wraps a [Reservoir] with a filter. -type FilteredReservoir[N int64 | float64] interface { - // Offer accepts the parameters associated with a measurement. The - // parameters will be stored as an exemplar if the filter decides to - // sample the measurement. - // - // The passed ctx needs to contain any baggage or span that were active - // when the measurement was made. This information may be used by the - // Reservoir in making a sampling decision. - Offer(ctx context.Context, val N, attr []attribute.KeyValue) - // Collect returns all the held exemplars in the reservoir. - Collect(dest *[]Exemplar) -} - -// filteredReservoir handles the pre-sampled exemplar of measurements made. -type filteredReservoir[N int64 | float64] struct { - filter Filter - reservoir Reservoir -} - -// NewFilteredReservoir creates a [FilteredReservoir] which only offers values -// that are allowed by the filter. -func NewFilteredReservoir[N int64 | float64](f Filter, r Reservoir) FilteredReservoir[N] { - return &filteredReservoir[N]{ - filter: f, - reservoir: r, - } -} - -func (f *filteredReservoir[N]) Offer(ctx context.Context, val N, attr []attribute.KeyValue) { - if f.filter(ctx) { - // only record the current time if we are sampling this measurement. - f.reservoir.Offer(ctx, time.Now(), NewValue(val), attr) - } -} - -func (f *filteredReservoir[N]) Collect(dest *[]Exemplar) { f.reservoir.Collect(dest) } diff --git a/sdk/metric/internal/aggregate/aggregate.go b/sdk/metric/internal/aggregate/aggregate.go index 1c4c3dc9ad0..f1f3ab67314 100644 --- a/sdk/metric/internal/aggregate/aggregate.go +++ b/sdk/metric/internal/aggregate/aggregate.go @@ -8,7 +8,6 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -38,8 +37,8 @@ type Builder[N int64 | float64] struct { // create new exemplar reservoirs for a new seen attribute set. // // If this is not provided a default factory function that returns an - // exemplar.Drop reservoir will be used. - ReservoirFunc func() exemplar.FilteredReservoir[N] + // DropReservoir reservoir will be used. + ReservoirFunc func() FilteredExemplarReservoir[N] // AggregationLimit is the cardinality limit of measurement attributes. Any // measurement for new attributes once the limit has been reached will be // aggregated into a single aggregate for the "otel.metric.overflow" @@ -50,12 +49,12 @@ type Builder[N int64 | float64] struct { AggregationLimit int } -func (b Builder[N]) resFunc() func() exemplar.FilteredReservoir[N] { +func (b Builder[N]) resFunc() func() FilteredExemplarReservoir[N] { if b.ReservoirFunc != nil { return b.ReservoirFunc } - return exemplar.Drop + return DropReservoir } type fltrMeasure[N int64 | float64] func(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) diff --git a/sdk/metric/internal/aggregate/aggregate_test.go b/sdk/metric/internal/aggregate/aggregate_test.go index 413070e6d3d..fec39f5f919 100644 --- a/sdk/metric/internal/aggregate/aggregate_test.go +++ b/sdk/metric/internal/aggregate/aggregate_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) @@ -73,8 +72,8 @@ func (c *clock) Register() (unregister func()) { return func() { now = orig } } -func dropExemplars[N int64 | float64]() exemplar.FilteredReservoir[N] { - return exemplar.Drop[N]() +func dropExemplars[N int64 | float64]() FilteredExemplarReservoir[N] { + return DropReservoir[N]() } func TestBuilderFilter(t *testing.T) { diff --git a/sdk/metric/exemplar/drop.go b/sdk/metric/internal/aggregate/drop.go similarity index 51% rename from sdk/metric/exemplar/drop.go rename to sdk/metric/internal/aggregate/drop.go index 33713c45670..4a3d4cc2218 100644 --- a/sdk/metric/exemplar/drop.go +++ b/sdk/metric/internal/aggregate/drop.go @@ -1,16 +1,17 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar" +package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" import ( "context" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric/exemplar" ) -// Drop returns a [FilteredReservoir] that drops all measurements it is offered. -func Drop[N int64 | float64]() FilteredReservoir[N] { return &dropRes[N]{} } +// DropReservoir returns a [FilteredReservoir] that drops all measurements it is offered. +func DropReservoir[N int64 | float64]() FilteredExemplarReservoir[N] { return &dropRes[N]{} } type dropRes[N int64 | float64] struct{} @@ -18,6 +19,6 @@ type dropRes[N int64 | float64] struct{} func (r *dropRes[N]) Offer(context.Context, N, []attribute.KeyValue) {} // Collect resets dest. No exemplars will ever be returned. -func (r *dropRes[N]) Collect(dest *[]Exemplar) { +func (r *dropRes[N]) Collect(dest *[]exemplar.Exemplar) { *dest = (*dest)[:0] } diff --git a/sdk/metric/exemplar/drop_test.go b/sdk/metric/internal/aggregate/drop_test.go similarity index 76% rename from sdk/metric/exemplar/drop_test.go rename to sdk/metric/internal/aggregate/drop_test.go index 33303e190d1..7b3a0f9c3ef 100644 --- a/sdk/metric/exemplar/drop_test.go +++ b/sdk/metric/internal/aggregate/drop_test.go @@ -1,12 +1,14 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package exemplar +package aggregate import ( "testing" "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/sdk/metric/exemplar" ) func TestDrop(t *testing.T) { @@ -15,9 +17,9 @@ func TestDrop(t *testing.T) { } func testDropFiltered[N int64 | float64](t *testing.T) { - r := Drop[N]() + r := DropReservoir[N]() - var dest []Exemplar + var dest []exemplar.Exemplar r.Collect(&dest) assert.Empty(t, dest, "non-sampled context should not be offered") diff --git a/sdk/metric/internal/aggregate/exponential_histogram.go b/sdk/metric/internal/aggregate/exponential_histogram.go index 50a2f6e1897..a4de5674ba1 100644 --- a/sdk/metric/internal/aggregate/exponential_histogram.go +++ b/sdk/metric/internal/aggregate/exponential_histogram.go @@ -12,7 +12,6 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -31,7 +30,7 @@ const ( // expoHistogramDataPoint is a single data point in an exponential histogram. type expoHistogramDataPoint[N int64 | float64] struct { attrs attribute.Set - res exemplar.FilteredReservoir[N] + res FilteredExemplarReservoir[N] count uint64 min N @@ -284,7 +283,7 @@ func (b *expoBuckets) downscale(delta int32) { // newExponentialHistogram returns an Aggregator that summarizes a set of // measurements as an exponential histogram. Each histogram is scoped by attributes // and the aggregation cycle the measurements were made in. -func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() exemplar.FilteredReservoir[N]) *expoHistogram[N] { +func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() FilteredExemplarReservoir[N]) *expoHistogram[N] { return &expoHistogram[N]{ noSum: noSum, noMinMax: noMinMax, @@ -307,7 +306,7 @@ type expoHistogram[N int64 | float64] struct { maxSize int maxScale int32 - newRes func() exemplar.FilteredReservoir[N] + newRes func() FilteredExemplarReservoir[N] limit limiter[*expoHistogramDataPoint[N]] values map[attribute.Distinct]*expoHistogramDataPoint[N] valuesMu sync.Mutex diff --git a/sdk/metric/internal/aggregate/filtered_reservoir.go b/sdk/metric/internal/aggregate/filtered_reservoir.go new file mode 100644 index 00000000000..691a910608d --- /dev/null +++ b/sdk/metric/internal/aggregate/filtered_reservoir.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" + +import ( + "context" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric/exemplar" +) + +// FilteredExemplarReservoir wraps a [exemplar.Reservoir] with a filter. +type FilteredExemplarReservoir[N int64 | float64] interface { + // Offer accepts the parameters associated with a measurement. The + // parameters will be stored as an exemplar if the filter decides to + // sample the measurement. + // + // The passed ctx needs to contain any baggage or span that were active + // when the measurement was made. This information may be used by the + // Reservoir in making a sampling decision. + Offer(ctx context.Context, val N, attr []attribute.KeyValue) + // Collect returns all the held exemplars in the reservoir. + Collect(dest *[]exemplar.Exemplar) +} + +// filteredExemplarReservoir handles the pre-sampled exemplar of measurements made. +type filteredExemplarReservoir[N int64 | float64] struct { + filter exemplar.Filter + reservoir exemplar.Reservoir +} + +// NewFilteredExemplarReservoir creates a [FilteredExemplarReservoir] which only offers values +// that are allowed by the filter. +func NewFilteredExemplarReservoir[N int64 | float64](f exemplar.Filter, r exemplar.Reservoir) FilteredExemplarReservoir[N] { + return &filteredExemplarReservoir[N]{ + filter: f, + reservoir: r, + } +} + +func (f *filteredExemplarReservoir[N]) Offer(ctx context.Context, val N, attr []attribute.KeyValue) { + if f.filter(ctx) { + // only record the current time if we are sampling this measurement. + f.reservoir.Offer(ctx, time.Now(), exemplar.NewValue(val), attr) + } +} + +func (f *filteredExemplarReservoir[N]) Collect(dest *[]exemplar.Exemplar) { f.reservoir.Collect(dest) } diff --git a/sdk/metric/internal/aggregate/histogram.go b/sdk/metric/internal/aggregate/histogram.go index f36178db4d2..35d020378bd 100644 --- a/sdk/metric/internal/aggregate/histogram.go +++ b/sdk/metric/internal/aggregate/histogram.go @@ -11,13 +11,12 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) type buckets[N int64 | float64] struct { attrs attribute.Set - res exemplar.FilteredReservoir[N] + res FilteredExemplarReservoir[N] counts []uint64 count uint64 @@ -48,13 +47,13 @@ type histValues[N int64 | float64] struct { noSum bool bounds []float64 - newRes func() exemplar.FilteredReservoir[N] + newRes func() FilteredExemplarReservoir[N] limit limiter[*buckets[N]] values map[attribute.Distinct]*buckets[N] valuesMu sync.Mutex } -func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() exemplar.FilteredReservoir[N]) *histValues[N] { +func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() FilteredExemplarReservoir[N]) *histValues[N] { // The responsibility of keeping all buckets correctly associated with the // passed boundaries is ultimately this type's responsibility. Make a copy // here so we can always guarantee this. Or, in the case of failure, have @@ -109,7 +108,7 @@ func (s *histValues[N]) measure(ctx context.Context, value N, fltrAttr attribute // newHistogram returns an Aggregator that summarizes a set of measurements as // an histogram. -func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() exemplar.FilteredReservoir[N]) *histogram[N] { +func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() FilteredExemplarReservoir[N]) *histogram[N] { return &histogram[N]{ histValues: newHistValues[N](boundaries, noSum, limit, r), noMinMax: noMinMax, diff --git a/sdk/metric/internal/aggregate/lastvalue.go b/sdk/metric/internal/aggregate/lastvalue.go index 1194593a482..a7b5fe572be 100644 --- a/sdk/metric/internal/aggregate/lastvalue.go +++ b/sdk/metric/internal/aggregate/lastvalue.go @@ -9,7 +9,6 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) @@ -17,10 +16,10 @@ import ( type datapoint[N int64 | float64] struct { attrs attribute.Set value N - res exemplar.FilteredReservoir[N] + res FilteredExemplarReservoir[N] } -func newLastValue[N int64 | float64](limit int, r func() exemplar.FilteredReservoir[N]) *lastValue[N] { +func newLastValue[N int64 | float64](limit int, r func() FilteredExemplarReservoir[N]) *lastValue[N] { return &lastValue[N]{ newRes: r, limit: newLimiter[datapoint[N]](limit), @@ -33,7 +32,7 @@ func newLastValue[N int64 | float64](limit int, r func() exemplar.FilteredReserv type lastValue[N int64 | float64] struct { sync.Mutex - newRes func() exemplar.FilteredReservoir[N] + newRes func() FilteredExemplarReservoir[N] limit limiter[datapoint[N]] values map[attribute.Distinct]datapoint[N] start time.Time @@ -115,7 +114,7 @@ func (s *lastValue[N]) copyDpts(dest *[]metricdata.DataPoint[N], t time.Time) in // newPrecomputedLastValue returns an aggregator that summarizes a set of // observations as the last one made. -func newPrecomputedLastValue[N int64 | float64](limit int, r func() exemplar.FilteredReservoir[N]) *precomputedLastValue[N] { +func newPrecomputedLastValue[N int64 | float64](limit int, r func() FilteredExemplarReservoir[N]) *precomputedLastValue[N] { return &precomputedLastValue[N]{lastValue: newLastValue[N](limit, r)} } diff --git a/sdk/metric/internal/aggregate/sum.go b/sdk/metric/internal/aggregate/sum.go index 768b87b2a75..c3b591c37c0 100644 --- a/sdk/metric/internal/aggregate/sum.go +++ b/sdk/metric/internal/aggregate/sum.go @@ -9,25 +9,24 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) type sumValue[N int64 | float64] struct { n N - res exemplar.FilteredReservoir[N] + res FilteredExemplarReservoir[N] attrs attribute.Set } // valueMap is the storage for sums. type valueMap[N int64 | float64] struct { sync.Mutex - newRes func() exemplar.FilteredReservoir[N] + newRes func() FilteredExemplarReservoir[N] limit limiter[sumValue[N]] values map[attribute.Distinct]sumValue[N] } -func newValueMap[N int64 | float64](limit int, r func() exemplar.FilteredReservoir[N]) *valueMap[N] { +func newValueMap[N int64 | float64](limit int, r func() FilteredExemplarReservoir[N]) *valueMap[N] { return &valueMap[N]{ newRes: r, limit: newLimiter[sumValue[N]](limit), @@ -55,7 +54,7 @@ func (s *valueMap[N]) measure(ctx context.Context, value N, fltrAttr attribute.S // newSum returns an aggregator that summarizes a set of measurements as their // arithmetic sum. Each sum is scoped by attributes and the aggregation cycle // the measurements were made in. -func newSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.FilteredReservoir[N]) *sum[N] { +func newSum[N int64 | float64](monotonic bool, limit int, r func() FilteredExemplarReservoir[N]) *sum[N] { return &sum[N]{ valueMap: newValueMap[N](limit, r), monotonic: monotonic, @@ -144,7 +143,7 @@ func (s *sum[N]) cumulative(dest *metricdata.Aggregation) int { // newPrecomputedSum returns an aggregator that summarizes a set of // observations as their arithmetic sum. Each sum is scoped by attributes and // the aggregation cycle the measurements were made in. -func newPrecomputedSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.FilteredReservoir[N]) *precomputedSum[N] { +func newPrecomputedSum[N int64 | float64](monotonic bool, limit int, r func() FilteredExemplarReservoir[N]) *precomputedSum[N] { return &precomputedSum[N]{ valueMap: newValueMap[N](limit, r), monotonic: monotonic, From 77692ceb291a459c14870bed6a287bc33841dbf0 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Wed, 4 Sep 2024 01:28:47 +0000 Subject: [PATCH 05/14] add readme --- sdk/metric/exemplar/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 sdk/metric/exemplar/README.md diff --git a/sdk/metric/exemplar/README.md b/sdk/metric/exemplar/README.md new file mode 100644 index 00000000000..153cd4a9632 --- /dev/null +++ b/sdk/metric/exemplar/README.md @@ -0,0 +1,3 @@ +# Metric API + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/sdk/metric/exemplar)](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric/exemplar) From b01719272bde7a6a44ac27d81f764a9dd7c683a4 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Thu, 19 Sep 2024 21:00:47 +0000 Subject: [PATCH 06/14] fix readme --- sdk/metric/exemplar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/metric/exemplar/README.md b/sdk/metric/exemplar/README.md index 153cd4a9632..d1025f5eb89 100644 --- a/sdk/metric/exemplar/README.md +++ b/sdk/metric/exemplar/README.md @@ -1,3 +1,3 @@ -# Metric API +# Metric SDK Exemplars [![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/sdk/metric/exemplar)](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric/exemplar) From 4c46e7c2872f3ab252a1ae6bd94695f7f2608c74 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Fri, 20 Sep 2024 00:47:33 +0000 Subject: [PATCH 07/14] rename FixedSize to FixedSizeReservoir --- CHANGELOG.md | 2 +- sdk/metric/exemplar.go | 2 +- .../exemplar/{rand.go => fixed_size_reservoir.go} | 4 ++-- .../{rand_test.go => fixed_size_reservoir_test.go} | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) rename sdk/metric/exemplar/{rand.go => fixed_size_reservoir.go} (98%) rename sdk/metric/exemplar/{rand_test.go => fixed_size_reservoir_test.go} (83%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7c1acbc5a..1ba55243393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add `go.opentelemetry.io/otel/sdk/metric/exemplar` package which includes `Exemplar`, `Filter`, `SampledFilter`, `AlwaysOnFilter`, `Histogram`, - `FixedSize`, `Reservoir`, `Value` and `ValueType` types. These will be used + `FixedSizeReservoir`, `Reservoir`, `Value` and `ValueType` types. These will be used for configuring the exemplar reservoir for the metrics sdk. (#5747) ### Changed diff --git a/sdk/metric/exemplar.go b/sdk/metric/exemplar.go index 941b6034924..070f0333947 100644 --- a/sdk/metric/exemplar.go +++ b/sdk/metric/exemplar.go @@ -73,6 +73,6 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() aggregate.Filtered } return func() aggregate.FilteredExemplarReservoir[N] { - return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.FixedSize(n)) + return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.FixedSizeReservoir(n)) } } diff --git a/sdk/metric/exemplar/rand.go b/sdk/metric/exemplar/fixed_size_reservoir.go similarity index 98% rename from sdk/metric/exemplar/rand.go rename to sdk/metric/exemplar/fixed_size_reservoir.go index 8215d7020f4..2786e2cfc21 100644 --- a/sdk/metric/exemplar/rand.go +++ b/sdk/metric/exemplar/fixed_size_reservoir.go @@ -12,11 +12,11 @@ import ( "go.opentelemetry.io/otel/attribute" ) -// FixedSize returns a [Reservoir] that samples at most k exemplars. If there +// FixedSizeReservoir returns a [Reservoir] that samples at most k exemplars. If there // are k or less measurements made, the Reservoir will sample each one. If // there are more than k, the Reservoir will then randomly sample all // additional measurement with a decreasing probability. -func FixedSize(k int) Reservoir { +func FixedSizeReservoir(k int) Reservoir { return newRandRes(newStorage(k)) } diff --git a/sdk/metric/exemplar/rand_test.go b/sdk/metric/exemplar/fixed_size_reservoir_test.go similarity index 83% rename from sdk/metric/exemplar/rand_test.go rename to sdk/metric/exemplar/fixed_size_reservoir_test.go index f9e1a847523..92a081c99bc 100644 --- a/sdk/metric/exemplar/rand_test.go +++ b/sdk/metric/exemplar/fixed_size_reservoir_test.go @@ -14,17 +14,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFixedSize(t *testing.T) { +func TestFixedSizeReservoir(t *testing.T) { t.Run("Int64", ReservoirTest[int64](func(n int) (Reservoir, int) { - return FixedSize(n), n + return FixedSizeReservoir(n), n })) t.Run("Float64", ReservoirTest[float64](func(n int) (Reservoir, int) { - return FixedSize(n), n + return FixedSizeReservoir(n), n })) } -func TestFixedSizeSamplingCorrectness(t *testing.T) { +func TestFixedSizeReservoirSamplingCorrectness(t *testing.T) { intensity := 0.1 sampleSize := 1000 @@ -38,7 +38,7 @@ func TestFixedSizeSamplingCorrectness(t *testing.T) { // Sort to test position bias. slices.Sort(data) - r := FixedSize(sampleSize) + r := FixedSizeReservoir(sampleSize) for _, value := range data { r.Offer(context.Background(), staticTime, NewValue(value), nil) } From 97170c1d2a3dd72ffe810ac3650fa1c92a7e9c09 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Fri, 20 Sep 2024 00:49:49 +0000 Subject: [PATCH 08/14] rename Histogram to HistogramReservoir --- CHANGELOG.md | 7 ++++--- sdk/metric/exemplar.go | 2 +- sdk/metric/exemplar/{hist.go => histogram_reservoir.go} | 4 ++-- .../exemplar/{hist_test.go => histogram_reservoir_test.go} | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) rename sdk/metric/exemplar/{hist.go => histogram_reservoir.go} (86%) rename sdk/metric/exemplar/{hist_test.go => histogram_reservoir_test.go} (76%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ba55243393..a37e27773f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add `go.opentelemetry.io/otel/sdk/metric/exemplar` package which includes - `Exemplar`, `Filter`, `SampledFilter`, `AlwaysOnFilter`, `Histogram`, - `FixedSizeReservoir`, `Reservoir`, `Value` and `ValueType` types. These will be used - for configuring the exemplar reservoir for the metrics sdk. (#5747) + `Exemplar`, `Filter`, `SampledFilter`, `AlwaysOnFilter`, + `HistogramReservoir`, `FixedSizeReservoir`, `Reservoir`, `Value` and + `ValueType` types. These will be used for configuring the exemplar reservoir + for the metrics sdk. (#5747) ### Changed diff --git a/sdk/metric/exemplar.go b/sdk/metric/exemplar.go index 070f0333947..f944993c16d 100644 --- a/sdk/metric/exemplar.go +++ b/sdk/metric/exemplar.go @@ -44,7 +44,7 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() aggregate.Filtered cp := slices.Clone(a.Boundaries) return func() aggregate.FilteredExemplarReservoir[N] { bounds := cp - return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.Histogram(bounds)) + return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.HistogramReservoir(bounds)) } } diff --git a/sdk/metric/exemplar/hist.go b/sdk/metric/exemplar/histogram_reservoir.go similarity index 86% rename from sdk/metric/exemplar/hist.go rename to sdk/metric/exemplar/histogram_reservoir.go index 4fdbb269fc2..448af825d2b 100644 --- a/sdk/metric/exemplar/hist.go +++ b/sdk/metric/exemplar/histogram_reservoir.go @@ -12,12 +12,12 @@ import ( "go.opentelemetry.io/otel/attribute" ) -// Histogram returns a [Reservoir] that samples the last measurement that falls +// HistogramReservoir returns a [Reservoir] that samples the last measurement that falls // within a histogram bucket. The histogram bucket upper-boundaries are define // by bounds. // // The passed bounds will be sorted by this function. -func Histogram(bounds []float64) Reservoir { +func HistogramReservoir(bounds []float64) Reservoir { slices.Sort(bounds) return &histRes{ bounds: bounds, diff --git a/sdk/metric/exemplar/hist_test.go b/sdk/metric/exemplar/histogram_reservoir_test.go similarity index 76% rename from sdk/metric/exemplar/hist_test.go rename to sdk/metric/exemplar/histogram_reservoir_test.go index 499c9a3a2d3..2e6e1a86a7d 100644 --- a/sdk/metric/exemplar/hist_test.go +++ b/sdk/metric/exemplar/histogram_reservoir_test.go @@ -8,10 +8,10 @@ import "testing" func TestHist(t *testing.T) { bounds := []float64{0, 100} t.Run("Int64", ReservoirTest[int64](func(int) (Reservoir, int) { - return Histogram(bounds), len(bounds) + return HistogramReservoir(bounds), len(bounds) })) t.Run("Float64", ReservoirTest[float64](func(int) (Reservoir, int) { - return Histogram(bounds), len(bounds) + return HistogramReservoir(bounds), len(bounds) })) } From 2131abcc32a9ff6ef68bcbd2683ca13e7e7cc55e Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Sat, 21 Sep 2024 16:59:40 +0000 Subject: [PATCH 09/14] FixedSizeReservoir -> NewFixedSizeReservoir --- sdk/metric/exemplar.go | 2 +- sdk/metric/exemplar/fixed_size_reservoir.go | 4 ++-- sdk/metric/exemplar/fixed_size_reservoir_test.go | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/metric/exemplar.go b/sdk/metric/exemplar.go index f944993c16d..c5b6965f4fb 100644 --- a/sdk/metric/exemplar.go +++ b/sdk/metric/exemplar.go @@ -73,6 +73,6 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() aggregate.Filtered } return func() aggregate.FilteredExemplarReservoir[N] { - return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.FixedSizeReservoir(n)) + return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.NewFixedSizeReservoir(n)) } } diff --git a/sdk/metric/exemplar/fixed_size_reservoir.go b/sdk/metric/exemplar/fixed_size_reservoir.go index 2786e2cfc21..bc9f5e43ab1 100644 --- a/sdk/metric/exemplar/fixed_size_reservoir.go +++ b/sdk/metric/exemplar/fixed_size_reservoir.go @@ -12,11 +12,11 @@ import ( "go.opentelemetry.io/otel/attribute" ) -// FixedSizeReservoir returns a [Reservoir] that samples at most k exemplars. If there +// NewFixedSizeReservoir returns a [Reservoir] that samples at most k exemplars. If there // are k or less measurements made, the Reservoir will sample each one. If // there are more than k, the Reservoir will then randomly sample all // additional measurement with a decreasing probability. -func FixedSizeReservoir(k int) Reservoir { +func NewFixedSizeReservoir(k int) Reservoir { return newRandRes(newStorage(k)) } diff --git a/sdk/metric/exemplar/fixed_size_reservoir_test.go b/sdk/metric/exemplar/fixed_size_reservoir_test.go index 92a081c99bc..2d29481774d 100644 --- a/sdk/metric/exemplar/fixed_size_reservoir_test.go +++ b/sdk/metric/exemplar/fixed_size_reservoir_test.go @@ -14,17 +14,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFixedSizeReservoir(t *testing.T) { +func TestNewFixedSizeReservoir(t *testing.T) { t.Run("Int64", ReservoirTest[int64](func(n int) (Reservoir, int) { - return FixedSizeReservoir(n), n + return NewFixedSizeReservoir(n), n })) t.Run("Float64", ReservoirTest[float64](func(n int) (Reservoir, int) { - return FixedSizeReservoir(n), n + return NewFixedSizeReservoir(n), n })) } -func TestFixedSizeReservoirSamplingCorrectness(t *testing.T) { +func TestNewFixedSizeReservoirSamplingCorrectness(t *testing.T) { intensity := 0.1 sampleSize := 1000 @@ -38,7 +38,7 @@ func TestFixedSizeReservoirSamplingCorrectness(t *testing.T) { // Sort to test position bias. slices.Sort(data) - r := FixedSizeReservoir(sampleSize) + r := NewFixedSizeReservoir(sampleSize) for _, value := range data { r.Offer(context.Background(), staticTime, NewValue(value), nil) } From d2ef5a12425392424da2d0e4d8f3cb6e8a8f9562 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Sat, 21 Sep 2024 17:00:27 +0000 Subject: [PATCH 10/14] HistogramReservoir -> NewHistogramReservoir --- sdk/metric/exemplar.go | 2 +- sdk/metric/exemplar/histogram_reservoir.go | 4 ++-- sdk/metric/exemplar/histogram_reservoir_test.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/metric/exemplar.go b/sdk/metric/exemplar.go index c5b6965f4fb..54c5fb037ec 100644 --- a/sdk/metric/exemplar.go +++ b/sdk/metric/exemplar.go @@ -44,7 +44,7 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() aggregate.Filtered cp := slices.Clone(a.Boundaries) return func() aggregate.FilteredExemplarReservoir[N] { bounds := cp - return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.HistogramReservoir(bounds)) + return aggregate.NewFilteredExemplarReservoir[N](filter, exemplar.NewHistogramReservoir(bounds)) } } diff --git a/sdk/metric/exemplar/histogram_reservoir.go b/sdk/metric/exemplar/histogram_reservoir.go index 448af825d2b..7f3cdd72835 100644 --- a/sdk/metric/exemplar/histogram_reservoir.go +++ b/sdk/metric/exemplar/histogram_reservoir.go @@ -12,12 +12,12 @@ import ( "go.opentelemetry.io/otel/attribute" ) -// HistogramReservoir returns a [Reservoir] that samples the last measurement that falls +// NewHistogramReservoir returns a [Reservoir] that samples the last measurement that falls // within a histogram bucket. The histogram bucket upper-boundaries are define // by bounds. // // The passed bounds will be sorted by this function. -func HistogramReservoir(bounds []float64) Reservoir { +func NewHistogramReservoir(bounds []float64) Reservoir { slices.Sort(bounds) return &histRes{ bounds: bounds, diff --git a/sdk/metric/exemplar/histogram_reservoir_test.go b/sdk/metric/exemplar/histogram_reservoir_test.go index 2e6e1a86a7d..64c101cb057 100644 --- a/sdk/metric/exemplar/histogram_reservoir_test.go +++ b/sdk/metric/exemplar/histogram_reservoir_test.go @@ -8,10 +8,10 @@ import "testing" func TestHist(t *testing.T) { bounds := []float64{0, 100} t.Run("Int64", ReservoirTest[int64](func(int) (Reservoir, int) { - return HistogramReservoir(bounds), len(bounds) + return NewHistogramReservoir(bounds), len(bounds) })) t.Run("Float64", ReservoirTest[float64](func(int) (Reservoir, int) { - return HistogramReservoir(bounds), len(bounds) + return NewHistogramReservoir(bounds), len(bounds) })) } From e0a6402becf9eb5c5a79e8733736c6b9f0d0b034 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Sat, 21 Sep 2024 17:03:30 +0000 Subject: [PATCH 11/14] Export exemplar.HistogramReservoir --- sdk/metric/exemplar/histogram_reservoir.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sdk/metric/exemplar/histogram_reservoir.go b/sdk/metric/exemplar/histogram_reservoir.go index 7f3cdd72835..23059e99fbd 100644 --- a/sdk/metric/exemplar/histogram_reservoir.go +++ b/sdk/metric/exemplar/histogram_reservoir.go @@ -12,27 +12,29 @@ import ( "go.opentelemetry.io/otel/attribute" ) -// NewHistogramReservoir returns a [Reservoir] that samples the last measurement that falls -// within a histogram bucket. The histogram bucket upper-boundaries are define -// by bounds. +// NewHistogramReservoir returns a [HistogramReservoir] that samples the last +// measurement that falls within a histogram bucket. The histogram bucket +// upper-boundaries are define by bounds. // // The passed bounds will be sorted by this function. -func NewHistogramReservoir(bounds []float64) Reservoir { +func NewHistogramReservoir(bounds []float64) *HistogramReservoir { slices.Sort(bounds) - return &histRes{ + return &HistogramReservoir{ bounds: bounds, storage: newStorage(len(bounds) + 1), } } -type histRes struct { +var _ Reservoir = &HistogramReservoir{} + +type HistogramReservoir struct { *storage // bounds are bucket bounds in ascending order. bounds []float64 } -func (r *histRes) Offer(ctx context.Context, t time.Time, v Value, a []attribute.KeyValue) { +func (r *HistogramReservoir) Offer(ctx context.Context, t time.Time, v Value, a []attribute.KeyValue) { var x float64 switch v.Type() { case Int64ValueType: From 59c0bd6b613144467d2c3e4d3e37119502b43650 Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Sat, 21 Sep 2024 17:07:46 +0000 Subject: [PATCH 12/14] export exemplar.FixedSizeReservoir --- sdk/metric/exemplar/fixed_size_reservoir.go | 28 ++++++++++--------- .../exemplar/fixed_size_reservoir_test.go | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/sdk/metric/exemplar/fixed_size_reservoir.go b/sdk/metric/exemplar/fixed_size_reservoir.go index bc9f5e43ab1..72656928646 100644 --- a/sdk/metric/exemplar/fixed_size_reservoir.go +++ b/sdk/metric/exemplar/fixed_size_reservoir.go @@ -12,15 +12,17 @@ import ( "go.opentelemetry.io/otel/attribute" ) -// NewFixedSizeReservoir returns a [Reservoir] that samples at most k exemplars. If there -// are k or less measurements made, the Reservoir will sample each one. If -// there are more than k, the Reservoir will then randomly sample all -// additional measurement with a decreasing probability. -func NewFixedSizeReservoir(k int) Reservoir { +// NewFixedSizeReservoir returns a [FixedSizeReservoir] that samples at most +// k exemplars. If there are k or less measurements made, the Reservoir will +// sample each one. If there are more than k, the Reservoir will then randomly +// sample all additional measurement with a decreasing probability. +func NewFixedSizeReservoir(k int) *FixedSizeReservoir { return newRandRes(newStorage(k)) } -type randRes struct { +var _ Reservoir = &FixedSizeReservoir{} + +type FixedSizeReservoir struct { *storage // count is the number of measurement seen. @@ -39,8 +41,8 @@ type randRes struct { rng *rand.Rand } -func newRandRes(s *storage) *randRes { - r := &randRes{ +func newRandRes(s *storage) *FixedSizeReservoir { + r := &FixedSizeReservoir{ storage: s, rng: rand.New(rand.NewSource(time.Now().UnixNano())), } @@ -50,7 +52,7 @@ func newRandRes(s *storage) *randRes { // randomFloat64 returns, as a float64, a uniform pseudo-random number in the // open interval (0.0,1.0). -func (r *randRes) randomFloat64() float64 { +func (r *FixedSizeReservoir) randomFloat64() float64 { // TODO: This does not return a uniform number. rng.Float64 returns a // uniformly random int in [0,2^53) that is divided by 2^53. Meaning it // returns multiples of 2^-53, and not all floating point numbers between 0 @@ -75,7 +77,7 @@ func (r *randRes) randomFloat64() float64 { return f } -func (r *randRes) Offer(ctx context.Context, t time.Time, n Value, a []attribute.KeyValue) { +func (r *FixedSizeReservoir) Offer(ctx context.Context, t time.Time, n Value, a []attribute.KeyValue) { // The following algorithm is "Algorithm L" from Li, Kim-Hung (4 December // 1994). "Reservoir-Sampling Algorithms of Time Complexity // O(n(1+log(N/n)))". ACM Transactions on Mathematical Software. 20 (4): @@ -131,7 +133,7 @@ func (r *randRes) Offer(ctx context.Context, t time.Time, n Value, a []attribute } // reset resets r to the initial state. -func (r *randRes) reset() { +func (r *FixedSizeReservoir) reset() { // This resets the number of exemplars known. r.count = 0 // Random index inserts should only happen after the storage is full. @@ -153,7 +155,7 @@ func (r *randRes) reset() { // advance updates the count at which the offered measurement will overwrite an // existing exemplar. -func (r *randRes) advance() { +func (r *FixedSizeReservoir) advance() { // Calculate the next value in the random number series. // // The current value of r.w is based on the max of a distribution of random @@ -180,7 +182,7 @@ func (r *randRes) advance() { r.next += int64(math.Log(r.randomFloat64())/math.Log(1-r.w)) + 1 } -func (r *randRes) Collect(dest *[]Exemplar) { +func (r *FixedSizeReservoir) Collect(dest *[]Exemplar) { r.storage.Collect(dest) // Call reset here even though it will reset r.count and restart the random // number series. This will persist any old exemplars as long as no new diff --git a/sdk/metric/exemplar/fixed_size_reservoir_test.go b/sdk/metric/exemplar/fixed_size_reservoir_test.go index 2d29481774d..1840abbd58d 100644 --- a/sdk/metric/exemplar/fixed_size_reservoir_test.go +++ b/sdk/metric/exemplar/fixed_size_reservoir_test.go @@ -44,7 +44,7 @@ func TestNewFixedSizeReservoirSamplingCorrectness(t *testing.T) { } var sum float64 - for _, m := range r.(*randRes).store { + for _, m := range r.store { sum += m.Value.Float64() } mean := sum / float64(sampleSize) From 210367bc09f4b81c0894b7e5018b7b0091e29e5b Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Sat, 21 Sep 2024 17:35:02 +0000 Subject: [PATCH 13/14] add comments --- sdk/metric/exemplar/fixed_size_reservoir.go | 18 ++++++++++++++++++ sdk/metric/exemplar/histogram_reservoir.go | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/sdk/metric/exemplar/fixed_size_reservoir.go b/sdk/metric/exemplar/fixed_size_reservoir.go index 72656928646..d37436e15a9 100644 --- a/sdk/metric/exemplar/fixed_size_reservoir.go +++ b/sdk/metric/exemplar/fixed_size_reservoir.go @@ -22,6 +22,10 @@ func NewFixedSizeReservoir(k int) *FixedSizeReservoir { var _ Reservoir = &FixedSizeReservoir{} +// FixedSizeReservoir is a [Reservoir] that samples at most k exemplars. If +// there are k or less measurements made, the Reservoir will sample each one. +// If there are more than k, the Reservoir will then randomly sample all +// additional measurement with a decreasing probability. type FixedSizeReservoir struct { *storage @@ -77,6 +81,17 @@ func (r *FixedSizeReservoir) randomFloat64() float64 { return f } +// Offer accepts the parameters associated with a measurement. The +// parameters will be stored as an exemplar if the Reservoir decides to +// sample the measurement. +// +// The passed ctx needs to contain any baggage or span that were active +// when the measurement was made. This information may be used by the +// Reservoir in making a sampling decision. +// +// The time t is the time when the measurement was made. The v and a +// parameters are the value and dropped (filtered) attributes of the +// measurement respectively. func (r *FixedSizeReservoir) Offer(ctx context.Context, t time.Time, n Value, a []attribute.KeyValue) { // The following algorithm is "Algorithm L" from Li, Kim-Hung (4 December // 1994). "Reservoir-Sampling Algorithms of Time Complexity @@ -182,6 +197,9 @@ func (r *FixedSizeReservoir) advance() { r.next += int64(math.Log(r.randomFloat64())/math.Log(1-r.w)) + 1 } +// Collect returns all the held exemplars. +// +// The Reservoir state is preserved after this call. func (r *FixedSizeReservoir) Collect(dest *[]Exemplar) { r.storage.Collect(dest) // Call reset here even though it will reset r.count and restart the random diff --git a/sdk/metric/exemplar/histogram_reservoir.go b/sdk/metric/exemplar/histogram_reservoir.go index 23059e99fbd..c27545a409a 100644 --- a/sdk/metric/exemplar/histogram_reservoir.go +++ b/sdk/metric/exemplar/histogram_reservoir.go @@ -27,6 +27,9 @@ func NewHistogramReservoir(bounds []float64) *HistogramReservoir { var _ Reservoir = &HistogramReservoir{} +// HistogramReservoir is a [Reservoir] that samples the last measurement that +// falls within a histogram bucket. The histogram bucket upper-boundaries are +// define by bounds. type HistogramReservoir struct { *storage @@ -34,6 +37,17 @@ type HistogramReservoir struct { bounds []float64 } +// Offer accepts the parameters associated with a measurement. The +// parameters will be stored as an exemplar if the Reservoir decides to +// sample the measurement. +// +// The passed ctx needs to contain any baggage or span that were active +// when the measurement was made. This information may be used by the +// Reservoir in making a sampling decision. +// +// The time t is the time when the measurement was made. The v and a +// parameters are the value and dropped (filtered) attributes of the +// measurement respectively. func (r *HistogramReservoir) Offer(ctx context.Context, t time.Time, v Value, a []attribute.KeyValue) { var x float64 switch v.Type() { From 204b29d65ead76161c2123f5239f6dec42413ced Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Thu, 26 Sep 2024 18:57:56 +0000 Subject: [PATCH 14/14] address review comments --- CHANGELOG.md | 6 +----- sdk/metric/exemplar/fixed_size_reservoir.go | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a37e27773f8..b201a5d6763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- Add `go.opentelemetry.io/otel/sdk/metric/exemplar` package which includes - `Exemplar`, `Filter`, `SampledFilter`, `AlwaysOnFilter`, - `HistogramReservoir`, `FixedSizeReservoir`, `Reservoir`, `Value` and - `ValueType` types. These will be used for configuring the exemplar reservoir - for the metrics sdk. (#5747) +- Add `go.opentelemetry.io/otel/sdk/metric/exemplar` package which includes `Exemplar`, `Filter`, `SampledFilter`, `AlwaysOnFilter`, `HistogramReservoir`, `FixedSizeReservoir`, `Reservoir`, `Value` and `ValueType` types. These will be used for configuring the exemplar reservoir for the metrics sdk. (#5747) ### Changed diff --git a/sdk/metric/exemplar/fixed_size_reservoir.go b/sdk/metric/exemplar/fixed_size_reservoir.go index d37436e15a9..34160ca608b 100644 --- a/sdk/metric/exemplar/fixed_size_reservoir.go +++ b/sdk/metric/exemplar/fixed_size_reservoir.go @@ -17,7 +17,7 @@ import ( // sample each one. If there are more than k, the Reservoir will then randomly // sample all additional measurement with a decreasing probability. func NewFixedSizeReservoir(k int) *FixedSizeReservoir { - return newRandRes(newStorage(k)) + return newFixedSizeReservoir(newStorage(k)) } var _ Reservoir = &FixedSizeReservoir{} @@ -45,7 +45,7 @@ type FixedSizeReservoir struct { rng *rand.Rand } -func newRandRes(s *storage) *FixedSizeReservoir { +func newFixedSizeReservoir(s *storage) *FixedSizeReservoir { r := &FixedSizeReservoir{ storage: s, rng: rand.New(rand.NewSource(time.Now().UnixNano())),