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
48dd8b1a09
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>
404 lines
11 KiB
Go
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...)}
|
|
}
|