Skip to content
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

Cache builders in MM runtime package #41857

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -33,32 +35,40 @@ public class MicrometerCountedInterceptor {

private final MeterRegistry meterRegistry;
private final MeterTagsSupport meterTagsSupport;
private final Map<String, Counter.Builder> 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.
*
* <p>
* 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.
*/
@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();
}
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,10 +19,14 @@
public class MicrometerMetricsFactory implements MetricsFactory {
final MeterRegistry globalRegistry;
final MicrometerConfig config;
final Map<String, MetricBuilder> metricsBuilders;
final Map<TimerMultiKey, Timer> timerMap;

public MicrometerMetricsFactory(MicrometerConfig config, MeterRegistry globalRegistry) {
this.globalRegistry = globalRegistry;
this.config = config;
this.metricsBuilders = new HashMap<>();
this.timerMap = new HashMap<>();
}

@Override
Expand All @@ -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 {
Expand Down Expand Up @@ -106,43 +113,44 @@ public <T, R extends Number> void buildGauge(T obj, Function<T, R> 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 <T> Callable<T> buildTimer(Callable<T> f) {
Timer timer = Timer.builder(name)
.description(description)
.tags(tags)
.register(globalRegistry);
Timer timer = getTimer();

return timer.wrap(f);
}

@Override
public <T> Supplier<T> buildTimer(Supplier<T> 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<TimerMultiKey, Timer>() {
public Timer apply(TimerMultiKey key) {
return Timer.builder(key.name)
.description(key.description)
.tags(key.tags)
.register(globalRegistry);
}
});
}
}

static class MicrometerTimeRecorder implements TimeRecorder {
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -33,10 +37,14 @@ public class MicrometerTimedInterceptor {

private final MeterRegistry meterRegistry;
private final MeterTagsSupport meterTagsSupport;
private final Map<String, Timer.Builder> methodsBuilders;
private final Map<String, LongTaskTimer.Sample> longMethodsBuilders;

public MicrometerTimedInterceptor(MeterRegistry meterRegistry, MeterTagsSupport meterTagsSupport) {
this.meterRegistry = meterRegistry;
this.meterTagsSupport = meterTagsSupport;
this.methodsBuilders = new HashMap<>();
this.longMethodsBuilders = new HashMap<>();
}

@AroundInvoke
Expand Down Expand Up @@ -86,7 +94,7 @@ public void accept(Object o, Throwable throwable, Boolean cancelled) {
}

private List<Sample> getSamples(ArcInvocationContext context) {
List<Timed> timed = context.findIterceptorBindings(Timed.class);
Set<Timed> timed = context.getInterceptorBindings(Timed.class);
if (timed.isEmpty()) {
return Collections.emptyList();
}
Expand All @@ -111,12 +119,17 @@ private void stop(List<Sample> 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<String, Timer.Builder>() {
@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) {
Expand All @@ -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<String, LongTaskTimer.Sample>() {
@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);
Expand All @@ -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;
Expand Down
Loading