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

Add docs for @MeterTag for @Counted #5640

Merged
Merged
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
32 changes: 32 additions & 0 deletions docs/modules/ROOT/pages/concepts/counters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,38 @@ public class ExampleService {
}
----

=== @MeterTag on Method Parameters

To support using the `@MeterTag` annotation on method parameters, you need to configure the `@CountedAspect` to add the `CountedMeterTagAnnotationHandler`.

[source,java,subs=+attributes]
-----
include::{include-java}/metrics/CountedAspectTest.java[tags=resolvers,indent=0]

include::{include-java}/metrics/CountedAspectTest.java[tags=meter_tag_annotation_handler,indent=0]
-----

Let's assume that we have the following interface.

[source,java,subs=+attributes]
-----
include::{include-java}/metrics/CountedAspectTest.java[tags=interface,indent=0]
-----

When its implementations would be called with different arguments (remember that the implementation needs to be annotated with `@Counted` annotation too), the following counters would be created:

[source,java,subs=+attributes]
-----
// Example for returning <toString()> on the parameter
include::{include-java}/metrics/CountedAspectTest.java[tags=example_value_to_string,indent=0]

// Example for calling the provided <ValueResolver> on the parameter
include::{include-java}/metrics/CountedAspectTest.java[tags=example_value_resolver,indent=0]

// Example for calling the provided <ValueExpressionResolver>
include::{include-java}/metrics/CountedAspectTest.java[tags=example_value_spel,indent=0]
-----

== Function-tracking Counters

Micrometer also provides a more infrequently used counter pattern that tracks a monotonically increasing function (a function that stays the same or increases over time but never decreases). Some monitoring systems, such as Prometheus, push cumulative values for counters to the backend, but others publish the rate at which a counter is incrementing over the push interval. By employing this pattern, you let the Micrometer implementation for your monitoring system choose whether to rate-normalize the counter, and your counter remains portable across different types of monitoring systems.
Expand Down
188 changes: 188 additions & 0 deletions docs/src/test/java/io/micrometer/docs/metrics/CountedAspectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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.docs.metrics;

import io.micrometer.common.annotation.ValueExpressionResolver;
import io.micrometer.common.annotation.ValueResolver;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.aop.CountedAspect;
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
import io.micrometer.core.aop.MeterTag;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

import static org.assertj.core.api.Assertions.assertThat;

class CountedAspectTest {

// tag::resolvers[]
ValueResolver valueResolver = parameter -> "Value from myCustomTagValueResolver [" + parameter + "]";

// Example of a ValueExpressionResolver that uses Spring Expression Language
ValueExpressionResolver valueExpressionResolver = new SpelValueExpressionResolver();

// end::resolvers[]

@ParameterizedTest
@EnumSource(AnnotatedTestClass.class)
void meterTagsWithText(AnnotatedTestClass annotatedClass) {
MeterRegistry registry = new SimpleMeterRegistry();
CountedAspect countedAspect = new CountedAspect(registry);
// tag::meter_tag_annotation_handler[]
// Setting the handler on the aspect
countedAspect.setMeterTagAnnotationHandler(
new CountedMeterTagAnnotationHandler(aClass -> valueResolver, aClass -> valueExpressionResolver));
// end::meter_tag_annotation_handler[]

AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance());
pf.addAspect(countedAspect);

MeterTagClassInterface service = pf.getProxy();

// tag::example_value_to_string[]
service.getAnnotationForArgumentToString(15L);

assertThat(registry.get("method.counted").tag("test", "15").counter().count()).isEqualTo(1);
// end::example_value_to_string[]
}

@ParameterizedTest
@EnumSource(AnnotatedTestClass.class)
void meterTagsWithResolver(AnnotatedTestClass annotatedClass) {
MeterRegistry registry = new SimpleMeterRegistry();
CountedAspect countedAspect = new CountedAspect(registry);
countedAspect.setMeterTagAnnotationHandler(
new CountedMeterTagAnnotationHandler(aClass -> valueResolver, aClass -> valueExpressionResolver));

AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance());
pf.addAspect(countedAspect);

MeterTagClassInterface service = pf.getProxy();

// @formatter:off
// tag::example_value_resolver[]
service.getAnnotationForTagValueResolver("foo");

assertThat(registry.get("method.counted")
.tag("test", "Value from myCustomTagValueResolver [foo]")
.counter()
.count()).isEqualTo(1);
// end::example_value_resolver[]
// @formatter:on
}

@ParameterizedTest
@EnumSource(AnnotatedTestClass.class)
void meterTagsWithExpression(AnnotatedTestClass annotatedClass) {
MeterRegistry registry = new SimpleMeterRegistry();
CountedAspect countedAspect = new CountedAspect(registry);
countedAspect.setMeterTagAnnotationHandler(
new CountedMeterTagAnnotationHandler(aClass -> valueResolver, aClass -> valueExpressionResolver));

AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance());
pf.addAspect(countedAspect);

MeterTagClassInterface service = pf.getProxy();

// tag::example_value_spel[]
service.getAnnotationForTagValueExpression("15L");

assertThat(registry.get("method.counted").tag("test", "hello characters").counter().count()).isEqualTo(1);
// end::example_value_spel[]
}

enum AnnotatedTestClass {

CLASS_WITHOUT_INTERFACE(MeterTagClass.class), CLASS_WITH_INTERFACE(MeterTagClassChild.class);

private final Class<? extends MeterTagClassInterface> clazz;

AnnotatedTestClass(Class<? extends MeterTagClassInterface> clazz) {
this.clazz = clazz;
}

@SuppressWarnings("unchecked")
<T extends MeterTagClassInterface> T newInstance() {
try {
return (T) clazz.getDeclaredConstructor().newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}

}

// tag::interface[]
interface MeterTagClassInterface {

@Counted
void getAnnotationForTagValueResolver(@MeterTag(key = "test", resolver = ValueResolver.class) String test);

@Counted
void getAnnotationForTagValueExpression(
@MeterTag(key = "test", expression = "'hello' + ' characters'") String test);

@Counted
void getAnnotationForArgumentToString(@MeterTag("test") Long param);

}
// end::interface[]

static class MeterTagClass implements MeterTagClassInterface {

@Counted
@Override
public void getAnnotationForTagValueResolver(
@MeterTag(key = "test", resolver = ValueResolver.class) String test) {
}

@Counted
@Override
public void getAnnotationForTagValueExpression(
@MeterTag(key = "test", expression = "'hello' + ' characters'") String test) {
}

@Counted
@Override
public void getAnnotationForArgumentToString(@MeterTag("test") Long param) {
}

}

static class MeterTagClassChild implements MeterTagClassInterface {

@Counted
@Override
public void getAnnotationForTagValueResolver(String test) {
}

@Counted
@Override
public void getAnnotationForTagValueExpression(String test) {
}

@Counted
@Override
public void getAnnotationForArgumentToString(Long param) {
}

}

}
Loading