1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-12 10:04:29 +02:00
opentelemetry-go/sdk/metric/view.go
Tyler Yahn 2e780d8e39
Add View, NewView, Instrument, Stream, and InstrumentKind to sdk/metric with unit tests (#3459)
* Add the InstrumentKind type and vars to sdk/metric

* Add the Instrument type to sdk/metric

* Add the Stream type to sdk/metric

* Add the View type to sdk/metric

* Add NewView to create Views matching OTel spec

* Add unit tests for NewView

* Add changes to changelog

* Apply suggestions from code review

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>

* Update CHANGELOG.md

* Update match and mask comments of Instrument

* Explain wildcard logic in NewView with comment

* Drop views that replace name for multi-inst match

* Comment how users are expected to match zero-vals

* Remove InstrumentKind and Scope from Stream

* Fix redundant word in NewView comment

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
Co-authored-by: Chester Cheung <cheung.zhy.csu@gmail.com>
2022-11-16 08:18:02 -08:00

125 lines
4.0 KiB
Go

// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric"
import (
"errors"
"regexp"
"strings"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/sdk/metric/aggregation"
)
var (
errMultiInst = errors.New("name replacement for multiple instruments")
emptyView = func(Instrument) (Stream, bool) { return Stream{}, false }
)
// 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. If you need to match a
// zero-value field, create a View directly.
//
// 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 Name,
// Description, and Unit of the returned Stream and no Aggregation or
// AttributeFilter are set. All non-zero-value fields of mask are 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 emptyView
}
var matchFunc func(Instrument) bool
if strings.ContainsAny(criteria.Name, "*?") {
if mask.Name != "" {
global.Error(
errMultiInst, "dropping view",
"criteria", criteria,
"mask", mask,
)
return emptyView
}
// Handle branching here in NewView instead of criteria.matches so
// criteria.matches remains inlinable for the simple case.
pattern := regexp.QuoteMeta(criteria.Name)
pattern = "^" + pattern + "$"
pattern = strings.ReplaceAll(pattern, `\?`, ".")
pattern = strings.ReplaceAll(pattern, `\*`, ".*")
re := regexp.MustCompile(pattern)
matchFunc = func(i Instrument) bool {
return re.MatchString(i.Name) &&
criteria.matchesDescription(i) &&
criteria.matchesKind(i) &&
criteria.matchesUnit(i) &&
criteria.matchesScope(i)
}
} 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",
"criteria", criteria,
"mask", mask,
)
agg = nil
}
}
return func(i Instrument) (Stream, bool) {
if matchFunc(i) {
return Stream{
Name: nonZero(mask.Name, i.Name),
Description: nonZero(mask.Description, i.Description),
Unit: nonZero(mask.Unit, i.Unit),
Aggregation: agg,
AttributeFilter: mask.AttributeFilter,
}, true
}
return Stream{}, false
}
}
// nonZero returns v if it is non-zero-valued, otherwise alt.
func nonZero[T comparable](v, alt T) T {
var zero T
if v != zero {
return v
}
return alt
}