1
0
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:
Tyler Yahn 2020-03-20 08:58:32 -07:00 committed by GitHub
parent a01f63bec4
commit f7df68b68b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 319 additions and 33 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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
View 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
View 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)
}

View 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)
}

View 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)
}

View File

@ -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{}),

View File

@ -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 {

View File

@ -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)
}