Skip to content

Commit d1aca7b

Browse files
authored
Associate views with MeterProvider instead of Reader (#3387)
* Split WithView from WithReader * Accept readers and views params in newPipelines * Update MeterProvider pipes init * Fix WithView comment * Fix view example MeterProvider option * Fix With{View,Reader} option in prom exporter test * Test Reader not required to be comparable * Add changes to changelog * Fix changelog option name
1 parent c8a13d6 commit d1aca7b

File tree

9 files changed

+105
-86
lines changed

9 files changed

+105
-86
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,21 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- The `WithView` `Option` is added to the `go.opentelemetry.io/otel/sdk/metric` package.
14+
This option is used to configure the view(s) a `MeterProvider` will use for all `Reader`s that are registered with it. (#3387)
15+
16+
### Changed
17+
18+
- The `"go.opentelemetry.io/otel/sdk/metric".WithReader` option no longer accepts views to associate with the `Reader`.
19+
Instead, views are now registered directly with the `MeterProvider` via the new `WithView` option.
20+
The views registered with the `MeterProvider` apply to all `Reader`s. (#3387)
21+
1122
### Fixed
1223

1324
- The `go.opentelemetry.io/otel/exporters/prometheus` exporter fixes duplicated `_total` suffixes. (#3369)
25+
- Remove comparable requirement for `Reader`s. (#3387)
1426
- Cumulative metrics from the OpenCensus bridge (`go.opentelemetry.io/otel/bridge/opencensus`) are defined as monotonic sums, instead of non-monotonic. (#3389)
1527
- Asynchronous counters (`Counter` and `UpDownCounter`) from the metric SDK now produce delta sums when configured with delta temporality. (#3398)
1628
- Exported `Status` codes in the `go.opentelemetry.io/otel/exporters/zipkin` exporter are now exported as all upper case values. (#3340)

example/view/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ func main() {
6666
log.Fatal(err)
6767
}
6868

69-
provider := metric.NewMeterProvider(metric.WithReader(exporter, customBucketsView, defaultView))
69+
provider := metric.NewMeterProvider(
70+
metric.WithReader(exporter),
71+
metric.WithView(customBucketsView, defaultView),
72+
)
7073
meter := provider.Meter(meterName)
7174

7275
// Start the prometheus HTTP server and pass the exporter Collector to it

exporters/prometheus/exporter_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ func TestPrometheusExporter(t *testing.T) {
260260

261261
provider := metric.NewMeterProvider(
262262
metric.WithResource(res),
263-
metric.WithReader(exporter, customBucketsView, defaultView),
263+
metric.WithReader(exporter),
264+
metric.WithView(customBucketsView, defaultView),
264265
)
265266
meter := provider.Meter("testmeter")
266267

sdk/metric/config.go

+22-12
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import (
2626
// config contains configuration options for a MeterProvider.
2727
type config struct {
2828
res *resource.Resource
29-
readers map[Reader][]view.View
29+
readers []Reader
30+
views []view.View
3031
}
3132

3233
// readerSignals returns a force-flush and shutdown function for a
@@ -35,7 +36,7 @@ type config struct {
3536
// single functions.
3637
func (c config) readerSignals() (forceFlush, shutdown func(context.Context) error) {
3738
var fFuncs, sFuncs []func(context.Context) error
38-
for r := range c.readers {
39+
for _, r := range c.readers {
3940
sFuncs = append(sFuncs, r.Shutdown)
4041
fFuncs = append(fFuncs, r.ForceFlush)
4142
}
@@ -112,21 +113,30 @@ func WithResource(res *resource.Resource) Option {
112113
})
113114
}
114115

115-
// WithReader associates a Reader with a MeterProvider. Any passed view config
116-
// will be used to associate a view with the Reader. If no views are passed
117-
// the default view will be use for the Reader.
118-
//
119-
// Passing this option multiple times for the same Reader will overwrite. The
120-
// last option passed will be the one used for that Reader.
116+
// WithReader associates Reader r with a MeterProvider.
121117
//
122118
// By default, if this option is not used, the MeterProvider will perform no
123119
// operations; no data will be exported without a Reader.
124-
func WithReader(r Reader, views ...view.View) Option {
120+
func WithReader(r Reader) Option {
125121
return optionFunc(func(cfg config) config {
126-
if cfg.readers == nil {
127-
cfg.readers = make(map[Reader][]view.View)
122+
if r == nil {
123+
return cfg
128124
}
129-
cfg.readers[r] = views
125+
cfg.readers = append(cfg.readers, r)
126+
return cfg
127+
})
128+
}
129+
130+
// WithView associates views a MeterProvider.
131+
//
132+
// Views are appended to existing ones in a MeterProvider if this option is
133+
// used multiple times.
134+
//
135+
// By default, if this option is not used, the MeterProvider will use the
136+
// default view.
137+
func WithView(views ...view.View) Option {
138+
return optionFunc(func(cfg config) config {
139+
cfg.views = append(cfg.views, views...)
130140
return cfg
131141
})
132142
}

sdk/metric/config_test.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,21 @@ func TestWithResource(t *testing.T) {
127127
func TestWithReader(t *testing.T) {
128128
r := &reader{}
129129
c := newConfig([]Option{WithReader(r)})
130-
assert.Contains(t, c.readers, r)
130+
require.Len(t, c.readers, 1)
131+
assert.Same(t, r, c.readers[0])
132+
}
133+
134+
func TestWithView(t *testing.T) {
135+
var views []view.View
136+
137+
v, err := view.New(view.MatchInstrumentKind(view.AsyncCounter), view.WithRename("a"))
138+
require.NoError(t, err)
139+
views = append(views, v)
140+
141+
v, err = view.New(view.MatchInstrumentKind(view.SyncCounter), view.WithRename("b"))
142+
require.NoError(t, err)
143+
views = append(views, v)
144+
145+
c := newConfig([]Option{WithView(views...)})
146+
assert.Equal(t, views, c.views)
131147
}

sdk/metric/pipeline.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -416,13 +416,13 @@ func isAggregatorCompatible(kind view.InstrumentKind, agg aggregation.Aggregatio
416416
// measurement.
417417
type pipelines []*pipeline
418418

419-
func newPipelines(res *resource.Resource, readers map[Reader][]view.View) pipelines {
419+
func newPipelines(res *resource.Resource, readers []Reader, views []view.View) pipelines {
420420
pipes := make([]*pipeline, 0, len(readers))
421-
for r, v := range readers {
421+
for _, r := range readers {
422422
p := &pipeline{
423423
resource: res,
424424
reader: r,
425-
views: v,
425+
views: views,
426426
}
427427
r.register(p)
428428
pipes = append(pipes, p)

sdk/metric/pipeline_registry_test.go

+32-67
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ func TestPipelineRegistryCreateAggregators(t *testing.T) {
257257

258258
testCases := []struct {
259259
name string
260-
views map[Reader][]view.View
260+
readers []Reader
261+
views []view.View
261262
inst view.Instrument
262263
wantCount int
263264
}{
@@ -266,72 +267,46 @@ func TestPipelineRegistryCreateAggregators(t *testing.T) {
266267
inst: view.Instrument{Name: "foo"},
267268
},
268269
{
269-
name: "1 reader 1 view gets 1 aggregator",
270-
inst: view.Instrument{Name: "foo"},
271-
views: map[Reader][]view.View{
272-
testRdr: {
273-
{},
274-
},
275-
},
270+
name: "1 reader 1 view gets 1 aggregator",
271+
inst: view.Instrument{Name: "foo"},
272+
readers: []Reader{testRdr},
273+
views: []view.View{{}},
276274
wantCount: 1,
277275
},
278276
{
279-
name: "1 reader 2 views gets 2 aggregator",
280-
inst: view.Instrument{Name: "foo"},
281-
views: map[Reader][]view.View{
282-
testRdr: {
283-
{},
284-
renameView,
285-
},
286-
},
277+
name: "1 reader 2 views gets 2 aggregator",
278+
inst: view.Instrument{Name: "foo"},
279+
readers: []Reader{testRdr},
280+
views: []view.View{{}, renameView},
287281
wantCount: 2,
288282
},
289283
{
290-
name: "2 readers 1 view each gets 2 aggregators",
291-
inst: view.Instrument{Name: "foo"},
292-
views: map[Reader][]view.View{
293-
testRdr: {
294-
{},
295-
},
296-
testRdrHistogram: {
297-
{},
298-
},
299-
},
284+
name: "2 readers 1 view each gets 2 aggregators",
285+
inst: view.Instrument{Name: "foo"},
286+
readers: []Reader{testRdr, testRdrHistogram},
287+
views: []view.View{{}},
300288
wantCount: 2,
301289
},
302290
{
303-
name: "2 reader 2 views each gets 4 aggregators",
304-
inst: view.Instrument{Name: "foo"},
305-
views: map[Reader][]view.View{
306-
testRdr: {
307-
{},
308-
renameView,
309-
},
310-
testRdrHistogram: {
311-
{},
312-
renameView,
313-
},
314-
},
291+
name: "2 reader 2 views each gets 4 aggregators",
292+
inst: view.Instrument{Name: "foo"},
293+
readers: []Reader{testRdr, testRdrHistogram},
294+
views: []view.View{{}, renameView},
315295
wantCount: 4,
316296
},
317297
{
318-
name: "An instrument is duplicated in two views share the same aggregator",
319-
inst: view.Instrument{Name: "foo"},
320-
views: map[Reader][]view.View{
321-
testRdr: {
322-
{},
323-
{},
324-
},
325-
},
298+
name: "An instrument is duplicated in two views share the same aggregator",
299+
inst: view.Instrument{Name: "foo"},
300+
readers: []Reader{testRdr},
301+
views: []view.View{{}, {}},
326302
wantCount: 1,
327303
},
328304
}
329305

330306
for _, tt := range testCases {
331307
t.Run(tt.name, func(t *testing.T) {
332-
p := newPipelines(resource.Empty(), tt.views)
308+
p := newPipelines(resource.Empty(), tt.readers, tt.views)
333309
testPipelineRegistryResolveIntAggregators(t, p, tt.wantCount)
334-
p = newPipelines(resource.Empty(), tt.views)
335310
testPipelineRegistryResolveFloatAggregators(t, p, tt.wantCount)
336311
})
337312
}
@@ -362,11 +337,10 @@ func testPipelineRegistryResolveFloatAggregators(t *testing.T, p pipelines, want
362337
func TestPipelineRegistryResource(t *testing.T) {
363338
v, err := view.New(view.MatchInstrumentName("bar"), view.WithRename("foo"))
364339
require.NoError(t, err)
365-
views := map[Reader][]view.View{
366-
NewManualReader(): {{}, v},
367-
}
340+
readers := []Reader{NewManualReader()}
341+
views := []view.View{{}, v}
368342
res := resource.NewSchemaless(attribute.String("key", "val"))
369-
pipes := newPipelines(res, views)
343+
pipes := newPipelines(res, readers, views)
370344
for _, p := range pipes {
371345
assert.True(t, res.Equal(p.resource), "resource not set")
372346
}
@@ -375,12 +349,9 @@ func TestPipelineRegistryResource(t *testing.T) {
375349
func TestPipelineRegistryCreateAggregatorsIncompatibleInstrument(t *testing.T) {
376350
testRdrHistogram := NewManualReader(WithAggregationSelector(func(ik view.InstrumentKind) aggregation.Aggregation { return aggregation.ExplicitBucketHistogram{} }))
377351

378-
views := map[Reader][]view.View{
379-
testRdrHistogram: {
380-
{},
381-
},
382-
}
383-
p := newPipelines(resource.Empty(), views)
352+
readers := []Reader{testRdrHistogram}
353+
views := []view.View{{}}
354+
p := newPipelines(resource.Empty(), readers, views)
384355
inst := view.Instrument{Name: "foo", Kind: view.AsyncGauge}
385356

386357
vc := cache[string, instrumentID]{}
@@ -389,8 +360,6 @@ func TestPipelineRegistryCreateAggregatorsIncompatibleInstrument(t *testing.T) {
389360
assert.Error(t, err)
390361
assert.Len(t, intAggs, 0)
391362

392-
p = newPipelines(resource.Empty(), views)
393-
394363
rf := newResolver(p, newInstrumentCache[float64](nil, &vc))
395364
floatAggs, err := rf.Aggregators(inst, unit.Dimensionless)
396365
assert.Error(t, err)
@@ -421,17 +390,13 @@ func TestResolveAggregatorsDuplicateErrors(t *testing.T) {
421390
view.MatchInstrumentName("bar"),
422391
view.WithRename("foo"),
423392
)
424-
views := map[Reader][]view.View{
425-
NewManualReader(): {
426-
{},
427-
renameView,
428-
},
429-
}
393+
readers := []Reader{NewManualReader()}
394+
views := []view.View{{}, renameView}
430395

431396
fooInst := view.Instrument{Name: "foo", Kind: view.SyncCounter}
432397
barInst := view.Instrument{Name: "bar", Kind: view.SyncCounter}
433398

434-
p := newPipelines(resource.Empty(), views)
399+
p := newPipelines(resource.Empty(), readers, views)
435400

436401
vc := cache[string, instrumentID]{}
437402
ri := newResolver(p, newInstrumentCache[int64](nil, &vc))

sdk/metric/provider.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func NewMeterProvider(options ...Option) *MeterProvider {
4545
conf := newConfig(options)
4646
flush, sdown := conf.readerSignals()
4747
return &MeterProvider{
48-
pipes: newPipelines(conf.res, conf.readers),
48+
pipes: newPipelines(conf.res, conf.readers, conf.views),
4949
forceFlush: flush,
5050
shutdown: sdown,
5151
}

sdk/metric/reader_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,15 @@ func TestDefaultTemporalitySelector(t *testing.T) {
236236
assert.Equal(t, metricdata.CumulativeTemporality, DefaultTemporalitySelector(ik))
237237
}
238238
}
239+
240+
type notComparable [0]func() // nolint:unused // non-comparable type itself is used.
241+
242+
type noCompareReader struct {
243+
notComparable // nolint:unused // non-comparable type itself is used.
244+
Reader
245+
}
246+
247+
func TestReadersNotRequiredToBeComparable(t *testing.T) {
248+
r := noCompareReader{Reader: NewManualReader()}
249+
assert.NotPanics(t, func() { _ = NewMeterProvider(WithReader(r)) })
250+
}

0 commit comments

Comments
 (0)