Skip to content

Commit f7df68b

Browse files
MrAliasrghetia
andauthoredMar 20, 2020
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>
1 parent a01f63b commit f7df68b

File tree

11 files changed

+319
-33
lines changed

11 files changed

+319
-33
lines changed
 

‎api/metric/api.go

+22
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"go.opentelemetry.io/otel/api/core"
2323
"go.opentelemetry.io/otel/api/unit"
24+
"go.opentelemetry.io/otel/sdk/resource"
2425
)
2526

2627
// Provider supports named Meter instances.
@@ -45,6 +46,8 @@ type Config struct {
4546
// Keys are recommended keys determined in the handles
4647
// obtained for the metric.
4748
Keys []core.Key
49+
// Resource describes the entity for which measurements are made.
50+
Resource resource.Resource
4851
}
4952

5053
// Option is an interface for applying metric options.
@@ -141,6 +144,12 @@ func (d Descriptor) NumberKind() core.NumberKind {
141144
return d.numberKind
142145
}
143146

147+
// Resource returns the Resource describing the entity for which the metric
148+
// instrument measures.
149+
func (d Descriptor) Resource() resource.Resource {
150+
return d.config.Resource
151+
}
152+
144153
// Meter is an interface to the metrics portion of the OpenTelemetry SDK.
145154
type Meter interface {
146155
// Labels returns a reference to a set of labels that cannot
@@ -212,3 +221,16 @@ type keysOption []core.Key
212221
func (k keysOption) Apply(config *Config) {
213222
config.Keys = append(config.Keys, k...)
214223
}
224+
225+
// WithResource applies provided Resource.
226+
//
227+
// This will override any existing Resource.
228+
func WithResource(r resource.Resource) Option {
229+
return resourceOption(r)
230+
}
231+
232+
type resourceOption resource.Resource
233+
234+
func (r resourceOption) Apply(config *Config) {
235+
config.Resource = resource.Resource(r)
236+
}

‎api/metric/api_test.go

+43-24
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"go.opentelemetry.io/otel/api/metric"
2626
"go.opentelemetry.io/otel/api/unit"
2727
mockTest "go.opentelemetry.io/otel/internal/metric"
28+
"go.opentelemetry.io/otel/sdk/resource"
2829

2930
"github.com/google/go-cmp/cmp"
3031
"github.com/stretchr/testify/assert"
@@ -35,19 +36,21 @@ var Must = metric.Must
3536

3637
func TestOptions(t *testing.T) {
3738
type testcase struct {
38-
name string
39-
opts []metric.Option
40-
keys []core.Key
41-
desc string
42-
unit unit.Unit
39+
name string
40+
opts []metric.Option
41+
keys []core.Key
42+
desc string
43+
unit unit.Unit
44+
resource resource.Resource
4345
}
4446
testcases := []testcase{
4547
{
46-
name: "no opts",
47-
opts: nil,
48-
keys: nil,
49-
desc: "",
50-
unit: "",
48+
name: "no opts",
49+
opts: nil,
50+
keys: nil,
51+
desc: "",
52+
unit: "",
53+
resource: resource.Resource{},
5154
},
5255
{
5356
name: "keys keys keys",
@@ -61,46 +64,61 @@ func TestOptions(t *testing.T) {
6164
key.New("bar"), key.New("bar2"),
6265
key.New("baz"), key.New("baz2"),
6366
},
64-
desc: "",
65-
unit: "",
67+
desc: "",
68+
unit: "",
69+
resource: resource.Resource{},
6670
},
6771
{
6872
name: "description",
6973
opts: []metric.Option{
7074
metric.WithDescription("stuff"),
7175
},
72-
keys: nil,
73-
desc: "stuff",
74-
unit: "",
76+
keys: nil,
77+
desc: "stuff",
78+
unit: "",
79+
resource: resource.Resource{},
7580
},
7681
{
7782
name: "description override",
7883
opts: []metric.Option{
7984
metric.WithDescription("stuff"),
8085
metric.WithDescription("things"),
8186
},
82-
keys: nil,
83-
desc: "things",
84-
unit: "",
87+
keys: nil,
88+
desc: "things",
89+
unit: "",
90+
resource: resource.Resource{},
8591
},
8692
{
8793
name: "unit",
8894
opts: []metric.Option{
8995
metric.WithUnit("s"),
9096
},
91-
keys: nil,
92-
desc: "",
93-
unit: "s",
97+
keys: nil,
98+
desc: "",
99+
unit: "s",
100+
resource: resource.Resource{},
94101
},
95102
{
96103
name: "unit override",
97104
opts: []metric.Option{
98105
metric.WithUnit("s"),
99106
metric.WithUnit("h"),
100107
},
101-
keys: nil,
102-
desc: "",
103-
unit: "h",
108+
keys: nil,
109+
desc: "",
110+
unit: "h",
111+
resource: resource.Resource{},
112+
},
113+
{
114+
name: "resource override",
115+
opts: []metric.Option{
116+
metric.WithResource(*resource.New(key.New("name").String("test-name"))),
117+
},
118+
keys: nil,
119+
desc: "",
120+
unit: "",
121+
resource: *resource.New(key.New("name").String("test-name")),
104122
},
105123
}
106124
for idx, tt := range testcases {
@@ -109,6 +127,7 @@ func TestOptions(t *testing.T) {
109127
Description: tt.desc,
110128
Unit: tt.unit,
111129
Keys: tt.keys,
130+
Resource: tt.resource,
112131
}); diff != "" {
113132
t.Errorf("Compare options: -got +want %s", diff)
114133
}

‎api/metric/sdkhelpers.go

+26
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919

2020
"go.opentelemetry.io/otel/api/core"
21+
"go.opentelemetry.io/otel/sdk/resource"
2122
)
2223

2324
// MeterImpl is a convenient interface for SDK and test
@@ -133,6 +134,29 @@ func Configure(opts []Option) Config {
133134
return config
134135
}
135136

137+
// Resourcer is implemented by any value that has a Resource method,
138+
// which returns the Resource associated with the value.
139+
// The Resource method is used to set the Resource for Descriptors of new
140+
// metric instruments.
141+
type Resourcer interface {
142+
Resource() resource.Resource
143+
}
144+
145+
// insertResource inserts a WithResource option at the beginning of opts
146+
// using the resource defined by impl if impl implements Resourcer.
147+
//
148+
// If opts contains a WithResource option already, that Option will take
149+
// precedence and overwrite the Resource set from impl.
150+
//
151+
// The returned []Option may uses the same underlying array as opts.
152+
func insertResource(impl MeterImpl, opts []Option) []Option {
153+
if r, ok := impl.(Resourcer); ok {
154+
// default to the impl resource and override if passed in opts.
155+
return append([]Option{WithResource(r.Resource())}, opts...)
156+
}
157+
return opts
158+
}
159+
136160
// WrapMeterImpl constructs a `Meter` implementation from a
137161
// `MeterImpl` implementation.
138162
func WrapMeterImpl(impl MeterImpl) Meter {
@@ -159,6 +183,7 @@ func (m *wrappedMeterImpl) RecordBatch(ctx context.Context, ls LabelSet, ms ...M
159183
}
160184

161185
func (m *wrappedMeterImpl) newSync(name string, metricKind Kind, numberKind core.NumberKind, opts []Option) (SyncImpl, error) {
186+
opts = insertResource(m.impl, opts)
162187
return m.impl.NewSyncInstrument(NewDescriptor(name, metricKind, numberKind, opts...))
163188
}
164189

@@ -219,6 +244,7 @@ func WrapFloat64MeasureInstrument(syncInst SyncImpl, err error) (Float64Measure,
219244
}
220245

221246
func (m *wrappedMeterImpl) newAsync(name string, mkind Kind, nkind core.NumberKind, opts []Option, callback func(func(core.Number, LabelSet))) (AsyncImpl, error) {
247+
opts = insertResource(m.impl, opts)
222248
return m.impl.NewAsyncInstrument(
223249
NewDescriptor(name, mkind, nkind, opts...),
224250
callback)

‎sdk/export/metric/metric.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019, OpenTelemetry Authors
1+
// Copyright 2020, OpenTelemetry Authors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

‎sdk/metric/config.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package metric
2+
3+
import "go.opentelemetry.io/otel/sdk/resource"
4+
5+
// Config contains configuration for an SDK.
6+
type Config struct {
7+
// ErrorHandler is the function called when the SDK encounters an error.
8+
//
9+
// This option can be overridden after instantiation of the SDK
10+
// with the `SetErrorHandler` method.
11+
ErrorHandler ErrorHandler
12+
13+
// Resource is the OpenTelemetry resource associated with all Meters
14+
// created by the SDK.
15+
Resource resource.Resource
16+
}
17+
18+
// Option is the interface that applies the value to a configuration option.
19+
type Option interface {
20+
// Apply sets the Option value of a Config.
21+
Apply(*Config)
22+
}
23+
24+
// WithErrorHandler sets the ErrorHandler configuration option of a Config.
25+
func WithErrorHandler(fn ErrorHandler) Option {
26+
return errorHandlerOption(fn)
27+
}
28+
29+
type errorHandlerOption ErrorHandler
30+
31+
func (o errorHandlerOption) Apply(config *Config) {
32+
config.ErrorHandler = ErrorHandler(o)
33+
}
34+
35+
// WithResource sets the Resource configuration option of a Config.
36+
func WithResource(r resource.Resource) Option {
37+
return resourceOption(r)
38+
}
39+
40+
type resourceOption resource.Resource
41+
42+
func (o resourceOption) Apply(config *Config) {
43+
config.Resource = resource.Resource(o)
44+
}

‎sdk/metric/config_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package metric
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"go.opentelemetry.io/otel/api/core"
10+
"go.opentelemetry.io/otel/sdk/resource"
11+
)
12+
13+
func TestWithErrorHandler(t *testing.T) {
14+
errH, reg := func() (ErrorHandler, *error) {
15+
e := fmt.Errorf("default invalid")
16+
reg := &e
17+
return func(err error) {
18+
*reg = err
19+
}, reg
20+
}()
21+
22+
c := &Config{}
23+
WithErrorHandler(errH).Apply(c)
24+
err1 := fmt.Errorf("error 1")
25+
c.ErrorHandler(err1)
26+
assert.EqualError(t, *reg, err1.Error())
27+
28+
// Ensure overwriting works.
29+
c = &Config{ErrorHandler: DefaultErrorHandler}
30+
WithErrorHandler(errH).Apply(c)
31+
err2 := fmt.Errorf("error 2")
32+
c.ErrorHandler(err2)
33+
assert.EqualError(t, *reg, err2.Error())
34+
}
35+
36+
func TestWithResource(t *testing.T) {
37+
r := resource.New(core.Key("A").String("a"))
38+
39+
c := &Config{}
40+
WithResource(*r).Apply(c)
41+
assert.Equal(t, *r, c.Resource)
42+
43+
// Ensure overwriting works.
44+
c = &Config{Resource: resource.Resource{}}
45+
WithResource(*r).Apply(c)
46+
assert.Equal(t, *r, c.Resource)
47+
}

‎sdk/metric/controller/push/config.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package push
2+
3+
import (
4+
sdk "go.opentelemetry.io/otel/sdk/metric"
5+
"go.opentelemetry.io/otel/sdk/resource"
6+
)
7+
8+
// Config contains configuration for a push Controller.
9+
type Config struct {
10+
// ErrorHandler is the function called when the Controller encounters an error.
11+
//
12+
// This option can be overridden after instantiation of the Controller
13+
// with the `SetErrorHandler` method.
14+
ErrorHandler sdk.ErrorHandler
15+
16+
// Resource is the OpenTelemetry resource associated with all Meters
17+
// created by the Controller.
18+
Resource resource.Resource
19+
}
20+
21+
// Option is the interface that applies the value to a configuration option.
22+
type Option interface {
23+
// Apply sets the Option value of a Config.
24+
Apply(*Config)
25+
}
26+
27+
// WithErrorHandler sets the ErrorHandler configuration option of a Config.
28+
func WithErrorHandler(fn sdk.ErrorHandler) Option {
29+
return errorHandlerOption(fn)
30+
}
31+
32+
type errorHandlerOption sdk.ErrorHandler
33+
34+
func (o errorHandlerOption) Apply(config *Config) {
35+
config.ErrorHandler = sdk.ErrorHandler(o)
36+
}
37+
38+
// WithResource sets the Resource configuration option of a Config.
39+
func WithResource(r resource.Resource) Option {
40+
return resourceOption(r)
41+
}
42+
43+
type resourceOption resource.Resource
44+
45+
func (o resourceOption) Apply(config *Config) {
46+
config.Resource = resource.Resource(o)
47+
}
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package push
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"go.opentelemetry.io/otel/api/core"
10+
sdk "go.opentelemetry.io/otel/sdk/metric"
11+
"go.opentelemetry.io/otel/sdk/resource"
12+
)
13+
14+
func TestWithErrorHandler(t *testing.T) {
15+
errH, reg := func() (sdk.ErrorHandler, *error) {
16+
e := fmt.Errorf("default invalid")
17+
reg := &e
18+
return func(err error) {
19+
*reg = err
20+
}, reg
21+
}()
22+
23+
c := &Config{}
24+
WithErrorHandler(errH).Apply(c)
25+
err1 := fmt.Errorf("error 1")
26+
c.ErrorHandler(err1)
27+
assert.EqualError(t, *reg, err1.Error())
28+
29+
// Ensure overwriting works.
30+
c = &Config{ErrorHandler: sdk.DefaultErrorHandler}
31+
WithErrorHandler(errH).Apply(c)
32+
err2 := fmt.Errorf("error 2")
33+
c.ErrorHandler(err2)
34+
assert.EqualError(t, *reg, err2.Error())
35+
}
36+
37+
func TestWithResource(t *testing.T) {
38+
r := resource.New(core.Key("A").String("a"))
39+
40+
c := &Config{}
41+
WithResource(*r).Apply(c)
42+
assert.Equal(t, *r, c.Resource)
43+
44+
// Ensure overwriting works.
45+
c = &Config{Resource: resource.Resource{}}
46+
WithResource(*r).Apply(c)
47+
assert.Equal(t, *r, c.Resource)
48+
}

‎sdk/metric/controller/push/push.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,31 @@ var _ Clock = realClock{}
6666
var _ Ticker = realTicker{}
6767

6868
// New constructs a Controller, an implementation of metric.Provider,
69-
// using the provided batcher, exporter, and collection period to
70-
// configure an SDK with periodic collection. The batcher itself is
71-
// configured with the aggregation selector policy.
69+
// using the provided batcher, exporter, collection period, and SDK
70+
// configuration options to configure an SDK with periodic collection.
71+
// The batcher itself is configured with the aggregation selector policy.
7272
//
7373
// If the Exporter implements the export.LabelEncoder interface, the
7474
// exporter will be used as the label encoder for the SDK itself,
7575
// otherwise the SDK will be configured with the default label
7676
// encoder.
77-
func New(batcher export.Batcher, exporter export.Exporter, period time.Duration) *Controller {
77+
func New(batcher export.Batcher, exporter export.Exporter, period time.Duration, opts ...Option) *Controller {
7878
lencoder, _ := exporter.(export.LabelEncoder)
7979

8080
if lencoder == nil {
8181
lencoder = sdk.NewDefaultLabelEncoder()
8282
}
8383

84-
impl := sdk.New(batcher, lencoder)
84+
c := &Config{ErrorHandler: sdk.DefaultErrorHandler}
85+
for _, opt := range opts {
86+
opt.Apply(c)
87+
}
88+
89+
impl := sdk.New(batcher, lencoder, sdk.WithResource(c.Resource), sdk.WithErrorHandler(c.ErrorHandler))
8590
return &Controller{
8691
sdk: impl,
8792
meter: metric.WrapMeterImpl(impl),
88-
errorHandler: sdk.DefaultErrorHandler,
93+
errorHandler: c.ErrorHandler,
8994
batcher: batcher,
9095
exporter: exporter,
9196
ch: make(chan struct{}),

‎sdk/metric/sdk.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
api "go.opentelemetry.io/otel/api/metric"
3030
export "go.opentelemetry.io/otel/sdk/export/metric"
3131
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
32+
"go.opentelemetry.io/otel/sdk/resource"
3233
)
3334

3435
type (
@@ -67,6 +68,9 @@ type (
6768

6869
// errorHandler supports delivering errors to the user.
6970
errorHandler ErrorHandler
71+
72+
// resource represents the entity producing telemetry.
73+
resource resource.Resource
7074
}
7175

7276
syncInstrument struct {
@@ -159,6 +163,7 @@ var (
159163
_ api.AsyncImpl = &asyncInstrument{}
160164
_ api.SyncImpl = &syncInstrument{}
161165
_ api.BoundSyncImpl = &record{}
166+
_ api.Resourcer = &SDK{}
162167
_ export.LabelStorage = &labels{}
163168

164169
kvType = reflect.TypeOf(core.KeyValue{})
@@ -298,14 +303,20 @@ func (s *syncInstrument) RecordOne(ctx context.Context, number core.Number, ls a
298303
// batcher will call Collect() when it receives a request to scrape
299304
// current metric values. A push-based batcher should configure its
300305
// own periodic collection.
301-
func New(batcher export.Batcher, labelEncoder export.LabelEncoder) *SDK {
306+
func New(batcher export.Batcher, labelEncoder export.LabelEncoder, opts ...Option) *SDK {
307+
c := &Config{ErrorHandler: DefaultErrorHandler}
308+
for _, opt := range opts {
309+
opt.Apply(c)
310+
}
311+
302312
m := &SDK{
303313
empty: labels{
304314
ordered: [0]core.KeyValue{},
305315
},
306316
batcher: batcher,
307317
labelEncoder: labelEncoder,
308-
errorHandler: DefaultErrorHandler,
318+
errorHandler: c.ErrorHandler,
319+
resource: c.Resource,
309320
}
310321
m.empty.meter = m
311322
m.empty.cachedValue = reflect.ValueOf(m.empty.ordered)
@@ -559,6 +570,16 @@ func (m *SDK) checkpoint(ctx context.Context, descriptor *metric.Descriptor, rec
559570
return 1
560571
}
561572

573+
// Resource returns the Resource this SDK was created with describing the
574+
// entity for which it creates instruments for.
575+
//
576+
// Resource means that the SDK implements the Resourcer interface and
577+
// therefore all metric instruments it creates will inherit its
578+
// Resource by default unless explicitly overwritten.
579+
func (m *SDK) Resource() resource.Resource {
580+
return m.resource
581+
}
582+
562583
// RecordBatch enters a batch of metric events.
563584
func (m *SDK) RecordBatch(ctx context.Context, ls api.LabelSet, measurements ...api.Measurement) {
564585
for _, meas := range measurements {

‎sdk/resource/resource.go

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package resource
1818

1919
import (
20+
"reflect"
21+
2022
"go.opentelemetry.io/otel/api/core"
2123
)
2224

@@ -70,3 +72,8 @@ func (r Resource) Attributes() []core.KeyValue {
7072
}
7173
return attrs
7274
}
75+
76+
// Equal returns true if other Resource is the equal to r.
77+
func (r Resource) Equal(other Resource) bool {
78+
return reflect.DeepEqual(r.labels, other.labels)
79+
}

0 commit comments

Comments
 (0)
Please sign in to comment.