// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:generate stringer -type=InstrumentKind -trimprefix=InstrumentKind package metric // import "go.opentelemetry.io/otel/sdk/metric" import ( "context" "errors" "fmt" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" ) var zeroScope instrumentation.Scope // InstrumentKind is the identifier of a group of instruments that all // performing the same function. type InstrumentKind uint8 const ( // instrumentKindUndefined is an undefined instrument kind, it should not // be used by any initialized type. instrumentKindUndefined InstrumentKind = 0 // nolint:deadcode,varcheck,unused // InstrumentKindCounter identifies a group of instruments that record // increasing values synchronously with the code path they are measuring. InstrumentKindCounter InstrumentKind = 1 // InstrumentKindUpDownCounter identifies a group of instruments that // record increasing and decreasing values synchronously with the code path // they are measuring. InstrumentKindUpDownCounter InstrumentKind = 2 // InstrumentKindHistogram identifies a group of instruments that record a // distribution of values synchronously with the code path they are // measuring. InstrumentKindHistogram InstrumentKind = 3 // InstrumentKindObservableCounter identifies a group of instruments that // record increasing values in an asynchronous callback. InstrumentKindObservableCounter InstrumentKind = 4 // InstrumentKindObservableUpDownCounter identifies a group of instruments // that record increasing and decreasing values in an asynchronous // callback. InstrumentKindObservableUpDownCounter InstrumentKind = 5 // InstrumentKindObservableGauge identifies a group of instruments that // record current values in an asynchronous callback. InstrumentKindObservableGauge InstrumentKind = 6 // InstrumentKindGauge identifies a group of instruments that record // instantaneous values synchronously with the code path they are // measuring. InstrumentKindGauge InstrumentKind = 7 ) type nonComparable [0]func() // nolint: unused // This is indeed used. // Instrument describes properties an instrument is created with. type Instrument struct { // Name is the human-readable identifier of the instrument. Name string // Description describes the purpose of the instrument. Description string // Kind defines the functional group of the instrument. Kind InstrumentKind // Unit is the unit of measurement recorded by the instrument. Unit string // Scope identifies the instrumentation that created the instrument. Scope instrumentation.Scope // Ensure forward compatibility if non-comparable fields need to be added. nonComparable // nolint: unused } // IsEmpty returns if all Instrument fields are their zero-value. func (i Instrument) IsEmpty() bool { return i.Name == "" && i.Description == "" && i.Kind == instrumentKindUndefined && i.Unit == "" && i.Scope == zeroScope } // matches returns whether all the non-zero-value fields of i match the // corresponding fields of other. If i is empty it will match all other, and // true will always be returned. func (i Instrument) matches(other Instrument) bool { return i.matchesName(other) && i.matchesDescription(other) && i.matchesKind(other) && i.matchesUnit(other) && i.matchesScope(other) } // matchesName returns true if the Name of i is "" or it equals the Name of // other, otherwise false. func (i Instrument) matchesName(other Instrument) bool { return i.Name == "" || i.Name == other.Name } // matchesDescription returns true if the Description of i is "" or it equals // the Description of other, otherwise false. func (i Instrument) matchesDescription(other Instrument) bool { return i.Description == "" || i.Description == other.Description } // matchesKind returns true if the Kind of i is its zero-value or it equals the // Kind of other, otherwise false. func (i Instrument) matchesKind(other Instrument) bool { return i.Kind == instrumentKindUndefined || i.Kind == other.Kind } // matchesUnit returns true if the Unit of i is its zero-value or it equals the // Unit of other, otherwise false. func (i Instrument) matchesUnit(other Instrument) bool { return i.Unit == "" || i.Unit == other.Unit } // matchesScope returns true if the Scope of i is its zero-value or it equals // the Scope of other, otherwise false. func (i Instrument) matchesScope(other Instrument) bool { return (i.Scope.Name == "" || i.Scope.Name == other.Scope.Name) && (i.Scope.Version == "" || i.Scope.Version == other.Scope.Version) && (i.Scope.SchemaURL == "" || i.Scope.SchemaURL == other.Scope.SchemaURL) } // Stream describes the stream of data an instrument produces. type Stream struct { // Name is the human-readable identifier of the stream. Name string // Description describes the purpose of the data. Description string // Unit is the unit of measurement recorded. Unit string // Aggregation the stream uses for an instrument. Aggregation Aggregation // AttributeFilter is an attribute Filter applied to the attributes // recorded for an instrument's measurement. If the filter returns false // the attribute will not be recorded, otherwise, if it returns true, it // will record the attribute. // // Use NewAllowKeysFilter from "go.opentelemetry.io/otel/attribute" to // provide an allow-list of attribute keys here. AttributeFilter attribute.Filter } // instID are the identifying properties of a instrument. type instID struct { // Name is the name of the stream. Name string // Description is the description of the stream. Description string // Kind defines the functional group of the instrument. Kind InstrumentKind // Unit is the unit of the stream. Unit string // Number is the number type of the stream. Number string } // Returns a normalized copy of the instID i. // // Instrument names are considered case-insensitive. Standardize the instrument // name to always be lowercase for the returned instID so it can be compared // without the name casing affecting the comparison. func (i instID) normalize() instID { i.Name = strings.ToLower(i.Name) return i } type int64Inst struct { measures []aggregate.Measure[int64] embedded.Int64Counter embedded.Int64UpDownCounter embedded.Int64Histogram embedded.Int64Gauge } var ( _ metric.Int64Counter = (*int64Inst)(nil) _ metric.Int64UpDownCounter = (*int64Inst)(nil) _ metric.Int64Histogram = (*int64Inst)(nil) _ metric.Int64Gauge = (*int64Inst)(nil) ) func (i *int64Inst) Add(ctx context.Context, val int64, opts ...metric.AddOption) { c := metric.NewAddConfig(opts) i.aggregate(ctx, val, c.Attributes()) } func (i *int64Inst) Record(ctx context.Context, val int64, opts ...metric.RecordOption) { c := metric.NewRecordConfig(opts) i.aggregate(ctx, val, c.Attributes()) } func (i *int64Inst) aggregate(ctx context.Context, val int64, s attribute.Set) { // nolint:revive // okay to shadow pkg with method. for _, in := range i.measures { in(ctx, val, s) } } type float64Inst struct { measures []aggregate.Measure[float64] embedded.Float64Counter embedded.Float64UpDownCounter embedded.Float64Histogram embedded.Float64Gauge } var ( _ metric.Float64Counter = (*float64Inst)(nil) _ metric.Float64UpDownCounter = (*float64Inst)(nil) _ metric.Float64Histogram = (*float64Inst)(nil) _ metric.Float64Gauge = (*float64Inst)(nil) ) func (i *float64Inst) Add(ctx context.Context, val float64, opts ...metric.AddOption) { c := metric.NewAddConfig(opts) i.aggregate(ctx, val, c.Attributes()) } func (i *float64Inst) Record(ctx context.Context, val float64, opts ...metric.RecordOption) { c := metric.NewRecordConfig(opts) i.aggregate(ctx, val, c.Attributes()) } func (i *float64Inst) aggregate(ctx context.Context, val float64, s attribute.Set) { for _, in := range i.measures { in(ctx, val, s) } } // observablID is a comparable unique identifier of an observable. type observablID[N int64 | float64] struct { name string description string kind InstrumentKind unit string scope instrumentation.Scope } type float64Observable struct { metric.Float64Observable *observable[float64] embedded.Float64ObservableCounter embedded.Float64ObservableUpDownCounter embedded.Float64ObservableGauge } var ( _ metric.Float64ObservableCounter = float64Observable{} _ metric.Float64ObservableUpDownCounter = float64Observable{} _ metric.Float64ObservableGauge = float64Observable{} ) func newFloat64Observable(m *meter, kind InstrumentKind, name, desc, u string) float64Observable { return float64Observable{ observable: newObservable[float64](m, kind, name, desc, u), } } type int64Observable struct { metric.Int64Observable *observable[int64] embedded.Int64ObservableCounter embedded.Int64ObservableUpDownCounter embedded.Int64ObservableGauge } var ( _ metric.Int64ObservableCounter = int64Observable{} _ metric.Int64ObservableUpDownCounter = int64Observable{} _ metric.Int64ObservableGauge = int64Observable{} ) func newInt64Observable(m *meter, kind InstrumentKind, name, desc, u string) int64Observable { return int64Observable{ observable: newObservable[int64](m, kind, name, desc, u), } } type observable[N int64 | float64] struct { metric.Observable observablID[N] meter *meter measures measures[N] dropAggregation bool } func newObservable[N int64 | float64](m *meter, kind InstrumentKind, name, desc, u string) *observable[N] { return &observable[N]{ observablID: observablID[N]{ name: name, description: desc, kind: kind, unit: u, scope: m.scope, }, meter: m, } } // observe records the val for the set of attrs. func (o *observable[N]) observe(val N, s attribute.Set) { o.measures.observe(val, s) } func (o *observable[N]) appendMeasures(meas []aggregate.Measure[N]) { o.measures = append(o.measures, meas...) } type measures[N int64 | float64] []aggregate.Measure[N] // observe records the val for the set of attrs. func (m measures[N]) observe(val N, s attribute.Set) { for _, in := range m { in(context.Background(), val, s) } } var errEmptyAgg = errors.New("no aggregators for observable instrument") // registerable returns an error if the observable o should not be registered, // and nil if it should. An errEmptyAgg error is returned if o is effectively a // no-op because it does not have any aggregators. Also, an error is returned // if scope defines a Meter other than the one o was created by. func (o *observable[N]) registerable(m *meter) error { if len(o.measures) == 0 { return errEmptyAgg } if m != o.meter { return fmt.Errorf( "invalid registration: observable %q from Meter %q, registered with Meter %q", o.name, o.scope.Name, m.scope.Name, ) } return nil }