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

@MetricOptions process based on condition #897

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
@Retention(RUNTIME)
@Target({METHOD})
public @interface MetricOptions {

String MEMBER_TAGGERS = "taggers";
String MEMBER_FILTER_TAGGERS = "filterTaggers";
String MEMBER_CONDITION = "condition";

/**
* @return array of {@link io.micronaut.configuration.metrics.aggregator.AbstractMethodTagger} to apply to metrics for method.
* Only utilized for filtering if {@link #filterTaggers()} is true
Expand All @@ -46,4 +51,14 @@
* @return whether to filter taggers using {@link #taggers()} array
*/
boolean filterTaggers() default false;

/**
* Evaluated expression that can be used to indicate whether the metric should be processed.
* Will be evaluated each time the method is called, and if the condition evaluates to false the metric will not be published.
* Evaluated using {@link io.micronaut.configuration.metrics.util.MetricOptionsUtil}
*
* @see <a href="https://docs.micronaut.io/latest/guide/#evaluatedExpressions">Evaluated Expressions</a>.
* @return The condition
*/
String condition() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpAttributes;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpResponseProvider;
Expand All @@ -31,8 +30,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import static java.util.concurrent.TimeUnit.NANOSECONDS;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.micronaut.configuration.metrics.aggregator.AbstractMethodTagger;
import io.micronaut.configuration.metrics.annotation.MetricOptions;
import io.micronaut.configuration.metrics.annotation.RequiresMetrics;
import io.micronaut.configuration.metrics.util.MetricOptionsUtil;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.async.publisher.Publishers;
Expand Down Expand Up @@ -95,7 +96,9 @@ public CountedInterceptor(MeterRegistry meterRegistry, ConversionService convers
public Object intercept(MethodInvocationContext<Object, Object> context) {
final AnnotationMetadata metadata = context.getAnnotationMetadata();
final String metricName = metadata.stringValue(Counted.class).orElse(DEFAULT_METRIC_NAME);
if (StringUtils.isNotEmpty(metricName)) {
final boolean conditionMet = MetricOptionsUtil.evaluateCondition(context);

if (StringUtils.isNotEmpty(metricName) && conditionMet) {
InterceptedMethod interceptedMethod = InterceptedMethod.of(context, conversionService);
try {
InterceptedMethod.ResultType resultType = interceptedMethod.resultType();
Expand Down Expand Up @@ -151,8 +154,8 @@ public Object intercept(MethodInvocationContext<Object, Object> context) {
}

private void doCount(AnnotationMetadata metadata, String metricName, @Nullable Throwable e, MethodInvocationContext<Object, Object> context) {
List<Class<? extends AbstractMethodTagger>> taggers = Arrays.asList(metadata.classValues(MetricOptions.class, "taggers"));
boolean filter = metadata.booleanValue(MetricOptions.class, "filterTaggers").orElse(false);
List<Class<? extends AbstractMethodTagger>> taggers = Arrays.asList(metadata.classValues(MetricOptions.class, MetricOptions.MEMBER_TAGGERS));
boolean filter = metadata.booleanValue(MetricOptions.class, MetricOptions.MEMBER_FILTER_TAGGERS).orElse(false);
Counter.builder(metricName)
.tags(
methodTaggers.isEmpty() ? Collections.emptyList() :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.micronaut.configuration.metrics.aggregator.AbstractMethodTagger;
import io.micronaut.configuration.metrics.annotation.MetricOptions;
import io.micronaut.configuration.metrics.annotation.RequiresMetrics;
import io.micronaut.configuration.metrics.util.MetricOptionsUtil;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.TypeHint;
Expand Down Expand Up @@ -123,10 +124,12 @@ protected TimedInterceptor(MeterRegistry meterRegistry, ConversionService conver
public Object intercept(MethodInvocationContext<Object, Object> context) {
final AnnotationMetadata metadata = context.getAnnotationMetadata();
final AnnotationValue<TimedSet> timedSet = metadata.getAnnotation(TimedSet.class);
if (timedSet != null) {
final boolean conditionMet = MetricOptionsUtil.evaluateCondition(context);

if (timedSet != null && conditionMet) {
final List<AnnotationValue<Timed>> timedAnnotations = timedSet.getAnnotations(VALUE_MEMBER, Timed.class);
if (!timedAnnotations.isEmpty()) {

if (!timedAnnotations.isEmpty()) {
String exceptionClass = "none";
List<Timer.Sample> syncInvokeSamples = null;
InterceptedMethod interceptedMethod = InterceptedMethod.of(context, conversionService);
Expand Down Expand Up @@ -216,8 +219,8 @@ private void stopTimed(String metricName, Timer.Sample sample,
final String description = metadata.stringValue("description").orElse(null);
final String[] tags = metadata.stringValues("extraTags");
final AnnotationMetadata annotationMetadata = context.getAnnotationMetadata();
final List<Class<? extends AbstractMethodTagger>> taggers = Arrays.asList(annotationMetadata.classValues(MetricOptions.class, "taggers"));
final boolean filter = annotationMetadata.booleanValue(MetricOptions.class, "filterTaggers").orElse(false);
final List<Class<? extends AbstractMethodTagger>> taggers = Arrays.asList(annotationMetadata.classValues(MetricOptions.class, MetricOptions.MEMBER_TAGGERS));
final boolean filter = annotationMetadata.booleanValue(MetricOptions.class, MetricOptions.MEMBER_FILTER_TAGGERS).orElse(false);
final double[] percentiles = metadata.doubleValues("percentiles");
final boolean histogram = metadata.isTrue("histogram");
final Timer timer = Timer.builder(metricName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2017-2024 original authors
*
* 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.micronaut.configuration.metrics.util;

import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.configuration.metrics.annotation.MetricOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility class for {@link MetricOptions}. Sharing means of evaluating condition
*
* @since 5.10.0
* @author Haiden Rothwell
*/
public class MetricOptionsUtil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be final and probably doesn't need to be public

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when not public it is not callable from the interceptors due to differing packages


private static final Logger LOG = LoggerFactory.getLogger(MetricOptionsUtil.class);

public static boolean evaluateCondition(MethodInvocationContext<?, ?> context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add javadoc and make not public

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar situation as the whole class being not public: if public is removed it is unable to be called where it currently is

if (!context.isPresent(MetricOptions.class, MetricOptions.MEMBER_CONDITION)) {
return true;
}
boolean expressionResult = context.booleanValue(MetricOptions.class, MetricOptions.MEMBER_CONDITION).orElse(false);
if (!expressionResult && LOG.isDebugEnabled()) {
LOG.debug("MetricOptions condition evaluated to false for invocation: {}", context);
}
return expressionResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class CountedAnnotationSpec extends Specification {

when:
Integer result = cc.max(4, 10)
registry.get("counted.test.max.blocking").tags("ordered", "1", "parameters", "a b").timer()
registry.get("counted.test.max.blocking").tags("ordered", "1", "parameters", "a b").counter()

then:
thrown(MeterNotFoundException)
Expand All @@ -155,4 +155,39 @@ class CountedAnnotationSpec extends Specification {
cleanup:
ctx.close()
}

void "metric is generated when condition evaluates to true"(){
given:
ApplicationContext ctx = ApplicationContext.run(["test.properties.enabled": true])
CountedTarget cc = ctx.getBean(CountedTarget)
MeterRegistry registry = ctx.getBean(MeterRegistry)

when:
Integer result = cc.maxWithCondition(4, 10)
def counter = registry.get("counted.test.maxWithCondition.blocking").tags("ordered", "2", "parameters", "a b").counter()

then:
result == 10
counter.count() == 1

cleanup:
ctx.close()
}

void "metric is not generated when condition evaluates to false"(){
given:
ApplicationContext ctx = ApplicationContext.run(["test.properties.enabled": false])
CountedTarget cc = ctx.getBean(CountedTarget)
MeterRegistry registry = ctx.getBean(MeterRegistry)

when:
Integer result = cc.maxWithCondition(4, 10)
registry.get("counted.test.maxWithCondition.blocking").tags("ordered", "2", "parameters", "a b").counter()

then:
thrown(MeterNotFoundException)

cleanup:
ctx.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,40 @@ class TimeAnnotationSpec extends Specification {
cleanup:
ctx.close()
}

void "metric is generated when condition evaluates to true"(){
given:
ApplicationContext ctx = ApplicationContext.run(["test.properties.enabled": true])
TimedTarget tt = ctx.getBean(TimedTarget)
MeterRegistry registry = ctx.getBean(MeterRegistry)

when:
Integer result = tt.maxWithCondition(4, 10)
def timer = registry.get("timed.test.maxWithCondition.blocking").tags("ordered", "2", "parameters", "a b").timer()

then:
result == 10
timer.count() == 1
timer.totalTime(MILLISECONDS) > 0

cleanup:
ctx.close()
}

void "metric is not generated when condition evaluates to false"(){
given:
ApplicationContext ctx = ApplicationContext.run(["test.properties.enabled": false])
TimedTarget tt = ctx.getBean(TimedTarget)
MeterRegistry registry = ctx.getBean(MeterRegistry)

when:
Integer result = tt.maxWithCondition(4, 10)
registry.get("timed.test.maxWithCondition.blocking").tags("ordered", "2", "parameters", "a b").timer()

then:
thrown(MeterNotFoundException)

cleanup:
ctx.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Integer maxWithOptions(int a, int b) {
return Math.max(a, b);
}

@Counted("counted.test.maxWithCondition.blocking")
@MetricOptions(condition = "#{ env['test.properties.enabled'] }")
Integer maxWithCondition(int a, int b) { return Math.max(a,b); }

@Counted("counted.test.max.blocking")
Integer error(int a, int b) {
throw new NumberFormatException("cannot");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Integer maxWithOptions(int a, int b) {
return Math.max(a, b);
}

@Timed("timed.test.maxWithCondition.blocking")
@MetricOptions(condition = "#{ env['test.properties.enabled'] }")
Integer maxWithCondition(int a, int b) { return Math.max(a,b); }

@Timed("timed.test.repeated1")
@Timed("timed.test.repeated2")
Integer repeated(int a, int b) {
Expand Down
Loading