Add option for time-based FunctionCounter#7329
Add option for time-based FunctionCounter#7329shakuzen wants to merge 7 commits intomicrometer-metrics:mainfrom
Conversation
This adds `baseTimeUnit` to `FunctionCounter.Builder` and a corresponding `timeFunctionCounter` to `MeterRegistry.More` to allow function counters to be scaled to the registry's base time unit, similar to `TimeGauge`. This will be useful for metrics like `jvm.gc.cpu.time` which are counts of time. Made-with: Cursor
Made-with: Cursor
| * @return The counter builder with added time unit. | ||
| * @since 1.17.0 | ||
| */ | ||
| public Builder<T> timeUnit(@Nullable TimeUnit timeUnit) { |
There was a problem hiding this comment.
Perhaps it's more clear to name the method functionTimeUnit to distinguish from the time unit that will end up on the Meter (Id), which is actually the registry's base time unit?
There was a problem hiding this comment.
I was thinking about this, I remember people getting confused with the TimeUnit parameter of FunctionTimer/TimeGauge.
There was a problem hiding this comment.
Do you mean to say it would be clear to name it functionTimeUnit or should we do more/something else to make it more clear?
There was a problem hiding this comment.
Oh sorry, I meant to say that using functionTimeUnit would be clear.
| .register(registry); | ||
| ---- | ||
|
|
||
| If the function tracks a time value, you can use `timeUnit` on the builder to create a `FunctionCounter` that scales its value to the registry's base time unit. This is analogous to how `TimeGauge` works, and provides a way to register monotonically increasing time values (like `jvm.gc.cpu.time` or `process.cpu.time`) in a way that automatically scales to the preferred time unit of the metrics backend. |
There was a problem hiding this comment.
I'm out of time right now, but I think this documentation can be improved. Either we should link to our documentation on the mentioned metrics (if we have any) or instead reference the JMX method from which they're derived, as that is more universally understandable.
| * @return The counter builder with added time unit. | ||
| * @since 1.17.0 | ||
| */ | ||
| public Builder<T> timeUnit(@Nullable TimeUnit timeUnit) { |
There was a problem hiding this comment.
I was thinking about this, I remember people getting confused with the TimeUnit parameter of FunctionTimer/TimeGauge.
Co-authored-by: Jonatan Ivanov <jonatan.ivanov@gmail.com> Signed-off-by: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com>
| * @return The counter builder with added time unit. | ||
| * @since 1.17.0 | ||
| */ | ||
| public Builder<T> timeUnit(@Nullable TimeUnit timeUnit) { |
There was a problem hiding this comment.
Another idea I had was adding a variant of the builder method that takes a function with return type Duration, and then we don't need to take a TimeUnit or baseUnit at all (we can return a Builder that doesn't have those methods). I mostly like this because it removes the issue now where timeUnit and baseUnit are mutually exclusive options, which is something users need to understand about the API. That said, I don't know if it is worth adding now unless we have a clear idea where it would be used. I don't think many (any?) backing functions for typical FunctionCounters currently return a Duration. Converting number return types to Duration is straightforward, but it requires wrapping a method instead of using it directly, as is most common for backing functions.
There was a problem hiding this comment.
I really like this idea. I'm also thinking if we should call the time-based property out in the method name:
static <T> TimeBasedBuilder<T> timeBasedBuilder(String name, T obj, Function<T, Duration> f) { ... }
static <T> TimeBasedBuilder<T> timeBasedBuilder(String name, T obj, ToDoubleFunction<T> f, TimeUnit sourceUnit) { ... }Other than providing an option with Duration, I think we should still provide an option with a ToDoubleFunction + TimeUnit since one tricky part with Duration is that it only accepts long values so if for some reason someone only have double, that conversion must be made (right now we can avoid this conversion by using our own TimeUtils.convert that works with double).
For the straightforward conversions to Duration (long -> Duration), we could provide a helper, like:
static <T> Function<T, Duration> toDuration(Function<T, Long> f, TemporalUnit sourceUnit) { ... }So users can do this:
FunctionCounter.timeBasedBuilder("test", Demo.class, toDuration(Demo::getElapsedTimeInNanos, ChronoUnit.NANOS));But now that I just wrote this down, that helper seems unnecessarry if we provide the extra option above (ToDoubleFunction<T> f, TimeUnit sourceUnit):
FunctionCounter.timeBasedBuilder("test", Demo.class, Demo::getElapsedTimeInNanos, TimeUnit.NANOSECONDS);There was a problem hiding this comment.
As discussed offline, if we want to go this route, I think the changes need to wait for 1.18.0-M1 so we have more time to give this the thought it deserves and leave room for feedback. RC1 is too late to introduce that much new public API.
timeUnittoFunctionCounter.BuilderfunctionTimeCountertoMeterRegistry.Moreto create aFunctionCounterthat scales its value to the registry's base time unitTimeGaugeworks, and provides a way to register monotonically increasing time values (likejvm.gc.cpu.timeorprocess.cpu.time) in a way that automatically scales to the preferred time unit of the metrics backend.See comments on #7297 for background motivating this change.
This intentionally only adds a new public method to FunctionCounter's Builder to reduce the surface area of this change to the minimum to achieve the feature.
An alternative would be introducing a dedicated type such as
FunctionTimeCounterwhich would perhaps make the feature more discoverable but obviously has more public API to support when the difference to aFunctionCounteris tiny.