You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-07-17 01:12:45 +02:00
Batch Observer callback support (#717)
* api/metric changes from jmacd:jmacd/batch_obs_2 * Add an SDK test * Use a single collector method * Two fixes * Comments; embed AsyncRunner * Comments * Comment fix * More comments * Renaming for clarity * Renaming for clarity (fix) * Lint
This commit is contained in:
@ -85,7 +85,7 @@ type asyncImpl struct {
|
||||
|
||||
instrument
|
||||
|
||||
callback func(func(metric.Number, []kv.KeyValue))
|
||||
runner metric.AsyncRunner
|
||||
}
|
||||
|
||||
// SyncImpler is implemented by all of the sync metric
|
||||
@ -245,21 +245,21 @@ func (bound *syncHandle) Unbind() {
|
||||
|
||||
func (m *meterImpl) NewAsyncInstrument(
|
||||
desc metric.Descriptor,
|
||||
callback func(func(metric.Number, []kv.KeyValue)),
|
||||
runner metric.AsyncRunner,
|
||||
) (metric.AsyncImpl, error) {
|
||||
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
if meterPtr := (*metric.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil {
|
||||
return (*meterPtr).NewAsyncInstrument(desc, callback)
|
||||
return (*meterPtr).NewAsyncInstrument(desc, runner)
|
||||
}
|
||||
|
||||
inst := &asyncImpl{
|
||||
instrument: instrument{
|
||||
descriptor: desc,
|
||||
},
|
||||
callback: callback,
|
||||
runner: runner,
|
||||
}
|
||||
m.asyncInsts = append(m.asyncInsts, inst)
|
||||
return inst, nil
|
||||
@ -276,7 +276,7 @@ func (obs *asyncImpl) setDelegate(d metric.MeterImpl) {
|
||||
implPtr := new(metric.AsyncImpl)
|
||||
|
||||
var err error
|
||||
*implPtr, err = d.NewAsyncInstrument(obs.descriptor, obs.callback)
|
||||
*implPtr, err = d.NewAsyncInstrument(obs.descriptor, obs.runner)
|
||||
|
||||
if err != nil {
|
||||
// TODO: There is no standard way to deliver this error to the user.
|
||||
|
@ -198,9 +198,7 @@ func (m Meter) RegisterInt64Observer(name string, callback Int64ObserverCallback
|
||||
}
|
||||
return wrapInt64ObserverInstrument(
|
||||
m.newAsync(name, ObserverKind, Int64NumberKind, opts,
|
||||
func(observe func(Number, []kv.KeyValue)) {
|
||||
callback(Int64ObserverResult{observe})
|
||||
}))
|
||||
newInt64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer creates a new floating point Observer with
|
||||
@ -213,21 +211,16 @@ func (m Meter) RegisterFloat64Observer(name string, callback Float64ObserverCall
|
||||
}
|
||||
return wrapFloat64ObserverInstrument(
|
||||
m.newAsync(name, ObserverKind, Float64NumberKind, opts,
|
||||
func(observe func(Number, []kv.KeyValue)) {
|
||||
callback(Float64ObserverResult{observe})
|
||||
}))
|
||||
newFloat64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// Observe captures a single integer value from the associated
|
||||
// instrument callback, with the given labels.
|
||||
func (io Int64ObserverResult) Observe(value int64, labels ...kv.KeyValue) {
|
||||
io.observe(NewInt64Number(value), labels)
|
||||
}
|
||||
|
||||
// Observe captures a single floating point value from the associated
|
||||
// instrument callback, with the given labels.
|
||||
func (fo Float64ObserverResult) Observe(value float64, labels ...kv.KeyValue) {
|
||||
fo.observe(NewFloat64Number(value), labels)
|
||||
// NewBatchObserver creates a new BatchObserver that supports
|
||||
// making batches of observations for multiple instruments.
|
||||
func (m Meter) NewBatchObserver(callback BatchObserverCallback) BatchObserver {
|
||||
return BatchObserver{
|
||||
meter: m,
|
||||
runner: newBatchAsyncRunner(callback),
|
||||
}
|
||||
}
|
||||
|
||||
// WithDescription applies provided description.
|
||||
|
@ -210,6 +210,51 @@ func checkBatches(t *testing.T, ctx context.Context, labels []kv.KeyValue, mock
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchObserver(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
|
||||
var obs1 metric.Int64Observer
|
||||
var obs2 metric.Float64Observer
|
||||
|
||||
labels := []kv.KeyValue{
|
||||
kv.String("A", "B"),
|
||||
kv.String("C", "D"),
|
||||
}
|
||||
|
||||
cb := Must(meter).NewBatchObserver(
|
||||
func(result metric.BatchObserverResult) {
|
||||
result.Observe(labels,
|
||||
obs1.Observation(42),
|
||||
obs2.Observation(42.0),
|
||||
)
|
||||
},
|
||||
)
|
||||
obs1 = cb.RegisterInt64Observer("test.observer.int")
|
||||
obs2 = cb.RegisterFloat64Observer("test.observer.float")
|
||||
|
||||
mockSDK.RunAsyncInstruments()
|
||||
|
||||
require.Len(t, mockSDK.MeasurementBatches, 1)
|
||||
|
||||
impl1 := obs1.AsyncImpl().Implementation().(*mockTest.Async)
|
||||
impl2 := obs2.AsyncImpl().Implementation().(*mockTest.Async)
|
||||
|
||||
require.NotNil(t, impl1)
|
||||
require.NotNil(t, impl2)
|
||||
|
||||
got := mockSDK.MeasurementBatches[0]
|
||||
require.Equal(t, labels, got.Labels)
|
||||
require.Len(t, got.Measurements, 2)
|
||||
|
||||
m1 := got.Measurements[0]
|
||||
require.Equal(t, impl1, m1.Instrument.Implementation().(*mockTest.Async))
|
||||
require.Equal(t, 0, m1.Number.CompareNumber(metric.Int64NumberKind, fortyTwo(t, metric.Int64NumberKind)))
|
||||
|
||||
m2 := got.Measurements[1]
|
||||
require.Equal(t, impl2, m2.Instrument.Implementation().(*mockTest.Async))
|
||||
require.Equal(t, 0, m2.Number.CompareNumber(metric.Float64NumberKind, fortyTwo(t, metric.Float64NumberKind)))
|
||||
}
|
||||
|
||||
func checkObserverBatch(t *testing.T, labels []kv.KeyValue, mock *mockTest.MeterImpl, kind metric.NumberKind, observer metric.AsyncImpl) {
|
||||
t.Helper()
|
||||
assert.Len(t, mock.MeasurementBatches, 1)
|
||||
@ -256,7 +301,7 @@ func (testWrappedMeter) NewSyncInstrument(_ metric.Descriptor) (metric.SyncImpl,
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (testWrappedMeter) NewAsyncInstrument(_ metric.Descriptor, _ func(func(metric.Number, []kv.KeyValue))) (metric.AsyncImpl, error) {
|
||||
func (testWrappedMeter) NewAsyncInstrument(_ metric.Descriptor, _ metric.AsyncRunner) (metric.AsyncImpl, error) {
|
||||
return nil, errors.New("Test wrap error")
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,12 @@ type MeterMust struct {
|
||||
meter Meter
|
||||
}
|
||||
|
||||
// BatchObserverMust is a wrapper for BatchObserver that panics when
|
||||
// any instrument constructor encounters an error.
|
||||
type BatchObserverMust struct {
|
||||
batch BatchObserver
|
||||
}
|
||||
|
||||
// Must constructs a MeterMust implementation from a Meter, allowing
|
||||
// the application to panic when any instrument constructor yields an
|
||||
// error.
|
||||
@ -86,3 +92,31 @@ func (mm MeterMust) RegisterFloat64Observer(name string, callback Float64Observe
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewBatchObserver returns a wrapper around BatchObserver that panics
|
||||
// when any instrument constructor returns an error.
|
||||
func (mm MeterMust) NewBatchObserver(callback BatchObserverCallback) BatchObserverMust {
|
||||
return BatchObserverMust{
|
||||
batch: mm.meter.NewBatchObserver(callback),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInt64Observer calls `BatchObserver.RegisterInt64Observer` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) RegisterInt64Observer(name string, oos ...Option) Int64Observer {
|
||||
if inst, err := bm.batch.RegisterInt64Observer(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer calls `BatchObserver.RegisterFloat64Observer` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) RegisterFloat64Observer(name string, oos ...Option) Float64Observer {
|
||||
if inst, err := bm.batch.RegisterFloat64Observer(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
@ -14,13 +14,20 @@
|
||||
|
||||
package metric
|
||||
|
||||
import "go.opentelemetry.io/otel/api/kv"
|
||||
|
||||
// Int64ObserverCallback is a type of callback that integral
|
||||
// observers run.
|
||||
type Int64ObserverCallback func(result Int64ObserverResult)
|
||||
type Int64ObserverCallback func(Int64ObserverResult)
|
||||
|
||||
// Float64ObserverCallback is a type of callback that floating point
|
||||
// observers run.
|
||||
type Float64ObserverCallback func(result Float64ObserverResult)
|
||||
type Float64ObserverCallback func(Float64ObserverResult)
|
||||
|
||||
// BatchObserverCallback is a callback argument for use with any
|
||||
// Observer instrument that will be reported as a batch of
|
||||
// observations.
|
||||
type BatchObserverCallback func(BatchObserverResult)
|
||||
|
||||
// Int64Observer is a metric that captures a set of int64 values at a
|
||||
// point in time.
|
||||
@ -33,3 +40,205 @@ type Int64Observer struct {
|
||||
type Float64Observer struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
// BatchObserver represents an Observer callback that can report
|
||||
// observations for multiple instruments.
|
||||
type BatchObserver struct {
|
||||
meter Meter
|
||||
runner AsyncBatchRunner
|
||||
}
|
||||
|
||||
// Int64ObserverResult is passed to an observer callback to capture
|
||||
// observations for one asynchronous integer metric instrument.
|
||||
type Int64ObserverResult struct {
|
||||
instrument AsyncImpl
|
||||
function func([]kv.KeyValue, ...Observation)
|
||||
}
|
||||
|
||||
// Float64ObserverResult is passed to an observer callback to capture
|
||||
// observations for one asynchronous floating point metric instrument.
|
||||
type Float64ObserverResult struct {
|
||||
instrument AsyncImpl
|
||||
function func([]kv.KeyValue, ...Observation)
|
||||
}
|
||||
|
||||
// BatchObserverResult is passed to a batch observer callback to
|
||||
// capture observations for multiple asynchronous instruments.
|
||||
type BatchObserverResult struct {
|
||||
function func([]kv.KeyValue, ...Observation)
|
||||
}
|
||||
|
||||
// AsyncRunner is expected to convert into an AsyncSingleRunner or an
|
||||
// AsyncBatchRunner. SDKs will encounter an error if the AsyncRunner
|
||||
// does not satisfy one of these interfaces.
|
||||
type AsyncRunner interface {
|
||||
// anyRunner() is a non-exported method with no functional use
|
||||
// other than to make this a non-empty interface.
|
||||
anyRunner()
|
||||
}
|
||||
|
||||
// AsyncSingleRunner is an interface implemented by single-observer
|
||||
// callbacks.
|
||||
type AsyncSingleRunner interface {
|
||||
// Run accepts a single instrument and function for capturing
|
||||
// observations of that instrument. Each call to the function
|
||||
// receives one captured observation. (The function accepts
|
||||
// multiple observations so the same implementation can be
|
||||
// used for batch runners.)
|
||||
Run(single AsyncImpl, capture func([]kv.KeyValue, ...Observation))
|
||||
|
||||
AsyncRunner
|
||||
}
|
||||
|
||||
// AsyncBatchRunner is an interface implemented by batch-observer
|
||||
// callbacks.
|
||||
type AsyncBatchRunner interface {
|
||||
// Run accepts a function for capturing observations of
|
||||
// multiple instruments.
|
||||
Run(capture func([]kv.KeyValue, ...Observation))
|
||||
|
||||
AsyncRunner
|
||||
}
|
||||
|
||||
// Observe captures a single integer value from the associated
|
||||
// instrument callback, with the given labels.
|
||||
func (ir Int64ObserverResult) Observe(value int64, labels ...kv.KeyValue) {
|
||||
ir.function(labels, Observation{
|
||||
instrument: ir.instrument,
|
||||
number: NewInt64Number(value),
|
||||
})
|
||||
}
|
||||
|
||||
// Observe captures a single floating point value from the associated
|
||||
// instrument callback, with the given labels.
|
||||
func (fr Float64ObserverResult) Observe(value float64, labels ...kv.KeyValue) {
|
||||
fr.function(labels, Observation{
|
||||
instrument: fr.instrument,
|
||||
number: NewFloat64Number(value),
|
||||
})
|
||||
}
|
||||
|
||||
// Observe captures a multiple observations from the associated batch
|
||||
// instrument callback, with the given labels.
|
||||
func (br BatchObserverResult) Observe(labels []kv.KeyValue, obs ...Observation) {
|
||||
br.function(labels, obs...)
|
||||
}
|
||||
|
||||
// Observation is used for reporting a batch of metric
|
||||
// values. Instances of this type should be created by Observer
|
||||
// instruments (e.g., Int64Observer.Observation()).
|
||||
type Observation struct {
|
||||
// number needs to be aligned for 64-bit atomic operations.
|
||||
number Number
|
||||
instrument AsyncImpl
|
||||
}
|
||||
|
||||
// AsyncImpl returns the instrument that created this observation.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (m Observation) AsyncImpl() AsyncImpl {
|
||||
return m.instrument
|
||||
}
|
||||
|
||||
// Number returns a number recorded in this observation.
|
||||
func (m Observation) Number() Number {
|
||||
return m.number
|
||||
}
|
||||
|
||||
// RegisterInt64Observer creates a new integer Observer instrument
|
||||
// with the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) RegisterInt64Observer(name string, opts ...Option) (Int64Observer, error) {
|
||||
if b.runner == nil {
|
||||
return wrapInt64ObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64ObserverInstrument(
|
||||
b.meter.newAsync(name, ObserverKind, Int64NumberKind, opts, b.runner))
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer creates a new floating point Observer with
|
||||
// the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) RegisterFloat64Observer(name string, opts ...Option) (Float64Observer, error) {
|
||||
if b.runner == nil {
|
||||
return wrapFloat64ObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64ObserverInstrument(
|
||||
b.meter.newAsync(name, ObserverKind, Float64NumberKind, opts,
|
||||
b.runner))
|
||||
}
|
||||
|
||||
// Observation returns an Observation, a BatchObserverCallback
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (i Int64Observer) Observation(v int64) Observation {
|
||||
return Observation{
|
||||
number: NewInt64Number(v),
|
||||
instrument: i.instrument,
|
||||
}
|
||||
}
|
||||
|
||||
// Observation returns an Observation, a BatchObserverCallback
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (f Float64Observer) Observation(v float64) Observation {
|
||||
return Observation{
|
||||
number: NewFloat64Number(v),
|
||||
instrument: f.instrument,
|
||||
}
|
||||
}
|
||||
|
||||
var _ AsyncSingleRunner = (*Int64ObserverCallback)(nil)
|
||||
var _ AsyncSingleRunner = (*Float64ObserverCallback)(nil)
|
||||
var _ AsyncBatchRunner = (*BatchObserverCallback)(nil)
|
||||
|
||||
// newInt64AsyncRunner returns a single-observer callback for integer Observer instruments.
|
||||
func newInt64AsyncRunner(c Int64ObserverCallback) AsyncSingleRunner {
|
||||
return &c
|
||||
}
|
||||
|
||||
// newFloat64AsyncRunner returns a single-observer callback for floating point Observer instruments.
|
||||
func newFloat64AsyncRunner(c Float64ObserverCallback) AsyncSingleRunner {
|
||||
return &c
|
||||
}
|
||||
|
||||
// newBatchAsyncRunner returns a batch-observer callback use with multiple Observer instruments.
|
||||
func newBatchAsyncRunner(c BatchObserverCallback) AsyncBatchRunner {
|
||||
return &c
|
||||
}
|
||||
|
||||
// anyRunner implements AsyncRunner.
|
||||
func (*Int64ObserverCallback) anyRunner() {}
|
||||
|
||||
// anyRunner implements AsyncRunner.
|
||||
func (*Float64ObserverCallback) anyRunner() {}
|
||||
|
||||
// anyRunner implements AsyncRunner.
|
||||
func (*BatchObserverCallback) anyRunner() {}
|
||||
|
||||
// Run implements AsyncSingleRunner.
|
||||
func (i *Int64ObserverCallback) Run(impl AsyncImpl, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*i)(Int64ObserverResult{
|
||||
instrument: impl,
|
||||
function: function,
|
||||
})
|
||||
}
|
||||
|
||||
// Run implements AsyncSingleRunner.
|
||||
func (f *Float64ObserverCallback) Run(impl AsyncImpl, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*f)(Float64ObserverResult{
|
||||
instrument: impl,
|
||||
function: function,
|
||||
})
|
||||
}
|
||||
|
||||
// Run implements AsyncBatchRunner.
|
||||
func (b *BatchObserverCallback) Run(function func([]kv.KeyValue, ...Observation)) {
|
||||
(*b)(BatchObserverResult{
|
||||
function: function,
|
||||
})
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ type uniqueInstrumentMeterImpl struct {
|
||||
state map[key]metric.InstrumentImpl
|
||||
}
|
||||
|
||||
var _ metric.MeterImpl = (*uniqueInstrumentMeterImpl)(nil)
|
||||
|
||||
type key struct {
|
||||
name string
|
||||
libraryName string
|
||||
@ -42,8 +44,6 @@ type key struct {
|
||||
var ErrMetricKindMismatch = fmt.Errorf(
|
||||
"A metric was already registered by this name with another kind or number type")
|
||||
|
||||
var _ metric.MeterImpl = (*uniqueInstrumentMeterImpl)(nil)
|
||||
|
||||
// NewUniqueInstrumentMeterImpl returns a wrapped metric.MeterImpl with
|
||||
// the addition of uniqueness checking.
|
||||
func NewUniqueInstrumentMeterImpl(impl metric.MeterImpl) metric.MeterImpl {
|
||||
@ -125,7 +125,7 @@ func (u *uniqueInstrumentMeterImpl) NewSyncInstrument(descriptor metric.Descript
|
||||
// NewAsyncInstrument implements metric.MeterImpl.
|
||||
func (u *uniqueInstrumentMeterImpl) NewAsyncInstrument(
|
||||
descriptor metric.Descriptor,
|
||||
callback func(func(metric.Number, []kv.KeyValue)),
|
||||
runner metric.AsyncRunner,
|
||||
) (metric.AsyncImpl, error) {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
@ -138,7 +138,7 @@ func (u *uniqueInstrumentMeterImpl) NewAsyncInstrument(
|
||||
return impl.(metric.AsyncImpl), nil
|
||||
}
|
||||
|
||||
asyncInst, err := u.impl.NewAsyncInstrument(descriptor, callback)
|
||||
asyncInst, err := u.impl.NewAsyncInstrument(descriptor, runner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ type MeterImpl interface {
|
||||
// one occur.
|
||||
NewAsyncInstrument(
|
||||
descriptor Descriptor,
|
||||
callback func(func(Number, []kv.KeyValue)),
|
||||
runner AsyncRunner,
|
||||
) (AsyncImpl, error)
|
||||
}
|
||||
|
||||
@ -83,18 +83,6 @@ type AsyncImpl interface {
|
||||
InstrumentImpl
|
||||
}
|
||||
|
||||
// Int64ObserverResult is passed to an observer callback to capture
|
||||
// observations for one asynchronous integer metric instrument.
|
||||
type Int64ObserverResult struct {
|
||||
observe func(Number, []kv.KeyValue)
|
||||
}
|
||||
|
||||
// Float64ObserverResult is passed to an observer callback to capture
|
||||
// observations for one asynchronous floating point metric instrument.
|
||||
type Float64ObserverResult struct {
|
||||
observe func(Number, []kv.KeyValue)
|
||||
}
|
||||
|
||||
// Configure is a helper that applies all the options to a Config.
|
||||
func Configure(opts []Option) Config {
|
||||
var config Config
|
||||
@ -165,13 +153,13 @@ func wrapFloat64MeasureInstrument(syncInst SyncImpl, err error) (Float64Measure,
|
||||
}
|
||||
|
||||
// newAsync constructs one new asynchronous instrument.
|
||||
func (m Meter) newAsync(name string, mkind Kind, nkind NumberKind, opts []Option, callback func(func(Number, []kv.KeyValue))) (AsyncImpl, error) {
|
||||
func (m Meter) newAsync(name string, mkind Kind, nkind NumberKind, opts []Option, runner AsyncRunner) (AsyncImpl, error) {
|
||||
if m.impl == nil {
|
||||
return NoopAsync{}, nil
|
||||
}
|
||||
desc := NewDescriptor(name, mkind, nkind, opts...)
|
||||
desc.config.LibraryName = m.libraryName
|
||||
return m.impl.NewAsyncInstrument(desc, callback)
|
||||
return m.impl.NewAsyncInstrument(desc, runner)
|
||||
}
|
||||
|
||||
// wrapInt64ObserverInstrument returns an `Int64Observer` from a
|
||||
|
Reference in New Issue
Block a user