Skip to content

Commit f7dd6b9

Browse files
add metric annotation instrumentation
1 parent 8b4e6d2 commit f7dd6b9

File tree

11 files changed

+835
-25
lines changed

11 files changed

+835
-25
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ out/
4848

4949
# Others #
5050
##########
51+
instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/annotations/
5152
/logs/*
5253
/bin
5354
/out

docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-annotations.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ Comparing source compatibility of against
2525
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
2626
+++ NEW INTERFACE: java.lang.annotation.Annotation
2727
+++ NEW SUPERCLASS: java.lang.Object
28-
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String[] attributes()
28+
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String[] additionalAttributes()
2929
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String description()
3030
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String returnValueAttribute()
31-
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.concurrent.TimeUnit unit()
31+
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String unit()
3232
+++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String value()
3333
+++ NEW ANNOTATION: java.lang.annotation.Target
3434
+++ NEW ELEMENT: value=java.lang.annotation.ElementType.METHOD,java.lang.annotation.ElementType.CONSTRUCTOR (+)

instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/Counted.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
* <p>The name should follow the instrument naming rule: <a
4242
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-naming-rule">https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-naming-rule</a>
4343
*
44-
* <p>The default name is {@code method.invocations.total}.
44+
* <p>The default name is {@code method.invocation.count}.
4545
*/
46-
String value() default "method.invocations.total";
46+
String value() default "method.invocation.count";
4747

4848
/**
4949
* Description of the instrument.
@@ -59,7 +59,7 @@
5959
* <p>Unit strings should follow the instrument unit rules: <a
6060
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-unit">https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-unit</a>
6161
*/
62-
String unit() default "1";
62+
String unit() default "{invocation}";
6363

6464
/**
6565
* List of key-value pairs to supply additional attributes.

instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/MetricAttribute.java

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
* <p>If not specified and the code is compiled using the `{@code -parameters}` argument to
3232
* `javac`, the parameter name will be used instead. If the parameter name is not available, e.g.,
3333
* because the code was not compiled with that flag, the attribute will be ignored.
34+
*
35+
* <p>Warning: be careful to fill it because it might cause an explosion of the cardinality on
36+
* your metric
3437
*/
3538
String value() default "";
3639
}

instrumentation-annotations/src/main/java/io/opentelemetry/instrumentation/annotations/Timed.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import java.lang.annotation.Retention;
1010
import java.lang.annotation.RetentionPolicy;
1111
import java.lang.annotation.Target;
12-
import java.util.concurrent.TimeUnit;
1312

1413
/**
1514
* This annotation creates a {@link io.opentelemetry.api.metrics.LongHistogram Histogram} instrument
@@ -44,7 +43,7 @@
4443
*
4544
* <p>The default name is {@code method.invocations.duration}.
4645
*/
47-
String value() default "method.invocations.duration";
46+
String value() default "method.invocation.duration";
4847

4948
/**
5049
* Description for the instrument.
@@ -59,7 +58,7 @@
5958
*
6059
* <p>Default is millis seconds.
6160
*/
62-
TimeUnit unit() default TimeUnit.MILLISECONDS;
61+
String unit() default "ms";
6362

6463
/**
6564
* List of key-value pairs to supply additional attributes.
@@ -74,7 +73,7 @@
7473
* })
7574
* </pre>
7675
*/
77-
String[] attributes() default {};
76+
String[] additionalAttributes() default {};
7877

7978
/**
8079
* Attribute name for the return value.
@@ -83,6 +82,9 @@
8382
* will be called on the return value to convert it to a String.
8483
*
8584
* <p>By default, the instrument will not have an attribute with the return value.
85+
*
86+
* <p>Warning: be careful to fill it because it might cause an explosion of the cardinality on
87+
* your metric
8688
*/
8789
String returnValueAttribute() default "";
8890
}

instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java

+29-16
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
import static java.util.logging.Level.FINE;
99

