mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-04 09:43:23 +02:00
Add support for Resources in the SDK (#552)
* Add support for Resources in the SDK Add `Config` types for the push `Controller` and the `SDK`. Included with this are helper functions to configure the `ErrorHandler` and `Resource`. Add a `Resource` to the Meter `Descriptor`. The choice to add the `Resource` here (instead of say a `Record` or the `Instrument` itself) was motivated by the definition of the `Descriptor` as the way to uniquely describe a metric instrument. Update the push `Controller` and default `SDK` to pass down their configured `Resource` from instantiation to the metric instruments. * Update New SDK constructor documentation * Change NewDescriptor constructor to take opts Add DescriptorConfig and DescriptorOption to configure the metric Descriptor with the description, unit, keys, and resource. Update all function calls to NewDescriptor to use new function signature. * Apply suggestions from code review Co-Authored-By: Rahul Patel <rghetia@yahoo.com> * Update and add copyright notices * Update push controller creator func Pass the configured ErrorHandler for the controller to the SDK. * Update Resource integration with the SDK Add back the Resource field to the Descriptor that was moved in the last merge with master. Add a resource.Provider interface. Have the default SDK implement the new resource.Provider interface and integrate the new interface into the newSync/newAsync workflows. Now, if the SDK has a Resource defined it will be passed to all Descriptors created for the instruments it creates. * Remove nil check for metric SDK config * Fix and add test for API Options Add an `Equal` method to the Resource so it can be compared with github.com/google/go-cmp/cmp. Add additional test of the API Option unit tests to ensure WithResource correctly sets a new resource. * Move the resource.Provider interface to the API package Move the interface to where it is used. Fix spelling. * Remove errant line * Remove nil checks for the push controller config * Fix check SDK implements Resourcer * Apply suggestions from code review Co-Authored-By: Rahul Patel <rghetia@yahoo.com> Co-authored-by: Rahul Patel <rghetia@yahoo.com>
This commit is contained in:
parent
a01f63bec4
commit
f7df68b68b
@ -21,6 +21,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/unit"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Provider supports named Meter instances.
|
||||
@ -45,6 +46,8 @@ type Config struct {
|
||||
// Keys are recommended keys determined in the handles
|
||||
// obtained for the metric.
|
||||
Keys []core.Key
|
||||
// Resource describes the entity for which measurements are made.
|
||||
Resource resource.Resource
|
||||
}
|
||||
|
||||
// Option is an interface for applying metric options.
|
||||
@ -141,6 +144,12 @@ func (d Descriptor) NumberKind() core.NumberKind {
|
||||
return d.numberKind
|
||||
}
|
||||
|
||||
// Resource returns the Resource describing the entity for which the metric
|
||||
// instrument measures.
|
||||
func (d Descriptor) Resource() resource.Resource {
|
||||
return d.config.Resource
|
||||
}
|
||||
|
||||
// Meter is an interface to the metrics portion of the OpenTelemetry SDK.
|
||||
type Meter interface {
|
||||
// Labels returns a reference to a set of labels that cannot
|
||||
@ -212,3 +221,16 @@ type keysOption []core.Key
|
||||
func (k keysOption) Apply(config *Config) {
|
||||
config.Keys = append(config.Keys, k...)
|
||||
}
|
||||
|
||||
// WithResource applies provided Resource.
|
||||
//
|
||||
// This will override any existing Resource.
|
||||
func WithResource(r resource.Resource) Option {
|
||||
return resourceOption(r)
|
||||
}
|
||||
|
||||
type resourceOption resource.Resource
|
||||
|
||||
func (r resourceOption) Apply(config *Config) {
|
||||
config.Resource = resource.Resource(r)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"go.opentelemetry.io/otel/api/unit"
|
||||
mockTest "go.opentelemetry.io/otel/internal/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -35,19 +36,21 @@ var Must = metric.Must
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
opts []metric.Option
|
||||
keys []core.Key
|
||||
desc string
|
||||
unit unit.Unit
|
||||
name string
|
||||
opts []metric.Option
|
||||
keys []core.Key
|
||||
desc string
|
||||
unit unit.Unit
|
||||
resource resource.Resource
|
||||
}
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "no opts",
|
||||
opts: nil,
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "",
|
||||
name: "no opts",
|
||||
opts: nil,
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "",
|
||||
resource: resource.Resource{},
|
||||
},
|
||||
{
|
||||
name: "keys keys keys",
|
||||
@ -61,17 +64,19 @@ func TestOptions(t *testing.T) {
|
||||
key.New("bar"), key.New("bar2"),
|
||||
key.New("baz"), key.New("baz2"),
|
||||
},
|
||||
desc: "",
|
||||
unit: "",
|
||||
desc: "",
|
||||
unit: "",
|
||||
resource: resource.Resource{},
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
opts: []metric.Option{
|
||||
metric.WithDescription("stuff"),
|
||||
},
|
||||
keys: nil,
|
||||
desc: "stuff",
|
||||
unit: "",
|
||||
keys: nil,
|
||||
desc: "stuff",
|
||||
unit: "",
|
||||
resource: resource.Resource{},
|
||||
},
|
||||
{
|
||||
name: "description override",
|
||||
@ -79,18 +84,20 @@ func TestOptions(t *testing.T) {
|
||||
metric.WithDescription("stuff"),
|
||||
metric.WithDescription("things"),
|
||||
},
|
||||
keys: nil,
|
||||
desc: "things",
|
||||
unit: "",
|
||||
keys: nil,
|
||||
desc: "things",
|
||||
unit: "",
|
||||
resource: resource.Resource{},
|
||||
},
|
||||
{
|
||||
name: "unit",
|
||||
opts: []metric.Option{
|
||||
metric.WithUnit("s"),
|
||||
},
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "s",
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "s",
|
||||
resource: resource.Resource{},
|
||||
},
|
||||
{
|
||||
name: "unit override",
|
||||
@ -98,9 +105,20 @@ func TestOptions(t *testing.T) {
|
||||
metric.WithUnit("s"),
|
||||
metric.WithUnit("h"),
|
||||
},
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "h",
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "h",
|
||||
resource: resource.Resource{},
|
||||
},
|
||||
{
|
||||
name: "resource override",
|
||||
opts: []metric.Option{
|
||||
metric.WithResource(*resource.New(key.New("name").String("test-name"))),
|
||||
},
|
||||
keys: nil,
|
||||
desc: "",
|
||||
unit: "",
|
||||
resource: *resource.New(key.New("name").String("test-name")),
|
||||
},
|
||||
}
|
||||
for idx, tt := range testcases {
|
||||
@ -109,6 +127,7 @@ func TestOptions(t *testing.T) {
|
||||
Description: tt.desc,
|
||||
Unit: tt.unit,
|
||||
Keys: tt.keys,
|
||||
Resource: tt.resource,
|
||||
}); diff != "" {
|
||||
t.Errorf("Compare options: -got +want %s", diff)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// MeterImpl is a convenient interface for SDK and test
|
||||
@ -133,6 +134,29 @@ func Configure(opts []Option) Config {
|
||||
return config
|
||||
}
|
||||
|
||||
// Resourcer is implemented by any value that has a Resource method,
|
||||
// which returns the Resource associated with the value.
|
||||
// The Resource method is used to set the Resource for Descriptors of new
|
||||
// metric instruments.
|
||||
type Resourcer interface {
|
||||
Resource() resource.Resource
|
||||
}
|
||||
|
||||
// insertResource inserts a WithResource option at the beginning of opts
|
||||
// using the resource defined by impl if impl implements Resourcer.
|
||||
//
|
||||
// If opts contains a WithResource option already, that Option will take
|
||||
// precedence and overwrite the Resource set from impl.
|
||||
//
|
||||
// The returned []Option may uses the same underlying array as opts.
|
||||
func insertResource(impl MeterImpl, opts []Option) []Option {
|
||||
if r, ok := impl.(Resourcer); ok {
|
||||
// default to the impl resource and override if passed in opts.
|
||||
return append([]Option{WithResource(r.Resource())}, opts...)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// WrapMeterImpl constructs a `Meter` implementation from a
|
||||
// `MeterImpl` implementation.
|
||||
func WrapMeterImpl(impl MeterImpl) Meter {
|
||||
@ -159,6 +183,7 @@ func (m *wrappedMeterImpl) RecordBatch(ctx context.Context, ls LabelSet, ms ...M
|
||||
}
|
||||
|
||||
func (m *wrappedMeterImpl) newSync(name string, metricKind Kind, numberKind core.NumberKind, opts []Option) (SyncImpl, error) {
|
||||
opts = insertResource(m.impl, opts)
|
||||
return m.impl.NewSyncInstrument(NewDescriptor(name, metricKind, numberKind, opts...))
|
||||
}
|
||||
|
||||
@ -219,6 +244,7 @@ func WrapFloat64MeasureInstrument(syncInst SyncImpl, err error) (Float64Measure,
|
||||
}
|
||||
|
||||
func (m *wrappedMeterImpl) newAsync(name string, mkind Kind, nkind core.NumberKind, opts []Option, callback func(func(core.Number, LabelSet))) (AsyncImpl, error) {
|
||||
opts = insertResource(m.impl, opts)
|
||||
return m.impl.NewAsyncInstrument(
|
||||
NewDescriptor(name, mkind, nkind, opts...),
|
||||
callback)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
// Copyright 2020, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
44
sdk/metric/config.go
Normal file
44
sdk/metric/config.go
Normal file
@ -0,0 +1,44 @@
|
||||
package metric
|
||||
|
||||
import "go.opentelemetry.io/otel/sdk/resource"
|
||||
|
||||
// Config contains configuration for an SDK.
|
||||
type Config struct {
|
||||
// ErrorHandler is the function called when the SDK encounters an error.
|
||||
//
|
||||
// This option can be overridden after instantiation of the SDK
|
||||
// with the `SetErrorHandler` method.
|
||||
ErrorHandler ErrorHandler
|
||||
|
||||
// Resource is the OpenTelemetry resource associated with all Meters
|
||||
// created by the SDK.
|
||||
Resource resource.Resource
|
||||
}
|
||||
|
||||
// Option is the interface that applies the value to a configuration option.
|
||||
type Option interface {
|
||||
// Apply sets the Option value of a Config.
|
||||
Apply(*Config)
|
||||
}
|
||||
|
||||
// WithErrorHandler sets the ErrorHandler configuration option of a Config.
|
||||
func WithErrorHandler(fn ErrorHandler) Option {
|
||||
return errorHandlerOption(fn)
|
||||
}
|
||||
|
||||
type errorHandlerOption ErrorHandler
|
||||
|
||||
func (o errorHandlerOption) Apply(config *Config) {
|
||||
config.ErrorHandler = ErrorHandler(o)
|
||||
}
|
||||
|
||||
// WithResource sets the Resource configuration option of a Config.
|
||||
func WithResource(r resource.Resource) Option {
|
||||
return resourceOption(r)
|
||||
}
|
||||
|
||||
type resourceOption resource.Resource
|
||||
|
||||
func (o resourceOption) Apply(config *Config) {
|
||||
config.Resource = resource.Resource(o)
|
||||
}
|
47
sdk/metric/config_test.go
Normal file
47
sdk/metric/config_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
func TestWithErrorHandler(t *testing.T) {
|
||||
errH, reg := func() (ErrorHandler, *error) {
|
||||
e := fmt.Errorf("default invalid")
|
||||
reg := &e
|
||||
return func(err error) {
|
||||
*reg = err
|
||||
}, reg
|
||||
}()
|
||||
|
||||
c := &Config{}
|
||||
WithErrorHandler(errH).Apply(c)
|
||||
err1 := fmt.Errorf("error 1")
|
||||
c.ErrorHandler(err1)
|
||||
assert.EqualError(t, *reg, err1.Error())
|
||||
|
||||
// Ensure overwriting works.
|
||||
c = &Config{ErrorHandler: DefaultErrorHandler}
|
||||
WithErrorHandler(errH).Apply(c)
|
||||
err2 := fmt.Errorf("error 2")
|
||||
c.ErrorHandler(err2)
|
||||
assert.EqualError(t, *reg, err2.Error())
|
||||
}
|
||||
|
||||
func TestWithResource(t *testing.T) {
|
||||
r := resource.New(core.Key("A").String("a"))
|
||||
|
||||
c := &Config{}
|
||||
WithResource(*r).Apply(c)
|
||||
assert.Equal(t, *r, c.Resource)
|
||||
|
||||
// Ensure overwriting works.
|
||||
c = &Config{Resource: resource.Resource{}}
|
||||
WithResource(*r).Apply(c)
|
||||
assert.Equal(t, *r, c.Resource)
|
||||
}
|
47
sdk/metric/controller/push/config.go
Normal file
47
sdk/metric/controller/push/config.go
Normal file
@ -0,0 +1,47 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
sdk "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Config contains configuration for a push Controller.
|
||||
type Config struct {
|
||||
// ErrorHandler is the function called when the Controller encounters an error.
|
||||
//
|
||||
// This option can be overridden after instantiation of the Controller
|
||||
// with the `SetErrorHandler` method.
|
||||
ErrorHandler sdk.ErrorHandler
|
||||
|
||||
// Resource is the OpenTelemetry resource associated with all Meters
|
||||
// created by the Controller.
|
||||
Resource resource.Resource
|
||||
}
|
||||
|
||||
// Option is the interface that applies the value to a configuration option.
|
||||
type Option interface {
|
||||
// Apply sets the Option value of a Config.
|
||||
Apply(*Config)
|
||||
}
|
||||
|
||||
// WithErrorHandler sets the ErrorHandler configuration option of a Config.
|
||||
func WithErrorHandler(fn sdk.ErrorHandler) Option {
|
||||
return errorHandlerOption(fn)
|
||||
}
|
||||
|
||||
type errorHandlerOption sdk.ErrorHandler
|
||||
|
||||
func (o errorHandlerOption) Apply(config *Config) {
|
||||
config.ErrorHandler = sdk.ErrorHandler(o)
|
||||
}
|
||||
|
||||
// WithResource sets the Resource configuration option of a Config.
|
||||
func WithResource(r resource.Resource) Option {
|
||||
return resourceOption(r)
|
||||
}
|
||||
|
||||
type resourceOption resource.Resource
|
||||
|
||||
func (o resourceOption) Apply(config *Config) {
|
||||
config.Resource = resource.Resource(o)
|
||||
}
|
48
sdk/metric/controller/push/config_test.go
Normal file
48
sdk/metric/controller/push/config_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
sdk "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
func TestWithErrorHandler(t *testing.T) {
|
||||
errH, reg := func() (sdk.ErrorHandler, *error) {
|
||||
e := fmt.Errorf("default invalid")
|
||||
reg := &e
|
||||
return func(err error) {
|
||||
*reg = err
|
||||
}, reg
|
||||
}()
|
||||
|
||||
c := &Config{}
|
||||
WithErrorHandler(errH).Apply(c)
|
||||
err1 := fmt.Errorf("error 1")
|
||||
c.ErrorHandler(err1)
|
||||
assert.EqualError(t, *reg, err1.Error())
|
||||
|
||||
// Ensure overwriting works.
|
||||
c = &Config{ErrorHandler: sdk.DefaultErrorHandler}
|
||||
WithErrorHandler(errH).Apply(c)
|
||||
err2 := fmt.Errorf("error 2")
|
||||
c.ErrorHandler(err2)
|
||||
assert.EqualError(t, *reg, err2.Error())
|
||||
}
|
||||
|
||||
func TestWithResource(t *testing.T) {
|
||||
r := resource.New(core.Key("A").String("a"))
|
||||
|
||||
c := &Config{}
|
||||
WithResource(*r).Apply(c)
|
||||
assert.Equal(t, *r, c.Resource)
|
||||
|
||||
// Ensure overwriting works.
|
||||
c = &Config{Resource: resource.Resource{}}
|
||||
WithResource(*r).Apply(c)
|
||||
assert.Equal(t, *r, c.Resource)
|
||||
}
|
@ -66,26 +66,31 @@ var _ Clock = realClock{}
|
||||
var _ Ticker = realTicker{}
|
||||
|
||||
// New constructs a Controller, an implementation of metric.Provider,
|
||||
// using the provided batcher, exporter, and collection period to
|
||||
// configure an SDK with periodic collection. The batcher itself is
|
||||
// configured with the aggregation selector policy.
|
||||
// using the provided batcher, exporter, collection period, and SDK
|
||||
// configuration options to configure an SDK with periodic collection.
|
||||
// The batcher itself is configured with the aggregation selector policy.
|
||||
//
|
||||
// If the Exporter implements the export.LabelEncoder interface, the
|
||||
// exporter will be used as the label encoder for the SDK itself,
|
||||
// otherwise the SDK will be configured with the default label
|
||||
// encoder.
|
||||
func New(batcher export.Batcher, exporter export.Exporter, period time.Duration) *Controller {
|
||||
func New(batcher export.Batcher, exporter export.Exporter, period time.Duration, opts ...Option) *Controller {
|
||||
lencoder, _ := exporter.(export.LabelEncoder)
|
||||
|
||||
if lencoder == nil {
|
||||
lencoder = sdk.NewDefaultLabelEncoder()
|
||||
}
|
||||
|
||||
impl := sdk.New(batcher, lencoder)
|
||||
c := &Config{ErrorHandler: sdk.DefaultErrorHandler}
|
||||
for _, opt := range opts {
|
||||
opt.Apply(c)
|
||||
}
|
||||
|
||||
impl := sdk.New(batcher, lencoder, sdk.WithResource(c.Resource), sdk.WithErrorHandler(c.ErrorHandler))
|
||||
return &Controller{
|
||||
sdk: impl,
|
||||
meter: metric.WrapMeterImpl(impl),
|
||||
errorHandler: sdk.DefaultErrorHandler,
|
||||
errorHandler: c.ErrorHandler,
|
||||
batcher: batcher,
|
||||
exporter: exporter,
|
||||
ch: make(chan struct{}),
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
api "go.opentelemetry.io/otel/api/metric"
|
||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -67,6 +68,9 @@ type (
|
||||
|
||||
// errorHandler supports delivering errors to the user.
|
||||
errorHandler ErrorHandler
|
||||
|
||||
// resource represents the entity producing telemetry.
|
||||
resource resource.Resource
|
||||
}
|
||||
|
||||
syncInstrument struct {
|
||||
@ -159,6 +163,7 @@ var (
|
||||
_ api.AsyncImpl = &asyncInstrument{}
|
||||
_ api.SyncImpl = &syncInstrument{}
|
||||
_ api.BoundSyncImpl = &record{}
|
||||
_ api.Resourcer = &SDK{}
|
||||
_ export.LabelStorage = &labels{}
|
||||
|
||||
kvType = reflect.TypeOf(core.KeyValue{})
|
||||
@ -298,14 +303,20 @@ func (s *syncInstrument) RecordOne(ctx context.Context, number core.Number, ls a
|
||||
// batcher will call Collect() when it receives a request to scrape
|
||||
// current metric values. A push-based batcher should configure its
|
||||
// own periodic collection.
|
||||
func New(batcher export.Batcher, labelEncoder export.LabelEncoder) *SDK {
|
||||
func New(batcher export.Batcher, labelEncoder export.LabelEncoder, opts ...Option) *SDK {
|
||||
c := &Config{ErrorHandler: DefaultErrorHandler}
|
||||
for _, opt := range opts {
|
||||
opt.Apply(c)
|
||||
}
|
||||
|
||||
m := &SDK{
|
||||
empty: labels{
|
||||
ordered: [0]core.KeyValue{},
|
||||
},
|
||||
batcher: batcher,
|
||||
labelEncoder: labelEncoder,
|
||||
errorHandler: DefaultErrorHandler,
|
||||
errorHandler: c.ErrorHandler,
|
||||
resource: c.Resource,
|
||||
}
|
||||
m.empty.meter = m
|
||||
m.empty.cachedValue = reflect.ValueOf(m.empty.ordered)
|
||||
@ -559,6 +570,16 @@ func (m *SDK) checkpoint(ctx context.Context, descriptor *metric.Descriptor, rec
|
||||
return 1
|
||||
}
|
||||
|
||||
// Resource returns the Resource this SDK was created with describing the
|
||||
// entity for which it creates instruments for.
|
||||
//
|
||||
// Resource means that the SDK implements the Resourcer interface and
|
||||
// therefore all metric instruments it creates will inherit its
|
||||
// Resource by default unless explicitly overwritten.
|
||||
func (m *SDK) Resource() resource.Resource {
|
||||
return m.resource
|
||||
}
|
||||
|
||||
// RecordBatch enters a batch of metric events.
|
||||
func (m *SDK) RecordBatch(ctx context.Context, ls api.LabelSet, measurements ...api.Measurement) {
|
||||
for _, meas := range measurements {
|
||||
|
@ -17,6 +17,8 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
)
|
||||
|
||||
@ -70,3 +72,8 @@ func (r Resource) Attributes() []core.KeyValue {
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
// Equal returns true if other Resource is the equal to r.
|
||||
func (r Resource) Equal(other Resource) bool {
|
||||
return reflect.DeepEqual(r.labels, other.labels)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user