-
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
Add observer metric #474
Add observer metric #474
Conversation
I must have some misunderstanding of the observers. During collection period we are going to call the callback of the observer. The callback receives a result object on which the callback can call the observe function many times. Also, observer is like a gauge, so by default we remember the value from the last event. So if we call the observe function several times within a single callback, we basically overwrite the last value all the time. Is this some corner case where calling observe multiple times doesn't make sense? Also, I tried splitting the observe function as I described in the PR description and it looks to me that the value is not connected to the passed label set. We seem to care about label sets during checkpoint, for the batcher. I wonder if it would make sense for the observer result object to have two functions: type Int64ObserverResult interface {
Observe(value ...int64)
Labels(labels ...LabelSet)
} Anyway, I need to read and understand the standard implementation for observers first. This seems to invalidate a lot I wrote above. |
Scratch the above post. Now each observer has a separate recorder for every labelset passed to the observe function and it makes more sense now. |
I'd like to get some initial opinion on the PR before I embark on a dull task of writing the tests for observers. ;) |
2addc87
to
0972daa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good.
api/global/internal/meter.go
Outdated
func (obs int64ObsImpl) SetCallback(callback metric.Int64ObserverCallback) { | ||
if obs.trySetCallbackInDelegate(callback) { | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are two attempts necessary?
Wouldn't this work?
obs.observer.setCallback(callback)
obs.trySetCallbackInDelegate(callback)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would work too, but it is an (unprofiled ;) ) optimization - obs.observer.setCallback(callback)
involves locking the mutex. And I expect the first call to trySetCallbackInDelegate
to usually succeed anyway.
api/metric/api.go
Outdated
@@ -127,10 +135,53 @@ type Meter interface { | |||
// a given name and customized with passed options. | |||
NewFloat64Measure(name string, mos ...MeasureOptionApplier) Float64Measure | |||
|
|||
// NewInt64Observer creates a new integral observer with a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:s/NewInt64Observer/RegisterInt64Observer/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
api/metric/api.go
Outdated
// given name, running a given callback, and customized with | ||
// passed options. Callback can be nil. | ||
RegisterInt64Observer(name string, callback Int64ObserverCallback, oos ...ObserverOptionApplier) Int64Observer | ||
// NewFloat64Observer creates a new floating point observer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't finish looking this over. It's looking good but I've found a few ways we can limit scope. I'll keep reviewing this again tomorrow.
4668a7b
to
0efae30
Compare
Updated. |
TestDirect seems to fail for some reason. I have run it on my computer in 100 iterations loop and all passed. :/ I suppose that we should be checking the core.Number equality in a different way, especially when dealing with floats. |
6069ce8
to
8780935
Compare
Rebased. And fixed the Added some tests for the internal global meter alignment stuff - test 386 was failing on those. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
One thing I wonder about is why we should have a @bogdandrutu It occurs to me that we haven't specified this detail, what do you think? |
I agree. Observer without a Callback is incomplete and most likely it would not require changing it. If we do find a use case later to update the callback then we can always add the SetCallback or UpdateCallback api. |
Co-Authored-By: Mauricio Vásquez <[email protected]>
if a recorder for a labelset is unused for a second collection cycle in a row, drop it
This reverts commit 37b7d05.
Compare concrete numbers, so we can get actual numbers in the error message when they are not equal, not some uint64 representation. This also uses InDelta for comparing floats.
So the tests can be reproducible - iterating a map made the order of measurements random.
This wasn't checked at all. After adding the checks, the test-386 failed.
test-386 was complaining about it
Rebased to master. Dropped the SetCallback function. |
👏 |
* wip: observers * wip: float observers * fix copy pasta * wip: rework observers in sdk * small fix in global meter * wip: aggregators and selectors * wip: monotonicity option for observers * some refactor * wip: docs needs more package docs (especially for api/metric and sdk/metric) * fix ci * Fix copy-pasta in docs Co-Authored-By: Mauricio Vásquez <[email protected]> * recycle unused recorders in observers if a recorder for a labelset is unused for a second collection cycle in a row, drop it * unregister * thread-safe set callback * Fix docs * Revert "wip: aggregators and selectors" This reverts commit 37b7d05. * update selector * tests * Rework number equality Compare concrete numbers, so we can get actual numbers in the error message when they are not equal, not some uint64 representation. This also uses InDelta for comparing floats. * Ensure that Observers are registered in the same order * Run observers in fixed order So the tests can be reproducible - iterating a map made the order of measurements random. * Ensure the proper alignment of the delegates This wasn't checked at all. After adding the checks, the test-386 failed. * Small tweaks to the global meter test * Ensure proper alignment of the callback pointer test-386 was complaining about it * update docs * update a TODO * address review issues * drop SetCallback Co-authored-by: Mauricio Vásquez <[email protected]> Co-authored-by: Rahul Patel <[email protected]>
Here is the initial stab at observers.
This adds integral and floating point observer instruments. The meter got
Register{Int,Float}64Observer(name, callback, options...)
functions. The observers are interfaces like as follows:The callback is
func({Int,Float}64ObserverResult)
and the result type is:Both observer and observer result interfaces are implemented by SDK and mocks, noop and global only implement observer.
On the SDK side, each labelset passed to the result's observe function gets its own recorder/aggregator. So, every observer may have several aggregators if the callback called observe several times, each time with a different labelset. That more-or-less mimics what happens when you bind an instrument several times, each time with a different labelset.
There are also three new aggregators for observers, which are basically compositions of two other aggregators: (gauge, minmaxsumcount), (gauge, ddsketch) and (gauge, array). These are used by the simple selector for observer instruments.
SDK calls the observer callbacks in the collection routine, after checkpointing records. Currently there is no protection against a callback that does
sleep(time.Eternity)
.TODO:
docssome are done, need more package docs (especially api/metric and sdk/metric)testsmonotonicity option for observerstheobserve
implementation in sdk could likely be split into two parts:first part would remain inobserve
and could call Update on recorder every time observer callback callsObserve
on result.second part would be moved to the observer loop inCollect
and could call Process on batcher after observer callback is finishedthe checkpointed variable should rather be incremented once per observer callback, not once per observation in the observer callback, I thinkadd observer aggregators and update aggregator selectors to handle the observer metric kindFixes #423