You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
Add x.Settable to allow reusing attribute options (#8178)
Fixes https://github.com/open-telemetry/opentelemetry-go/issues/7851 This adopts this proposal from @MrAlias: https://github.com/open-telemetry/opentelemetry-go/issues/7851#issuecomment-3837399805 This adds a Set function to attrOpt, and changes attrOpt functions to use a pointer receiver. Users can use this by checking if the attribute option implements the `Settable` interface in `metric/x`. There are no public API changes to the metric package, as this is still an experiment. See the benchmark change and the interface documentation for how it can be used to avoid calling metric.WithAttributeSet. I updated the benchmark in https://github.com/open-telemetry/opentelemetry-go/commit/448394b549375f4f6742201e199e0339d0b78524, but didn't commit it here to avoid a dependency between the SDK and the `metric/x` package. As expected, this removes one allocation from the Dynamic case! The results are: ``` │ main.txt │ resettable.txt │ │ sec/op │ sec/op vs base │ EndToEndCounterAdd/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24 238.2n ± 5% 220.7n ± 9% -7.39% (p=0.002 n=6) EndToEndCounterAdd/NoFilter/Attributes/5/Dynamic/WithAttributeSet-24 611.1n ± 4% 620.3n ± 2% ~ (p=0.394 n=6) EndToEndCounterAdd/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24 1.147µ ± 7% 1.171µ ± 5% ~ (p=0.258 n=6) EndToEndCounterAdd/Filtered/Attributes/1/Dynamic/WithAttributeSet-24 363.8n ± 6% 363.5n ± 6% ~ (p=1.000 n=6) EndToEndCounterAdd/Filtered/Attributes/5/Dynamic/WithAttributeSet-24 1.464µ ± 5% 1.475µ ± 10% ~ (p=0.727 n=6) EndToEndCounterAdd/Filtered/Attributes/10/Dynamic/WithAttributeSet-24 2.924µ ± 7% 2.589µ ± 6% -11.47% (p=0.002 n=6) │ main.txt │ resettable.txt │ │ B/op │ B/op vs base │ EndToEndCounterAdd/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24 88.00 ± 0% 64.00 ± 0% -27.27% (p=0.002 n=6) EndToEndCounterAdd/NoFilter/Attributes/5/Dynamic/WithAttributeSet-24 344.0 ± 0% 321.0 ± 0% -6.69% (p=0.002 n=6) EndToEndCounterAdd/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24 729.0 ± 0% 706.0 ± 0% -3.16% (p=0.002 n=6) EndToEndCounterAdd/Filtered/Attributes/1/Dynamic/WithAttributeSet-24 152.0 ± 0% 128.0 ± 0% -15.79% (p=0.002 n=6) EndToEndCounterAdd/Filtered/Attributes/5/Dynamic/WithAttributeSet-24 921.0 ± 0% 899.0 ± 0% -2.39% (p=0.002 n=6) EndToEndCounterAdd/Filtered/Attributes/10/Dynamic/WithAttributeSet-24 2.026Ki ± 0% 2.006Ki ± 0% -1.01% (p=0.002 n=6) │ main.txt │ resettable.txt │ │ allocs/op │ allocs/op vs base │ EndToEndCounterAdd/NoFilter/Attributes/1/Dynamic/WithAttributeSet-24 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.002 n=6) EndToEndCounterAdd/NoFilter/Attributes/5/Dynamic/WithAttributeSet-24 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.002 n=6) EndToEndCounterAdd/NoFilter/Attributes/10/Dynamic/WithAttributeSet-24 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.002 n=6) EndToEndCounterAdd/Filtered/Attributes/1/Dynamic/WithAttributeSet-24 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.002 n=6) EndToEndCounterAdd/Filtered/Attributes/5/Dynamic/WithAttributeSet-24 4.000 ± 0% 3.000 ± 0% -25.00% (p=0.002 n=6) EndToEndCounterAdd/Filtered/Attributes/10/Dynamic/WithAttributeSet-24 4.000 ± 0% 3.000 ± 0% -25.00% (p=0.002 n=6) ``` --------- Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
Set `OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=<max_size>` to enable for all periodic readers.
|
||||
See `go.opentelemetry.io/otel/sdk/metric/internal/x` for feature documentation. (#8071)
|
||||
- Add `WithDefaultAttributes` to `go.opentelemetry.io/otel/metric/x` to support setting default attributes on instruments. (#8135)
|
||||
- Add `Settable` to `go.opentelemetry.io/otel/metric/x` to allow reusing attribute options. (#8178)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
+21
-5
@@ -310,6 +310,10 @@ type attrOpt struct {
|
||||
set attribute.Set
|
||||
}
|
||||
|
||||
func (o *attrOpt) Set(set attribute.Set) {
|
||||
o.set = set
|
||||
}
|
||||
|
||||
// mergeSets returns the union of keys between a and b. Any duplicate keys will
|
||||
// use the value associated with b.
|
||||
func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
@@ -322,7 +326,7 @@ func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
return attribute.NewSet(merged...)
|
||||
}
|
||||
|
||||
func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
func (o *attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -333,7 +337,7 @@ func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
func (o *attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -344,7 +348,7 @@ func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
func (o *attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -361,8 +365,14 @@ func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
// If multiple WithAttributeSet or WithAttributes options are passed the
|
||||
// attributes will be merged together in the order they are passed. Attributes
|
||||
// with duplicate keys will use the last value passed.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][attribute.Set], which can be
|
||||
// used to replace the option's attribute set and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
return attrOpt{set: attributes}
|
||||
return &attrOpt{set: attributes}
|
||||
}
|
||||
|
||||
// WithAttributes converts attributes into an attribute Set and sets the Set to
|
||||
@@ -380,8 +390,14 @@ func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
//
|
||||
// See [WithAttributeSet] for information about how multiple WithAttributes are
|
||||
// merged.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][[]attribute.KeyValue], which can be
|
||||
// used to replace the option's attributes and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption {
|
||||
cp := make([]attribute.KeyValue, len(attributes))
|
||||
copy(cp, attributes)
|
||||
return attrOpt{set: attribute.NewSet(cp...)}
|
||||
return &attrOpt{set: attribute.NewSet(cp...)}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
@@ -127,3 +128,34 @@ func TestWithAttributesConcurrentSafe(*testing.T) {
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSettableOptions(t *testing.T) {
|
||||
type settable interface {
|
||||
Set(attribute.Set)
|
||||
}
|
||||
|
||||
aliceAttr := attribute.String("user", "Alice")
|
||||
alice := attribute.NewSet(aliceAttr)
|
||||
bobAttr := attribute.String("user", "Bob")
|
||||
bob := attribute.NewSet(bobAttr)
|
||||
|
||||
t.Run("WithAttributeSet", func(t *testing.T) {
|
||||
opt := WithAttributeSet(alice)
|
||||
r, ok := opt.(settable)
|
||||
require.True(t, ok, "WithAttributeSet option does not implement settable")
|
||||
|
||||
r.Set(bob)
|
||||
c := NewAddConfig([]AddOption{opt.(AddOption)})
|
||||
assert.Equal(t, bob, c.Attributes())
|
||||
})
|
||||
|
||||
t.Run("WithAttributes", func(t *testing.T) {
|
||||
opt := WithAttributes(aliceAttr)
|
||||
r, ok := opt.(settable)
|
||||
require.True(t, ok, "WithAttributes option does not implement settable")
|
||||
|
||||
r.Set(bob)
|
||||
c := NewAddConfig([]AddOption{opt.(AddOption)})
|
||||
assert.Equal(t, bob, c.Attributes())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,3 +27,32 @@ func (o defaultAttributesOption) AllowedKeys() []attribute.Key {
|
||||
func WithDefaultAttributes(keys ...attribute.Key) metric.InstrumentOption {
|
||||
return defaultAttributesOption{keys: keys}
|
||||
}
|
||||
|
||||
// Settable is an optional interface that Options can implement
|
||||
// to allow reuse without additional allocations.
|
||||
//
|
||||
// Example usage with sync.Pool:
|
||||
//
|
||||
// var optionPool = sync.Pool{
|
||||
// New: func() any {
|
||||
// return metric.WithAttributeSet(*attribute.EmptySet())
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// func record(ctx context.Context, counter metric.Int64Counter, set attribute.Set) {
|
||||
// opt := optionPool.Get().(metric.MeasurementOption)
|
||||
// defer optionPool.Put(opt)
|
||||
//
|
||||
// if r, ok := opt.(x.Settable[attribute.Set]); ok {
|
||||
// r.Set(set)
|
||||
// } else {
|
||||
// opt = metric.WithAttributeSet(set)
|
||||
// }
|
||||
// counter.Add(ctx, 1, opt)
|
||||
// }
|
||||
//
|
||||
// WARNING: It is the user's responsibility to ensure that the option is not
|
||||
// concurrently set while being passed to the API or used by another goroutine.
|
||||
type Settable[T any] interface {
|
||||
Set(T)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user