Skip to content

Commit

Permalink
Add NewView to create Views matching OTel spec
Browse files Browse the repository at this point in the history
  • Loading branch information
MrAlias committed Nov 10, 2022
1 parent 607ba0f commit bdde868
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
86 changes: 86 additions & 0 deletions sdk/metric/instrument.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions sdk/metric/view.go
Expand Up @@ -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
}
}

0 comments on commit bdde868

Please sign in to comment.