diff --git a/sdk/metric/instrument.go b/sdk/metric/instrument.go index 429ac51acb8..e1e75ca8b85 100644 --- a/sdk/metric/instrument.go +++ b/sdk/metric/instrument.go @@ -30,6 +30,12 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" ) +var ( + zeroUnit unit.Unit + zeroInstrumentKind InstrumentKind + zeroScope instrumentation.Scope +) + // InstrumentKind is the identifier of a group of instruments that all // performing the same function. type InstrumentKind uint8 @@ -79,6 +85,86 @@ type Instrument struct { nonComparable // nolint: unused } +// mask returns a copy of p with all non-zero-value fields of m replacing the +// fields of the returned copy. +func (p Instrument) mask(m Instrument) Instrument { + if m.Name != "" { + p.Name = m.Name + } + if m.Description != "" { + p.Description = m.Description + } + if m.Kind != zeroInstrumentKind { + p.Kind = m.Kind + } + if m.Unit != zeroUnit { + p.Unit = m.Unit + } + if m.Scope.Name != "" { + p.Scope.Name = m.Scope.Name + } + if m.Scope.Version != "" { + p.Scope.Version = m.Scope.Version + } + if m.Scope.SchemaURL != "" { + p.Scope.SchemaURL = m.Scope.SchemaURL + } + return p +} + +// empty returns if all fields of p are their zero-value. +func (p Instrument) empty() bool { + return p.Name == "" && + p.Description == "" && + p.Kind == zeroInstrumentKind && + p.Unit == zeroUnit && + p.Scope == zeroScope +} + +// matches returns if all the non-zero-value fields of o match the +// corresponding fields of p. +// +// If o is empty true is returned. +func (p Instrument) matches(o Instrument) bool { + return p.matchesName(o) && + p.matchesDescription(o) && + p.matchesKind(o) && + p.matchesUnit(o) && + p.matchesScope(o) +} + +// matchesName returns true if the Name field of o is a non-zero-value and +// equals the Name field of p, otherwise false. +func (p Instrument) matchesName(o Instrument) bool { + return p.Name == "" || p.Name == o.Name +} + +// matchesDescription returns true if the Description field of o is a +// non-zero-value and equals the Description field of p, otherwise false. +func (p Instrument) matchesDescription(o Instrument) bool { + return p.Description == "" || p.Description == o.Description +} + +// matchesKind returns true if the Kind field of o is a non-zero-value and +// equals the Kind field of p, otherwise false. +func (p Instrument) matchesKind(o Instrument) bool { + return p.Kind == zeroInstrumentKind || p.Kind == o.Kind +} + +// matchesUnit returns true if the Unit field of o is a non-zero-value and +// equals the Unit field of p, otherwise false. +func (p Instrument) matchesUnit(o Instrument) bool { + return p.Unit == zeroUnit || p.Unit == o.Unit +} + +// matchesScope returns true if the Scope field of o is a non-zero-value and +// equals the Scope field of p, otherwise false. +func (p Instrument) matchesScope(o Instrument) bool { + return (p.Scope.Name == "" || p.Scope.Name == o.Scope.Name) && + (p.Scope.Version == "" || p.Scope.Version == o.Scope.Version) && + (p.Scope.SchemaURL == "" || p.Scope.SchemaURL == o.Scope.SchemaURL) +} + // Stream describes the stream of data an instrument produces. type Stream struct { Instrument diff --git a/sdk/metric/view.go b/sdk/metric/view.go index fa3d38f0d8b..6858bb74646 100644 --- a/sdk/metric/view.go +++ b/sdk/metric/view.go @@ -14,8 +14,82 @@ package metric // import "go.opentelemetry.io/otel/sdk/metric" +import ( + "regexp" + "strings" + + "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/sdk/metric/aggregation" +) + // View is an override to the default behavior of the SDK. It defines how data // should be collected for certain instruments. It returns true and the exact // Stream to use for matching Instruments. Otherwise, if the view does not // match, false is returned. type View func(Instrument) (Stream, bool) + +// NewView returns a View that applies the Stream mask for all instruments that +// match criteria. The returned View will only apply mask if all non-zero-value +// fields of criteria match the corresponding Instrument passed to the view. If +// no criteria are provided, all field of criteria are their zero-values, a +// view that matches no instruments is returned. +// +// The Name field of criteria supports wildcard pattern matching. The wildcard +// "*" is recognized as matching zero or more characters, and "?" is recognized +// as matching exactly one character. For example, a pattern of "*" will match +// all instrument names. +// +// The Stream mask only applies updates for non-zero-value fields. By default, +// the Instrument the View matches against will be use for the returned Stream +// and no Aggregation or AttributeFilter are set. If mask has a non-zero-value +// value for any of the Aggregation or AttributeFilter fields, or any of the +// Instrument fields, that value is used instead of the default. If you need to +// zero out an Stream field returned from a View, create a View directly. +func NewView(criteria Instrument, mask Stream) View { + if criteria.empty() { + return func(Instrument) (Stream, bool) { return Stream{}, false } + } + + var matchFunc func(Instrument) bool + if strings.ContainsAny(criteria.Name, "*?") { + pattern := regexp.QuoteMeta(criteria.Name) + pattern = "^" + pattern + "$" + pattern = strings.ReplaceAll(pattern, "\\?", ".") + pattern = strings.ReplaceAll(pattern, "\\*", ".*") + re := regexp.MustCompile(pattern) + matchFunc = func(p Instrument) bool { + return re.MatchString(p.Name) && + criteria.matchesDescription(p) && + criteria.matchesKind(p) && + criteria.matchesUnit(p) && + criteria.matchesScope(p) + } + } else { + matchFunc = criteria.matches + } + + var agg aggregation.Aggregation + if mask.Aggregation != nil { + agg = mask.Aggregation.Copy() + if err := agg.Err(); err != nil { + global.Error( + err, "not using aggregation with view", + "aggregation", agg, + "view", criteria, + ) + agg = nil + } + } + + return func(p Instrument) (Stream, bool) { + if matchFunc(p) { + stream := Stream{ + Instrument: p.mask(mask.Instrument), + Aggregation: agg, + AttributeFilter: mask.AttributeFilter, + } + return stream, true + } + return Stream{}, false + } +}