Skip to content

Commit 84322ca

Browse files
authored
Avoid DistributionStatisticsConfig creation when retrieving timers (#6661)
In the case where the Timer/LongTaskTimer builder is not used, the DistributionStatisticsConfig is necessarily one with the default configuration being passed to registerMeterIfNecessary. We can avoid creating and configuring a DistributionStatisticsConfig that will always have the same config by keeping a static default one to use in these cases. In performance critical areas the API to register Timer/LongTaskTimer that doesn't use the Builder should be preferred for this reason. The usage in DefaultMeterObservationHandler has been updated accordingly, which should improve the performance and allocations for all applications using it.
1 parent 9a42d62 commit 84322ca

File tree

7 files changed

+92
-16
lines changed

7 files changed

+92
-16
lines changed

benchmarks/benchmarks-core/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ compileJava {
2828

2929
dependencies {
3030
jmh project(':micrometer-core')
31-
// jmh 'io.micrometer:micrometer-core:1.13.0-M2'
31+
// jmh 'io.micrometer:micrometer-core:1.16.0-M2'
3232
jmh project(':micrometer-registry-prometheus')
33-
// jmh 'io.micrometer:micrometer-registry-prometheus:1.13.0-M2'
33+
// jmh 'io.micrometer:micrometer-registry-prometheus:1.16.0-M2'
3434
jmh project(':micrometer-registry-otlp')
35-
// jmh 'io.micrometer:micrometer-registry-otlp:1.13.0-M2'
35+
// jmh 'io.micrometer:micrometer-registry-otlp:1.16.0-M2'
3636

3737
jmh libs.dropwizardMetricsCore5
3838
jmh libs.prometheusMetrics

benchmarks/benchmarks-core/src/jmh/java/io/micrometer/benchmark/core/MeterRegistrationBenchmark.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
*/
1616
package io.micrometer.benchmark.core;
1717

18-
import io.micrometer.core.instrument.Counter;
19-
import io.micrometer.core.instrument.Meter;
20-
import io.micrometer.core.instrument.MeterRegistry;
18+
import io.micrometer.core.instrument.*;
2119
import io.micrometer.core.instrument.config.MeterFilter;
2220
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2321
import org.openjdk.jmh.annotations.*;
22+
import org.openjdk.jmh.annotations.Measurement;
2423
import org.openjdk.jmh.profile.GCProfiler;
2524
import org.openjdk.jmh.runner.Runner;
2625
import org.openjdk.jmh.runner.RunnerException;
@@ -50,6 +49,8 @@ public static void main(String[] args) throws RunnerException {
5049

5150
Meter.MeterProvider<Counter> counterMeterProvider = Counter.builder("jmh.existing").withRegistry(registry);
5251

52+
Tags tags = Tags.of("key", "value");
53+
5354
@Setup
5455
public void setup() {
5556
registry.config()
@@ -58,6 +59,17 @@ public void setup() {
5859
registry.counter("jmh.stale");
5960
registry.config().meterFilter(MeterFilter.acceptNameStartsWith("jmh"));
6061
registry.counter("jmh.existing", "k1", "v1");
62+
registry.timer("jmh.existing.timer", tags);
63+
}
64+
65+
@Benchmark
66+
public Timer registerExistingTimer() {
67+
return registry.timer("jmh.existing.timer", tags);
68+
}
69+
70+
@Benchmark
71+
public Timer registerExistingTimerBuilder() {
72+
return Timer.builder("jmh.existing.timer").tags(tags).register(registry);
6173
}
6274

6375
@Benchmark

benchmarks/benchmarks-core/src/jmh/java/io/micrometer/benchmark/core/ObservationBenchmark.java

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717

1818
import java.util.concurrent.TimeUnit;
1919

20+
import io.micrometer.common.KeyValue;
21+
import io.micrometer.common.KeyValues;
2022
import io.micrometer.core.instrument.LongTaskTimer;
2123
import io.micrometer.core.instrument.Tags;
2224
import io.micrometer.core.instrument.Timer;
2325
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
2426
import io.micrometer.core.instrument.observation.ObservationOrTimerCompatibleInstrumentation;
2527
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2628
import io.micrometer.observation.Observation;
29+
import io.micrometer.observation.ObservationConvention;
2730
import io.micrometer.observation.ObservationRegistry;
2831
import org.openjdk.jmh.annotations.*;
2932
import org.openjdk.jmh.profile.GCProfiler;
@@ -34,8 +37,8 @@
3437
@Fork(1)
3538
@Threads(4)
3639
@State(Scope.Benchmark)
37-
@BenchmarkMode(Mode.SampleTime)
38-
@OutputTimeUnit(TimeUnit.MICROSECONDS)
40+
@BenchmarkMode(Mode.AverageTime)
41+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
3942
public class ObservationBenchmark {
4043

4144
SimpleMeterRegistry meterRegistry;
@@ -46,6 +49,8 @@ public class ObservationBenchmark {
4649

4750
Timer timer;
4851

52+
ObservationConvention<Observation.Context> convention;
53+
4954
@Setup
5055
public void setup() {
5156
this.meterRegistry = new SimpleMeterRegistry();
@@ -54,6 +59,25 @@ public void setup() {
5459
this.observationRegistry.observationConfig()
5560
.observationHandler(new DefaultMeterObservationHandler(meterRegistry));
5661
this.noopRegistry = ObservationRegistry.create();
62+
this.convention = new ObservationConvention<Observation.Context>() {
63+
64+
@Override
65+
public String getName() {
66+
return "http.server.requests";
67+
}
68+
69+
@Override
70+
public KeyValues getLowCardinalityKeyValues(Observation.Context context) {
71+
return KeyValues.of(KeyValue.of("exception", "none"), KeyValue.of("method", "GET"),
72+
KeyValue.of("outcome", "SUCCESS"), KeyValue.of("status", "200"),
73+
KeyValue.of("url", "/books/{bookId}"));
74+
}
75+
76+
@Override
77+
public boolean supportsContext(Observation.Context context) {
78+
return true;
79+
}
80+
};
5781
}
5882

5983
@TearDown
@@ -106,7 +130,11 @@ public long builtTimerAndLongTaskTimer() {
106130
@Benchmark
107131
public Observation observationWithoutThreadContention() {
108132
Observation observation = Observation.createNotStarted("test.obs", observationRegistry)
109-
.lowCardinalityKeyValue("abc", "123")
133+
.lowCardinalityKeyValue("exception", "none")
134+
.lowCardinalityKeyValue("method", "GET")
135+
.lowCardinalityKeyValue("outcome", "SUCCESS")
136+
.lowCardinalityKeyValue("status", "200")
137+
.lowCardinalityKeyValue("url", "/books/{bookId}")
110138
.start();
111139
observation.stop();
112140

@@ -116,13 +144,34 @@ public Observation observationWithoutThreadContention() {
116144
@Benchmark
117145
public Observation observation() {
118146
Observation observation = Observation.createNotStarted("test.obs", observationRegistry)
119-
.lowCardinalityKeyValue("abc", "123")
147+
.lowCardinalityKeyValue("exception", "none")
148+
.lowCardinalityKeyValue("method", "GET")
149+
.lowCardinalityKeyValue("outcome", "SUCCESS")
150+
.lowCardinalityKeyValue("status", "200")
151+
.lowCardinalityKeyValue("url", "/books/{bookId}")
120152
.start();
121153
observation.stop();
122154

123155
return observation;
124156
}
125157

158+
@Threads(1)
159+
@Benchmark
160+
public Observation observationConventionWithoutThreadContention() {
161+
Observation observation = Observation.start(convention, observationRegistry);
162+
observation.stop();
163+
164+
return observation;
165+
}
166+
167+
@Benchmark
168+
public Observation observationConvention() {
169+
Observation observation = Observation.start(convention, observationRegistry);
170+
observation.stop();
171+
172+
return observation;
173+
}
174+
126175
@Benchmark
127176
public ObservationOrTimerCompatibleInstrumentation<Observation.Context> observationOrTimer() {
128177
ObservationOrTimerCompatibleInstrumentation<Observation.Context> instrumentation = ObservationOrTimerCompatibleInstrumentation

micrometer-core/src/main/java/io/micrometer/core/instrument/AbstractTimerBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@
3232
@SuppressWarnings("unchecked")
3333
public abstract class AbstractTimerBuilder<B extends AbstractTimerBuilder<B>> {
3434

35+
/**
36+
* Default {@link DistributionStatisticConfig} used with {@link Timer} if not
37+
* overridden.
38+
*/
39+
static final DistributionStatisticConfig DEFAULT_DISTRIBUTION_CONFIG = DistributionStatisticConfig.builder()
40+
.minimumExpectedValue((double) Duration.ofMillis(1).toNanos())
41+
.maximumExpectedValue((double) Duration.ofSeconds(30).toNanos())
42+
.build();
43+
3544
protected final String name;
3645

3746
protected Tags tags = Tags.empty();

micrometer-core/src/main/java/io/micrometer/core/instrument/LongTaskTimer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,11 @@ abstract class Sample {
287287
*/
288288
class Builder {
289289

290+
static final DistributionStatisticConfig DEFAULT_DISTRIBUTION_CONFIG = DistributionStatisticConfig.builder()
291+
.minimumExpectedValue((double) Duration.ofMinutes(2).toNanos())
292+
.maximumExpectedValue((double) Duration.ofHours(2).toNanos())
293+
.build();
294+
290295
private final String name;
291296

292297
private Tags tags = Tags.empty();

micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,8 @@ public DistributionSummary summary(String name, String... tags) {
486486
* @return A new or existing timer.
487487
*/
488488
public Timer timer(String name, Iterable<Tag> tags) {
489-
return Timer.builder(name).tags(tags).register(this);
489+
return this.timer(new Meter.Id(name, Tags.of(tags), null, null, Meter.Type.TIMER),
490+
AbstractTimerBuilder.DEFAULT_DISTRIBUTION_CONFIG, pauseDetector);
490491
}
491492

492493
/**
@@ -1057,7 +1058,8 @@ public LongTaskTimer longTaskTimer(String name, String... tags) {
10571058
* @return A new or existing long task timer.
10581059
*/
10591060
public LongTaskTimer longTaskTimer(String name, Iterable<Tag> tags) {
1060-
return LongTaskTimer.builder(name).tags(tags).register(MeterRegistry.this);
1061+
return longTaskTimer(new Meter.Id(name, Tags.of(tags), null, null, Meter.Type.LONG_TASK_TIMER),
1062+
LongTaskTimer.Builder.DEFAULT_DISTRIBUTION_CONFIG);
10611063
}
10621064

10631065
/**

micrometer-core/src/main/java/io/micrometer/core/instrument/observation/DefaultMeterObservationHandler.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,8 @@ public DefaultMeterObservationHandler(MeterRegistry meterRegistry, IgnoredMeters
7171
@Override
7272
public void onStart(Observation.Context context) {
7373
if (shouldCreateLongTaskTimer) {
74-
LongTaskTimer.Sample longTaskSample = LongTaskTimer.builder(context.getName() + ".active")
75-
.tags(createTags(context))
76-
.register(meterRegistry)
74+
LongTaskTimer.Sample longTaskSample = meterRegistry.more()
75+
.longTaskTimer(context.getName() + ".active", createTags(context))
7776
.start();
7877
context.put(LongTaskTimer.Sample.class, longTaskSample);
7978
}
@@ -89,7 +88,7 @@ public void onStop(Observation.Context context) {
8988
List<Tag> tags = createTags(context);
9089
tags.add(Tag.of("error", getErrorValue(context)));
9190
Timer.Sample sample = context.getRequired(Timer.Sample.class);
92-
sample.stop(Timer.builder(context.getName()).tags(tags).register(this.meterRegistry));
91+
sample.stop(this.meterRegistry.timer(context.getName(), tags));
9392

9493
if (shouldCreateLongTaskTimer) {
9594
LongTaskTimer.Sample longTaskSample = context.getRequired(LongTaskTimer.Sample.class);

0 commit comments

Comments
 (0)