diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java index 4b2230d53cf0b..ff19689d91f10 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerCountedInterceptor.java @@ -1,6 +1,8 @@ package io.quarkus.micrometer.runtime; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.BiConsumer; @@ -33,24 +35,32 @@ public class MicrometerCountedInterceptor { private final MeterRegistry meterRegistry; private final MeterTagsSupport meterTagsSupport; + private final Map countedBuilderMap; public MicrometerCountedInterceptor(MeterRegistry meterRegistry, MeterTagsSupport meterTagsSupport) { this.meterRegistry = meterRegistry; this.meterTagsSupport = meterTagsSupport; + this.countedBuilderMap = new HashMap<>(); } /** - * Intercept methods annotated with the {@link Counted} annotation and expose a few counters about - * their execution status. By default, record both failed and successful attempts. If the + * Intercept methods annotated with the {@link Counted} annotation and expose a + * few counters about + * their execution status. By default, record both failed and successful + * attempts. If the * {@link Counted#recordFailuresOnly()} is set to {@code true}, then record only - * failed attempts. In case of a failure, tags the counter with the simple name of the thrown + * failed attempts. In case of a failure, tags the counter with the simple name + * of the thrown * exception. * *

- * When the annotated method returns a {@link CompletionStage} or any of its subclasses, - * the counters will be incremented only when the {@link CompletionStage} is completed. + * When the annotated method returns a {@link CompletionStage} or any of its + * subclasses, + * the counters will be incremented only when the {@link CompletionStage} is + * completed. * If completed exceptionally a failure is recorded, otherwise if - * {@link Counted#recordFailuresOnly()} is set to {@code false}, a success is recorded. + * {@link Counted#recordFailuresOnly()} is set to {@code false}, a success is + * recorded. * * @return Whatever the intercepted method returns. * @throws Throwable When the intercepted method throws one. @@ -58,7 +68,7 @@ public MicrometerCountedInterceptor(MeterRegistry meterRegistry, MeterTagsSuppor @AroundInvoke @SuppressWarnings("unchecked") Object countedMethod(ArcInvocationContext context) throws Exception { - MicrometerCounted counted = context.findIterceptorBinding(MicrometerCounted.class); + MicrometerCounted counted = context.getInterceptorBinding(MicrometerCounted.class); if (counted == null) { return context.proceed(); } @@ -112,7 +122,7 @@ private void recordCompletionResult(MicrometerCounted counted, Tags commonTags, } private void record(MicrometerCounted counted, Tags commonTags, Throwable throwable) { - Counter.Builder builder = Counter.builder(counted.value()) + Counter.Builder builder = countedBuilderMap.computeIfAbsent(counted.value(), Counter::builder) .tags(commonTags) .tags(counted.extraTags()) .tag("exception", MicrometerRecorder.getExceptionTag(throwable)) diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerMetricsFactory.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerMetricsFactory.java index e237dfc60abcf..9963af27ac2dd 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerMetricsFactory.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerMetricsFactory.java @@ -1,5 +1,8 @@ package io.quarkus.micrometer.runtime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -16,10 +19,14 @@ public class MicrometerMetricsFactory implements MetricsFactory { final MeterRegistry globalRegistry; final MicrometerConfig config; + final Map metricsBuilders; + final Map timerMap; public MicrometerMetricsFactory(MicrometerConfig config, MeterRegistry globalRegistry) { this.globalRegistry = globalRegistry; this.config = config; + this.metricsBuilders = new HashMap<>(); + this.timerMap = new HashMap<>(); } @Override @@ -35,7 +42,7 @@ public boolean metricsSystemSupported(String name) { */ @Override public MetricBuilder builder(String name, MetricsFactory.Type type) { - return new MicrometerMetricsBuilder(name); + return metricsBuilders.computeIfAbsent(name, MicrometerMetricsBuilder::new); } class MicrometerMetricsBuilder implements MetricBuilder { @@ -106,43 +113,44 @@ public void buildGauge(T obj, Function gaugeFunction @Override public TimeRecorder buildTimer() { - Timer timer = Timer.builder(name) - .description(description) - .tags(tags) - .register(globalRegistry); + Timer timer = getTimer(); return new MicrometerTimeRecorder(timer); } @Override public Runnable buildTimer(Runnable f) { - Timer timer = Timer.builder(name) - .description(description) - .tags(tags) - .register(globalRegistry); + Timer timer = getTimer(); return timer.wrap(f); } @Override public Callable buildTimer(Callable f) { - Timer timer = Timer.builder(name) - .description(description) - .tags(tags) - .register(globalRegistry); + Timer timer = getTimer(); return timer.wrap(f); } @Override public Supplier buildTimer(Supplier f) { - Timer timer = Timer.builder(name) - .description(description) - .tags(tags) - .register(globalRegistry); + Timer timer = getTimer(); return timer.wrap(f); } + + private Timer getTimer() { + TimerMultiKey key = new TimerMultiKey(name, description, tags); + + return timerMap.computeIfAbsent(key, new Function() { + public Timer apply(TimerMultiKey key) { + return Timer.builder(key.name) + .description(key.description) + .tags(key.tags) + .register(globalRegistry); + } + }); + } } static class MicrometerTimeRecorder implements TimeRecorder { @@ -157,4 +165,44 @@ public void update(long amount, TimeUnit unit) { timer.record(amount, unit); } } + + private class TimerMultiKey { + private final String name; + private final String description; + private final Tags tags; + + TimerMultiKey(String name, String description, Tags tags) { + this.name = name; + this.description = description; + this.tags = tags; + } + + @Override + public int hashCode() { + int result = 31 * name.hashCode(); + + if (description != null && !description.isEmpty()) { + result += 31 * description.hashCode(); + } + + if (tags != null) { + result += 31 * tags.hashCode(); + } + + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TimerMultiKey)) { + return false; + } + + TimerMultiKey key = (TimerMultiKey) obj; + + return Objects.equals(this.name, key.name) && + Objects.equals(this.description, key.description) && + Objects.equals(this.tags, key.tags); + } + } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java index 76415a669883e..7ee550d3c6978 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerTimedInterceptor.java @@ -2,8 +2,12 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import jakarta.annotation.Priority; import jakarta.interceptor.AroundInvoke; @@ -33,10 +37,14 @@ public class MicrometerTimedInterceptor { private final MeterRegistry meterRegistry; private final MeterTagsSupport meterTagsSupport; + private final Map methodsBuilders; + private final Map longMethodsBuilders; public MicrometerTimedInterceptor(MeterRegistry meterRegistry, MeterTagsSupport meterTagsSupport) { this.meterRegistry = meterRegistry; this.meterTagsSupport = meterTagsSupport; + this.methodsBuilders = new HashMap<>(); + this.longMethodsBuilders = new HashMap<>(); } @AroundInvoke @@ -86,7 +94,7 @@ public void accept(Object o, Throwable throwable, Boolean cancelled) { } private List getSamples(ArcInvocationContext context) { - List timed = context.findIterceptorBindings(Timed.class); + Set timed = context.getInterceptorBindings(Timed.class); if (timed.isEmpty()) { return Collections.emptyList(); } @@ -111,12 +119,17 @@ private void stop(List samples, String throwableClassName) { private void record(Timed timed, Timer.Sample sample, String exceptionClass, Tags timerTags) { final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value(); try { - Timer.Builder builder = Timer.builder(metricName) - .description(timed.description().isEmpty() ? null : timed.description()) - .tags(timerTags) - .tag("exception", exceptionClass) - .publishPercentileHistogram(timed.histogram()) - .publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles()); + Timer.Builder builder = methodsBuilders.computeIfAbsent(metricName, new Function() { + @Override + public Timer.Builder apply(String t) { + return Timer.builder(metricName) + .description(timed.description().isEmpty() ? null : timed.description()) + .tags(timerTags) + .tag("exception", exceptionClass) + .publishPercentileHistogram(timed.histogram()) + .publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles()); + } + }); sample.stop(builder.register(meterRegistry)); } catch (Exception e) { @@ -130,13 +143,18 @@ LongTaskTimer.Sample startLongTaskTimer(Timed timed, Tags commonTags, String met try { // This will throw if the annotation is incorrect. // Errors are checked for at build time, but ... - return LongTaskTimer.builder(metricName) - .description(timed.description().isEmpty() ? null : timed.description()) - .tags(commonTags) - .tags(timed.extraTags()) - .publishPercentileHistogram(timed.histogram()) - .register(meterRegistry) - .start(); + return longMethodsBuilders.computeIfAbsent(metricName, new Function() { + @Override + public LongTaskTimer.Sample apply(String t) { + return LongTaskTimer.builder(metricName) + .description(timed.description().isEmpty() ? null : timed.description()) + .tags(commonTags) + .tags(timed.extraTags()) + .publishPercentileHistogram(timed.histogram()) + .register(meterRegistry) + .start(); + } + }); } catch (Exception e) { // ignoring on purpose: possible meter registration error should not interrupt main code flow. log.warnf(e, "Unable to create long task timer named %s", metricName); @@ -153,10 +171,6 @@ private void stopLongTaskTimer(String metricName, LongTaskTimer.Sample sample) { } } - private Tags getCommonTags(String className, String methodName) { - return Tags.of("class", className, "method", methodName); - } - abstract static class Sample { protected final Timed timed;