1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00
Files
opentelemetry-go/metric/instrument.go
T
David Ashpole 48dd8b1a09 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>
2026-04-13 12:47:46 -04:00

404 lines
11 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metric // import "go.opentelemetry.io/otel/metric"
import (
"go.opentelemetry.io/otel/attribute"
)
// Observable is used as a grouping mechanism for all instruments that are
// updated within a Callback.
type Observable interface {
observable()
}
// InstrumentOption applies options to all instruments.
type InstrumentOption interface {
Int64CounterOption
Int64UpDownCounterOption
Int64HistogramOption
Int64GaugeOption
Int64ObservableCounterOption
Int64ObservableUpDownCounterOption
Int64ObservableGaugeOption
Float64CounterOption
Float64UpDownCounterOption
Float64HistogramOption
Float64GaugeOption
Float64ObservableCounterOption
Float64ObservableUpDownCounterOption
Float64ObservableGaugeOption
}
// HistogramOption applies options to histogram instruments.
type HistogramOption interface {
Int64HistogramOption
Float64HistogramOption
}
type descOpt string
func (o descOpt) applyFloat64Counter(c Float64CounterConfig) Float64CounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64UpDownCounter(c Float64UpDownCounterConfig) Float64UpDownCounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64HistogramConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64ObservableUpDownCounter(
c Float64ObservableUpDownCounterConfig,
) Float64ObservableUpDownCounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64ObservableGauge(c Float64ObservableGaugeConfig) Float64ObservableGaugeConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64Counter(c Int64CounterConfig) Int64CounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64UpDownCounter(c Int64UpDownCounterConfig) Int64UpDownCounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64ObservableUpDownCounter(
c Int64ObservableUpDownCounterConfig,
) Int64ObservableUpDownCounterConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64ObservableGaugeConfig {
c.description = string(o)
return c
}
// WithDescription sets the instrument description.
func WithDescription(desc string) InstrumentOption { return descOpt(desc) }
type unitOpt string
func (o unitOpt) applyFloat64Counter(c Float64CounterConfig) Float64CounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64UpDownCounter(c Float64UpDownCounterConfig) Float64UpDownCounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64HistogramConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64ObservableUpDownCounter(
c Float64ObservableUpDownCounterConfig,
) Float64ObservableUpDownCounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64ObservableGauge(c Float64ObservableGaugeConfig) Float64ObservableGaugeConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64Counter(c Int64CounterConfig) Int64CounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64UpDownCounter(c Int64UpDownCounterConfig) Int64UpDownCounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64ObservableUpDownCounter(
c Int64ObservableUpDownCounterConfig,
) Int64ObservableUpDownCounterConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64ObservableGauge(c Int64ObservableGaugeConfig) Int64ObservableGaugeConfig {
c.unit = string(o)
return c
}
// WithUnit sets the instrument unit.
//
// The unit u should be defined using the appropriate [UCUM](https://ucum.org) case-sensitive code.
func WithUnit(u string) InstrumentOption { return unitOpt(u) }
// WithExplicitBucketBoundaries sets the instrument explicit bucket boundaries.
//
// This option is considered "advisory", and may be ignored by API implementations.
func WithExplicitBucketBoundaries(bounds ...float64) HistogramOption { return bucketOpt(bounds) }
type bucketOpt []float64
func (o bucketOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64HistogramConfig {
c.explicitBucketBoundaries = o
return c
}
func (o bucketOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfig {
c.explicitBucketBoundaries = o
return c
}
// AddOption applies options to an addition measurement. See
// [MeasurementOption] for other options that can be used as an AddOption.
type AddOption interface {
applyAdd(AddConfig) AddConfig
}
// AddConfig contains options for an addition measurement.
type AddConfig struct {
attrs attribute.Set
}
// NewAddConfig returns a new [AddConfig] with all opts applied.
func NewAddConfig(opts []AddOption) AddConfig {
config := AddConfig{attrs: *attribute.EmptySet()}
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyAdd(config)
}
return config
}
// Attributes returns the configured attribute set.
func (c AddConfig) Attributes() attribute.Set {
return c.attrs
}
// RecordOption applies options to an addition measurement. See
// [MeasurementOption] for other options that can be used as a RecordOption.
type RecordOption interface {
applyRecord(RecordConfig) RecordConfig
}
// RecordConfig contains options for a recorded measurement.
type RecordConfig struct {
attrs attribute.Set
}
// NewRecordConfig returns a new [RecordConfig] with all opts applied.
func NewRecordConfig(opts []RecordOption) RecordConfig {
config := RecordConfig{attrs: *attribute.EmptySet()}
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyRecord(config)
}
return config
}
// Attributes returns the configured attribute set.
func (c RecordConfig) Attributes() attribute.Set {
return c.attrs
}
// ObserveOption applies options to an addition measurement. See
// [MeasurementOption] for other options that can be used as a ObserveOption.
type ObserveOption interface {
applyObserve(ObserveConfig) ObserveConfig
}
// ObserveConfig contains options for an observed measurement.
type ObserveConfig struct {
attrs attribute.Set
}
// NewObserveConfig returns a new [ObserveConfig] with all opts applied.
func NewObserveConfig(opts []ObserveOption) ObserveConfig {
config := ObserveConfig{attrs: *attribute.EmptySet()}
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyObserve(config)
}
return config
}
// Attributes returns the configured attribute set.
func (c ObserveConfig) Attributes() attribute.Set {
return c.attrs
}
// MeasurementOption applies options to all instrument measurement.
type MeasurementOption interface {
AddOption
RecordOption
ObserveOption
}
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 {
// NewMergeIterator uses the first value for any duplicates.
iter := attribute.NewMergeIterator(&b, &a)
merged := make([]attribute.KeyValue, 0, a.Len()+b.Len())
for iter.Next() {
merged = append(merged, iter.Attribute())
}
return attribute.NewSet(merged...)
}
func (o *attrOpt) applyAdd(c AddConfig) AddConfig {
switch {
case o.set.Len() == 0:
case c.attrs.Len() == 0:
c.attrs = o.set
default:
c.attrs = mergeSets(c.attrs, o.set)
}
return c
}
func (o *attrOpt) applyRecord(c RecordConfig) RecordConfig {
switch {
case o.set.Len() == 0:
case c.attrs.Len() == 0:
c.attrs = o.set
default:
c.attrs = mergeSets(c.attrs, o.set)
}
return c
}
func (o *attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
switch {
case o.set.Len() == 0:
case c.attrs.Len() == 0:
c.attrs = o.set
default:
c.attrs = mergeSets(c.attrs, o.set)
}
return c
}
// WithAttributeSet sets the attribute Set associated with a measurement is
// made with.
//
// 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}
}
// WithAttributes converts attributes into an attribute Set and sets the Set to
// be associated with a measurement. This is shorthand for:
//
// cp := make([]attribute.KeyValue, len(attributes))
// copy(cp, attributes)
// WithAttributeSet(attribute.NewSet(cp...))
//
// [attribute.NewSet] may modify the passed attributes so this will make a copy
// of attributes before creating a set in order to ensure this function is
// concurrent safe. This makes this option function less optimized in
// comparison to [WithAttributeSet]. Therefore, [WithAttributeSet] should be
// preferred for performance sensitive code.
//
// 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...)}
}