10-
import application.io.opentelemetry.instrumentation.annotations.WithSpan;
1110
import application.io.opentelemetry.instrumentation.annotations.Counted;
1211
import application.io.opentelemetry.instrumentation.annotations.MetricAttribute;
1312
import application.io.opentelemetry.instrumentation.annotations.Timed;
13+
import application.io.opentelemetry.instrumentation.annotations.WithSpan;
1414
import com.google.common.base.Stopwatch;
1515
import io.opentelemetry.api.GlobalOpenTelemetry;
1616
import io.opentelemetry.api.common.Attributes;
@@ -66,9 +66,9 @@ public static void recordHistogramWithAttributes(
6666
Timed timedAnnotation = methodRequest.method().getAnnotation(Timed.class);
6767
AttributesBuilder attributesBuilder = Attributes.builder();
6868
extractMetricAttributes(methodRequest, attributesBuilder);
69-
extractAdditionAttributes(timedAnnotation, attributesBuilder);
70-
getHistogram(timedAnnotation)
71-
.record(stopwatch.stop().elapsed().toMillis(), attributesBuilder.build());
69+
extractAdditionAttributes(timedAnnotation.additionalAttributes(), attributesBuilder);
70+
double duration = ((double) stopwatch.stop().elapsed().toMillis()) / 1000;
71+
getHistogram(timedAnnotation).record(duration, attributesBuilder.build());
7272
}
7373

7474
private static void extractMetricAttributes(
@@ -90,26 +90,39 @@ private static void extractMetricAttributes(
9090
}
9191
}
9292

93+
public static void recordHistogram(Method method, Stopwatch stopwatch) {
94+
Timed timedAnnotation = method.getAnnotation(Timed.class);
95+
AttributesBuilder attributesBuilder = Attributes.builder();
96+
extractAdditionAttributes(timedAnnotation.additionalAttributes(), attributesBuilder);
97+
double duration = ((double) stopwatch.stop().elapsed().toMillis()) / 1000;
98+
getHistogram(timedAnnotation).record(duration, attributesBuilder.build());
99+
}
100+
93101
private static void extractAdditionAttributes(
94-
Timed timedAnnotation, AttributesBuilder attributesBuilder) {
95-
int length = timedAnnotation.attributes().length;
102+
String[] attributes, AttributesBuilder attributesBuilder) {
103+
int length = attributes.length;
96104
for (int i = 0; i < length / 2; i++) {
97-
attributesBuilder.put(
98-
timedAnnotation.attributes()[i],
99-
i + 1 > length ? "" : timedAnnotation.attributes()[i + 1]);
105+
attributesBuilder.put(attributes[i], i + 1 > length ? "" : attributes[i + 1]);
100106
}
101107
}
102108

