-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redesign of Asynchronous Instrument Registration API via the Meter #3519
Comments
So in this approach, I see two things that are potential problems.
This additionally doesn't prevent users from using an instrument from a different meter. |
The API specifically states this as a valid callback design:
It is also how other implementations, e.g. python, are designed. I don't follow how this isn't the intent of the API. Can you elaborate?
If the SDK is the one doing the observation, why can it not verify the instrument belongs to the appropriate meter in the process? |
I'm looking into an alternate proposal that uses the |
Alternate proposal to use the https://go.dev/play/p/uF-Utm0S5h6 type Meter struct {
name string
registrations []registration
}
func NewMeter(name string) *Meter { return &Meter{name: name} }
func (m *Meter) Instrument() *Instrument {
return &Instrument{
meter: m.name,
callers: make([]uintptr, 8),
}
}
func (m *Meter) Register(f Callback, instruments ...*Instrument) error {
for _, i := range instruments {
if i.meter != m.name {
return fmt.Errorf("instrument from another meter: %s", i.meter)
}
}
m.registrations = append(m.registrations, registration{
Instrument: instruments,
Callback: f,
})
return nil
}
func (m *Meter) Collect() error {
for _, reg := range m.registrations {
if err := reg.collect(); err != nil {
return err
}
}
return nil
}
type Callback func() error
type registration struct {
Instrument []*Instrument
Callback Callback
}
func (r registration) collect() error {
pc, _, _, ok := runtime.Caller(1)
if !ok {
return errors.New("failed to get program counter")
}
for _, i := range r.Instrument {
i.caller = pc
}
err := r.Callback()
for _, i := range r.Instrument {
i.caller = 0
}
return err
}
func contains(pc uintptr, frames *runtime.Frames) bool {
for {
frame, more := frames.Next()
if pc == frame.PC {
return true
}
if !more {
break
}
}
return false
}
type Instrument struct {
meter string
caller uintptr // TODO: locking and multiple callers.
callers []uintptr
}
func (i *Instrument) Observe() error {
if i.caller == 0 {
return errors.New("Observe must be called from a registered callback")
}
var stackIdx int
for {
n := runtime.Callers(1+stackIdx, i.callers)
if contains(i.caller, runtime.CallersFrames(i.callers)) {
// Called from a reservation.
fmt.Println("Instrument observed")
return nil
}
if n != len(i.callers) {
break
}
stackIdx += n
}
return errors.New("Observe must be called from the callback it is registered with")
} |
There seems to be a very high overhead cost to this approach. I think we can expect the users to use the context in the callback in the Observe calls. Could we do something simpler like use a random number when the meter is created and pass that in the context? |
Can you elaborate on this? What were your findings? |
From #3373
This seems to motivate us resolving the redesign here to ensure we do comply with this. |
From #3380
We do not follow this recommendation from the specification. |
Currently the metric API provides the following registration API for asynchronous instruments via a meter:
opentelemetry-go/metric/meter.go
Line 58 in 4146bd1
Also, the OpenTelemetry specification states ...
This API allows for callbacks to observe values for instruments they are not registered for, and allows for the callback to be associated with instruments from different Meter instances.
The SDK tries to prevent this by embedding a unique token in the passed context.
opentelemetry-go/sdk/metric/pipeline.go
Line 118 in 4146bd1
That producer key is not unique to a
Meter
meaning it does not prevent instruments from different meters from being updated. Furthermore, it means a users has to pass that context to theObserve
methods they invoke. This latter point is not explicitly stated and is a potential frustration point for users that use a different context here.Proposal
Similar to the callback design proposed in #3507, having the callback return a set of observations for the instruments it is registered with will ensure only those instruments are updated and that they are valid.
The text was updated successfully, but these errors were encountered: