Skip to content

Commit

Permalink
Document MeterProvider (#4874)
Browse files Browse the repository at this point in the history
Closes gh-4872

Co-authored-by: Tommy Ludwig <[email protected]>
  • Loading branch information
jonatan-ivanov and shakuzen authored Mar 27, 2024
1 parent 68e4cc4 commit c9792c5
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
** xref:concepts/distribution-summaries.adoc[Distribution Summaries]
** xref:concepts/long-task-timers.adoc[Long Task Timers]
** xref:concepts/histogram-quantiles.adoc[Histograms and Percentiles]
** xref:concepts/meter-provider.adoc[Meter Provider]
* xref:implementations.adoc[Implementations]
** xref:implementations/appOptics.adoc[AppOptics]
** xref:implementations/atlas.adoc[Atlas]
Expand Down
49 changes: 49 additions & 0 deletions docs/modules/ROOT/pages/concepts/meter-provider.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[[meter-provider]]
= Meter Provider

It's a common use-case to attach tags dynamically to a `Meter`. Let's say we execute a job and we want to use a `Timer` to instrument it:

[source, java]
----
Timer.Sample sample = Timer.start(registry);
Result result = job.execute();
Timer timer = Timer.builder("job.execution")
.tag("job.name", "job")
.tag("status", result.status())
.register(registry);
sample.stop(timer);
----

This lets us dynamically determine the `status` tag from the end state of the operation we are timing. There are two drawbacks of doing this:

1. Every time the above is executed, a new `Timer.Builder` instance is created. This increases the amount of data that the GC needs to collect.
2. The code above is somewhat boilerplate, it does not let you define the common properties of a Timer and attach what is dynamically changing but everything is always present.

NOTE: In some cases you can use `registry.timer("job.execution", "job.name", "my-job", "status", result.status())` instead of using `Timer.Builder` which can save you some extra objects but this is not always possible.

You can resolve both of these issues by using a `MeterProvider`. It's a convenience interface to create new meters from tags using a common "template".

NOTE: Not every `Meter` can do this, `MeterProvider` can be used with `Counter`, `Timer`, `LongTaskTimer`, and `DistributionSummary`.

Here's what you can do instead of the above:

[source, java]
----
private MeterProvider<Timer> timerProvider = Timer.builder("job.execution")
.tag("job.name", "my-job")
.withRegistry(registry); <1>
// ...
Timer.Sample sample = Timer.start(registry);
Result result = job.execute();
sample.stop(timerProvider.withTags("status", result.status())); <2>
----
<1> Definition of the `MeterProvider` for `Timer` with all the "static" fields necessary. Please note the `withRegistry` method call.
<2> Definition of the dynamic tags. Note that only those tags are defined here that are dynamic and everying else is defined where the `MeterProvider` is created. The `withTags` method returns a `Timer` that is created using the tags defined in `withTags` plus everything else that is defined by the `MeterProvider`.

This and the previous example produce the same output, the only difference is the amount of boilerplate in your code and the amount of builder objects created in the heap.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.core.samples;

import io.micrometer.core.instrument.Meter.MeterProvider;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.samples.utils.SampleConfig;
import reactor.core.publisher.Flux;

import java.time.Duration;

public class MeterProviderSample {

public static void main(String[] args) {
MeterRegistry registry = SampleConfig.myMonitoringSystem();
MeterProvider<Timer> timerProvider = Timer.builder("job.execution")
.tag("job.name", "job")
.withRegistry(registry);

Flux.interval(Duration.ofSeconds(1)).doOnEach(d -> {
Timer.Sample sample = Timer.start(registry);
Result result = new Job().execute();
sample.stop(timerProvider.withTags("status", result.status()));
}).blockLast();
}

static class Job {

Result execute() {
try {
Thread.sleep((long) (Math.random() * 100 + 100));
}
catch (InterruptedException e) {
// ignored
}

return new Result(Math.random() > 0.2 ? "SUCCESS" : "FAILED");
}

}

static class Result {

final String status;

Result(String status) {
this.status = status;
}

String status() {
return status;
}

}

}

0 comments on commit c9792c5

Please sign in to comment.