103-
public static void recordHistogram(Method method, Stopwatch stopwatch) {
104-
Timed timedAnnotation = method.getAnnotation(Timed.class);
109+
public static void recordCountWithAttributes(MethodRequest methodRequest) {
110+
Counted countedAnnotation = methodRequest.method().getAnnotation(Counted.class);
105111
AttributesBuilder attributesBuilder = Attributes.builder();
106-
extractAdditionAttributes(timedAnnotation, attributesBuilder);
107-
getHistogram(timedAnnotation)
108-
.record(stopwatch.stop().elapsed().toMillis(), attributesBuilder.build());
112+
extractMetricAttributes(methodRequest, attributesBuilder);
113+
extractAdditionAttributes(countedAnnotation.additionalAttributes(), attributesBuilder);
114+
getCounter(methodRequest.method()).add(1, attributesBuilder.build());
109115
}
110116

111-
private static LongCounter getCounter(MethodRequest methodRequest) {
112-
Counted countedAnnotation = methodRequest.method().getAnnotation(Counted.class);
117+
public static void recordCount(Method method) {
118+
Counted countedAnnotation = method.getAnnotation(Counted.class);
119+
AttributesBuilder attributesBuilder = Attributes.builder();
120+
extractAdditionAttributes(countedAnnotation.additionalAttributes(), attributesBuilder);
121+
getCounter(method).add(1, attributesBuilder.build());
122+
}
123+
124+
private static LongCounter getCounter(Method method) {
125+
Counted countedAnnotation = method.getAnnotation(Counted.class);
113126
if (!COUNTERS.containsKey(countedAnnotation.value())) {
114127
synchronized (countedAnnotation.value()) {
115128
if (!COUNTERS.containsKey(countedAnnotation.value())) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.instrumentationannotations;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.KotlinCoroutineUtil.isKotlinSuspendMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.hasParameters;
11+
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
12+
import static net.bytebuddy.matcher.ElementMatchers.named;
13+
import static net.bytebuddy.matcher.ElementMatchers.not;
14+
import static net.bytebuddy.matcher.ElementMatchers.whereAny;
15+
16+
import com.google.common.base.Stopwatch;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
18+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
19+
import java.lang.reflect.Method;
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.description.annotation.AnnotationSource;
22+
import net.bytebuddy.description.method.MethodDescription;
23+
import net.bytebuddy.description.type.TypeDescription;
24+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
25+
import net.bytebuddy.matcher.ElementMatcher;
26+
27+
public class CountedInstrumentation implements TypeInstrumentation {
28+
29+
private final ElementMatcher.Junction<AnnotationSource> annotatedMethodMatcher;
30+
private final ElementMatcher.Junction<MethodDescription> annotatedParametersMatcher;
31+
// this matcher matches all methods that should be excluded from transformation
32+
private final ElementMatcher.Junction<MethodDescription> excludedMethodsMatcher;
33+
34+
CountedInstrumentation() {
35+
annotatedMethodMatcher =
36+
isAnnotatedWith(named("application.io.opentelemetry.instrumentation.annotations.Counted"));
37+
annotatedParametersMatcher =
38+
hasParameters(
39+
whereAny(
40+
isAnnotatedWith(
41+
named(
42+
"application.io.opentelemetry.instrumentation.annotations.MetricAttribute"))));
43+
// exclude all kotlin suspend methods, these are handle in kotlinx-coroutines instrumentation
44+
excludedMethodsMatcher =
45+
AnnotationExcludedMethods.configureExcludedMethods().or(isKotlinSuspendMethod());
46+
}
47+
48+
@Override
49+
public ElementMatcher<TypeDescription> typeMatcher() {
50+
return declaresMethod(annotatedMethodMatcher);
51+
}
52+
53+
@Override
54+
public void transform(TypeTransformer transformer) {
55+
ElementMatcher.Junction<MethodDescription> countedMethods =
56+
annotatedMethodMatcher.and(not(excludedMethodsMatcher));
57+
58+
ElementMatcher.Junction<MethodDescription> timedMethodsWithParameters =
59+
countedMethods.and(annotatedParametersMatcher);
60+
61+
ElementMatcher.Junction<MethodDescription> timedMethodsWithoutParameters =
62+
countedMethods.and(not(annotatedParametersMatcher));
63+
64+
transformer.applyAdviceToMethod(
65+
timedMethodsWithoutParameters, CountedInstrumentation.class.getName() + "$CountedAdvice");
66+
67+
// Only apply advice for tracing parameters as attributes if any of the parameters are annotated
68+
// with @MetricsAttribute to avoid unnecessarily copying the arguments into an array.
69+
transformer.applyAdviceToMethod(
70+
timedMethodsWithParameters,
71+
CountedInstrumentation.class.getName() + "$CountedAttributesAdvice");
72+
}
73+
74+
@SuppressWarnings("unused")
75+
public static class CountedAttributesAdvice {
76+
77+
@Advice.OnMethodEnter(suppress = Throwable.class)
78+
public static void onEnter(
79+
@Advice.Origin Method originMethod,
80+
@Advice.Local("otelMethod") Method method,
81+
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args,
82+
@Advice.Local("otelRequest") MethodRequest request,
83+
@Advice.Local("stopwatch") Stopwatch stopwatch) {
84+
85+
// Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
86+
// to local variable so that there would be only one call to Class.getMethod.
87+
method = originMethod;
88+
request = new MethodRequest(method, args);
89+
}
90+
91+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
92+
public static void onExit(
93+
@Advice.Local("otelMethod") Method method,
94+
@Advice.Local("otelRequest") MethodRequest request,
95+
@Advice.Local("stopwatch") Stopwatch stopwatch,
96+
@Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
97+
@Advice.Thrown Throwable throwable) {
98+
AnnotationSingletons.recordCountWithAttributes(request);
99+
}
100+
}
101+
102+
@SuppressWarnings("unused")
103+
public static class CountedAdvice {
104+
105+
@Advice.OnMethodEnter(suppress = Throwable.class)
106+
public static void onEnter(
107+
@Advice.Origin Method originMethod,
108+
@Advice.Local("otelMethod") Method method,
109+
@Advice.Local("stopwatch") Stopwatch stopwatch) {
110+
// Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
111+
// to local variable so that there would be only one call to Class.getMethod.
112+
method = originMethod;
113+
}
114+
115+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
116+
public static void stopSpan(
117+
@Advice.Local("otelMethod") Method method,
118+
@Advice.Local("stopwatch") Stopwatch stopwatch,
119+
@Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
120+
@Advice.Thrown Throwable throwable) {
121+
AnnotationSingletons.recordCount(method);
122+
}
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.instrumentationannotations;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Arrays.asList;
10+
11+
import com.google.auto.service.AutoService;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import java.util.List;
15+
import net.bytebuddy.matcher.ElementMatcher;
16+
17+
@AutoService(InstrumentationModule.class)
18+
public class CountedInstrumentationsModule extends InstrumentationModule {
19+
20+
public CountedInstrumentationsModule() {
21+
super("opentelemetry-instrumentation-annotations", "counted");
22+
}
23+
24+
@Override
25+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
26+
return hasClassesNamed("application.io.opentelemetry.instrumentation.annotations.Counted");
27+
}
28+
29+
@Override
30+
public List<TypeInstrumentation> typeInstrumentations() {
31+
return asList(new CountedInstrumentation());
32+
}
33+
34+
@Override
35+
public int order() {
36+
// Run first to ensure other automatic instrumentation is added after and therefore is executed
37+
// earlier in the instrumented method and create the span to attach attributes to.
38+
return -1000;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.instrumentationannotations;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Arrays.asList;
10+
11+
import com.google.auto.service.AutoService;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import java.util.List;
15+
import net.bytebuddy.matcher.ElementMatcher;
16+
17+
@AutoService(InstrumentationModule.class)
18+
public class TimedInstrumentationsModule extends InstrumentationModule {
19+
20+
public TimedInstrumentationsModule() {
21+
super("opentelemetry-instrumentation-annotations", "timed");
22+
}
23+
24+
@Override
25+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
26+
return hasClassesNamed("application.io.opentelemetry.instrumentation.annotations.Timed");
27+
}
28+
29+
@Override
30+
public List<TypeInstrumentation> typeInstrumentations() {
31+
return asList(new TimedInstrumentation());
32+
}
33+
34+
@Override
35+
public int order() {
36+
// Run first to ensure other automatic instrumentation is added after and therefore is executed
37+
// earlier in the instrumented method and create the span to attach attributes to.
38+
return -1000;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.opentelemetry.test.annotation;
2+
3+
import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.annotations.Counted;
4+
5+
public class CountedExample {
6+
7+
@Counted
8+
public String defaultExample() {
9+
return "defualt example";
10+
}
11+
12+
@Counted("another.name")
13+
public String exampleWithAnotherName() {
14+
return "example with another name.";
15+
}
16+
17+
}

0 commit comments

Comments
 (0)