diff --git a/.fossa.yml b/.fossa.yml
index 04356f396aa3..07e9301f626d 100644
--- a/.fossa.yml
+++ b/.fossa.yml
@@ -10,6 +10,9 @@ targets:
- type: gradle
path: ./
target: ':instrumentation-annotations'
+ - type: gradle
+ path: ./
+ target: ':instrumentation-annotations-incubator'
- type: gradle
path: ./
target: ':instrumentation-annotations-support'
@@ -214,9 +217,6 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:opentelemetry-extension-kotlin-1.0:javaagent'
- - type: gradle
- path: ./
- target: ':instrumentation:opentelemetry-instrumentation-annotations-1.16:javaagent'
- type: gradle
path: ./
target: ':instrumentation:opentelemetry-instrumentation-api:javaagent'
@@ -811,6 +811,15 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:opentelemetry-api:opentelemetry-api-1.52:javaagent'
+ - type: gradle
+ path: ./
+ target: ':instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-1.16:javaagent'
+ - type: gradle
+ path: ./
+ target: ':instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-common:javaagent'
+ - type: gradle
+ path: ./
+ target: ':instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-incubator:javaagent'
- type: gradle
path: ./
target: ':instrumentation:pekko:pekko-actor-1.0:javaagent'
diff --git a/conventions/src/main/kotlin/otel.java-conventions.gradle.kts b/conventions/src/main/kotlin/otel.java-conventions.gradle.kts
index a944cbccc349..ff6ff6ee9acd 100644
--- a/conventions/src/main/kotlin/otel.java-conventions.gradle.kts
+++ b/conventions/src/main/kotlin/otel.java-conventions.gradle.kts
@@ -476,6 +476,7 @@ configurations.configureEach {
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")).using(project(":instrumentation-api"))
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator")).using(project(":instrumentation-api-incubator"))
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")).using(project(":instrumentation-annotations"))
+ substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations-incubator")).using(project(":instrumentation-annotations-incubator"))
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations-support")).using(
project(":instrumentation-annotations-support")
)
diff --git a/instrumentation-annotations-incubator/build.gradle.kts b/instrumentation-annotations-incubator/build.gradle.kts
new file mode 100644
index 000000000000..7a0e2dac5de6
--- /dev/null
+++ b/instrumentation-annotations-incubator/build.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ id("otel.java-conventions")
+ id("otel.japicmp-conventions")
+ id("otel.publish-conventions")
+
+ id("otel.animalsniffer-conventions")
+}
+
+group = "io.opentelemetry.instrumentation"
+
+dependencies {
+ api("io.opentelemetry:opentelemetry-api")
+}
+
+tasks.test {
+ // This module does not have tests, but has example classes in the test directory. Gradle 9 fails
+ // the build when there are source files in the test directory but no tests to run so we disable
+ // the test task.
+ enabled = false
+}
diff --git a/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Attribute.java b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Attribute.java
new file mode 100644
index 000000000000..49a12942e38f
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Attribute.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation marks that a parameter of a method or constructor annotated with {@link Timed} or
+ * {@link Counted} should be added as an attribute to the metric.
+ *
+ *
`{@link Object#toString()}` will be called on the parameter value to convert it to a String.
+ *
+ *
Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
+ * that the attribute should be captured.
+ *
+ *
If you are a library developer, then probably you should NOT use this annotation, because it
+ * is non-functional without the OpenTelemetry auto-instrumentation agent, or some other annotation
+ * processor.
+ *
+ *
Warning: be careful using this because it might cause an explosion of the cardinality on your
+ * metric.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Attribute {
+
+ /**
+ * Optional name of the attribute.
+ *
+ *
If not specified and the code is compiled using the `{@code -parameters}` argument to
+ * `javac`, the parameter name will be used instead. If the parameter name is not available, e.g.,
+ * because the code was not compiled with that flag, the attribute will be ignored.
+ */
+ String name() default "";
+}
diff --git a/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Counted.java b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Counted.java
new file mode 100644
index 000000000000..913011258e98
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Counted.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation creates a {@link io.opentelemetry.api.metrics.LongCounter Counter} instrument
+ * recording the number of invocations of the annotated method or constructor.
+ *
+ *
By default, the Counter instrument will have the following attributes:
+ *
+ *
+ * code.namespace: The fully qualified name of the class whose method is invoked.
+ * code.function: The name of the annotated method.
+ * error.type: This is only present if an Exception is thrown, and contains the {@link
+ * Class#getName name} of the Exception class.
+ *
+ *
+ * Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
+ * that a Counter metric should be captured.
+ *
+ *
If you are a library developer, then probably you should NOT use this annotation, because it
+ * is non-functional without the OpenTelemetry auto-instrumentation agent, or some other annotation
+ * processor.
+ */
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Counted {
+
+ /**
+ * Name of the Counter metric.
+ *
+ *
The name should follow the metric naming rules: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-name-syntax
+ */
+ String name();
+
+ /**
+ * Description of the metric.
+ *
+ *
Description strings should follow the metric description rules: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-description
+ *
+ *
This property will not take effect if the value is not specified.
+ */
+ String description() default "";
+
+ /**
+ * Unit of the metric.
+ *
+ *
Unit strings should follow the metric unit rules: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-unit
+ *
+ *
This property will not take effect if the value is not specified.
+ */
+ String unit() default "{invocation}";
+}
diff --git a/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/ReturnValueAttribute.java b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/ReturnValueAttribute.java
new file mode 100644
index 000000000000..52299e8628f6
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/ReturnValueAttribute.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation allows for adding the method return value as an attribute to metrics recorded
+ * using the {@link Timed} and {@link Counted} annotations.
+ *
+ *
{@link Object#toString()} will be called on the return value to convert it to a String.
+ *
+ *
Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
+ * that the attribute should be captured.
+ *
+ *
If you are a library developer, then probably you should NOT use this annotation, because it
+ * is non-functional without the OpenTelemetry auto-instrumentation agent, or some other annotation
+ * processor.
+ *
+ *
Warning: be careful using this because it might cause an explosion of the cardinality on your
+ * metric.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReturnValueAttribute {
+
+ /**
+ * Attribute name for the return value.
+ *
+ *
The name of the attribute for the return value of the method call.
+ */
+ String value();
+}
diff --git a/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/StaticAttribute.java b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/StaticAttribute.java
new file mode 100644
index 000000000000..0edd842fd634
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/StaticAttribute.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation allows for adding static attributes to the metrics recorded using {@link Timed}
+ * and {@link Counted} annotations.
+ *
+ *
Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
+ * that the static attributes should be captured.
+ *
+ *
If you are a library developer, then probably you should NOT use this annotation, because it
+ * is non-functional without the OpenTelemetry auto-instrumentation agent, or some other annotation
+ * processor.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(StaticAttributes.class)
+public @interface StaticAttribute {
+
+ /** Name of the static attribute. */
+ String name();
+
+ /** Value of the static attribute. */
+ String value();
+}
diff --git a/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/StaticAttributes.java b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/StaticAttributes.java
new file mode 100644
index 000000000000..a28447086184
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/StaticAttributes.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation allows for adding static attributes to the metrics recorded using {@link Timed}
+ * and {@link Counted} annotations.
+ *
+ *
Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
+ * that the static attributes should be captured.
+ *
+ *
If you are a library developer, then probably you should NOT use this annotation, because it
+ * is non-functional without the OpenTelemetry auto-instrumentation agent, or some other annotation
+ * processor.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StaticAttributes {
+
+ /** Array of {@link StaticAttribute} annotations describing the attributes to capture. */
+ StaticAttribute[] value();
+}
diff --git a/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Timed.java b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Timed.java
new file mode 100644
index 000000000000..3c65fcd6576a
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/main/java/io/opentelemetry/instrumentation/annotations/incubator/Timed.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation creates a {@link io.opentelemetry.api.metrics.DoubleHistogram Histogram}
+ * instrument observing the duration of invocations of the annotated method or constructor.
+ *
+ *
By default, the Histogram instrument will have the following attributes:
+ *
+ *
+ * code.namespace: The fully qualified name of the class whose method is invoked.
+ * code.function: The name of the annotated method.
+ * error.type: This is only present if an Exception is thrown, and contains the {@link
+ * Class#getName name} of the Exception class.
+ *
+ *
+ * Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
+ * that the Histogram instrument should be created.
+ *
+ *
If you are a library developer, then probably you should NOT use this annotation, because it
+ * is non-functional without the OpenTelemetry auto-instrumentation agent, or some other annotation
+ * processor.
+ */
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Timed {
+
+ /**
+ * Name of the Histogram metric.
+ *
+ *
The name should follow the metric naming rules: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-naming-rule
+ */
+ String name();
+
+ /**
+ * Description for the metric.
+ *
+ *
Description strings should follow the metric description rules: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-description
+ */
+ String description() default "";
+}
diff --git a/instrumentation-annotations-incubator/src/test/java/io/opentelemetry/instrumentation/annotations/incubator/CountedUsageExamples.java b/instrumentation-annotations-incubator/src/test/java/io/opentelemetry/instrumentation/annotations/incubator/CountedUsageExamples.java
new file mode 100644
index 000000000000..3beb58c1576b
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/test/java/io/opentelemetry/instrumentation/annotations/incubator/CountedUsageExamples.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+public class CountedUsageExamples {
+
+ @Counted(name = "customizedName")
+ public void method() {}
+
+ @Counted(name = "methodWithAttributes")
+ public void attributes(
+ @Attribute String attribute1, @Attribute(name = "attribute2") long attribute2) {}
+}
diff --git a/instrumentation-annotations-incubator/src/test/java/io/opentelemetry/instrumentation/annotations/incubator/TimedUsageExamples.java b/instrumentation-annotations-incubator/src/test/java/io/opentelemetry/instrumentation/annotations/incubator/TimedUsageExamples.java
new file mode 100644
index 000000000000..7328b9d5aa3c
--- /dev/null
+++ b/instrumentation-annotations-incubator/src/test/java/io/opentelemetry/instrumentation/annotations/incubator/TimedUsageExamples.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.annotations.incubator;
+
+public class TimedUsageExamples {
+
+ @Timed(name = "customizedName")
+ public void method() {}
+
+ @Timed(name = "methodWithAttributes")
+ public void attributes(
+ @Attribute String attribute1, @Attribute(name = "attribute2") long attribute2) {}
+}
diff --git a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodBinder.java b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodBinder.java
new file mode 100644
index 000000000000..0413b3298feb
--- /dev/null
+++ b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodBinder.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.annotation.support;
+
+import io.opentelemetry.api.common.AttributesBuilder;
+import java.lang.reflect.Method;
+import java.util.function.BiConsumer;
+import javax.annotation.Nullable;
+
+/** Helper class for binding method parameters and return value to attributes. */
+public final class MethodBinder {
+
+ /** Create binding for method return value. */
+ @Nullable
+ public static BiConsumer bindReturnValue(
+ Method method, String attributeName) {
+ Class> returnType = method.getReturnType();
+ if (returnType == void.class) {
+ return null;
+ }
+ AttributeBinding binding = AttributeBindingFactory.createBinding(attributeName, returnType);
+ return binding::apply;
+ }
+
+ /** Create binding for method parameters. */
+ @Nullable
+ public static BiConsumer bindParameters(
+ Method method, ParameterAttributeNamesExtractor parameterAttributeNamesExtractor) {
+ AttributeBindings bindings = AttributeBindings.bind(method, parameterAttributeNamesExtractor);
+ if (bindings.isEmpty()) {
+ return null;
+ }
+ return bindings::apply;
+ }
+
+ private MethodBinder() {}
+}
diff --git a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationCallback.java b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationCallback.java
new file mode 100644
index 000000000000..f409e2627117
--- /dev/null
+++ b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationCallback.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.annotation.support.async;
+
+import io.opentelemetry.context.Context;
+import javax.annotation.Nullable;
+
+/** Callback that is called when async computation completes. */
+public interface AsyncOperationCallback {
+ void onEnd(
+ Context context, REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error);
+}
diff --git a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndStrategy.java b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndStrategy.java
index 01dbaa55eef5..c78e9629aebe 100644
--- a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndStrategy.java
+++ b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndStrategy.java
@@ -11,7 +11,8 @@
/**
* Implementations of this interface describe how to compose over {@linkplain #supports(Class)
* supported} asynchronous computation types and delay marking the operation as ended by calling
- * {@link Instrumenter#end(Context, Object, Object, Throwable)}.
+ * {@link Instrumenter#end(Context, Object, Object, Throwable)} or {@link
+ * AsyncOperationCallback#onEnd(Context, Object, Object, Throwable)}.
*/
public interface AsyncOperationEndStrategy {
@@ -36,10 +37,35 @@ public interface AsyncOperationEndStrategy {
* @return Either {@code asyncValue} or a value composing over {@code asyncValue} for notification
* of completion.
*/
- Object end(
+ default Object end(
Instrumenter instrumenter,
Context context,
REQUEST request,
Object asyncValue,
+ Class responseType) {
+ return end(instrumenter::end, context, request, asyncValue, responseType);
+ }
+
+ /**
+ * Composes over {@code asyncValue} and delays the {@link AsyncOperationCallback#onEnd(Context,
+ * Object, Object, Throwable)} call until after the asynchronous operation represented by {@code
+ * asyncValue} completes.
+ *
+ * @param handler The {@link AsyncOperationCallback} to be used to end the operation stored in the
+ * {@code context}.
+ * @param asyncValue Return value from the instrumented method. Must be an instance of a {@code
+ * asyncType} for which {@link #supports(Class)} returned true (in particular it must not be
+ * {@code null}).
+ * @param responseType Expected type of the response that should be obtained from the {@code
+ * asyncValue}. If the result of the async computation is instance of the passed type it will
+ * be passed when the {@code handler} is called.
+ * @return Either {@code asyncValue} or a value composing over {@code asyncValue} for notification
+ * of completion.
+ */
+ Object end(
+ AsyncOperationCallback handler,
+ Context context,
+ REQUEST request,
+ Object asyncValue,
Class responseType);
}
diff --git a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndSupport.java b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndSupport.java
index 8bcbd5c3f978..08dc92c01810 100644
--- a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndSupport.java
+++ b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/AsyncOperationEndSupport.java
@@ -10,40 +10,59 @@
import javax.annotation.Nullable;
/**
- * A wrapper over {@link Instrumenter} that is able to defer {@link Instrumenter#end(Context,
- * Object, Object, Throwable)} until asynchronous computation finishes.
+ * A wrapper over {@link AsyncOperationCallback} that is able to defer {@link
+ * AsyncOperationCallback#onEnd(Context, Object, Object, Throwable)} until asynchronous computation
+ * finishes.
*/
public final class AsyncOperationEndSupport {
/**
- * Returns a new {@link AsyncOperationEndSupport} that wraps over passed {@code syncInstrumenter},
+ * Returns a new {@link AsyncOperationEndSupport} that wraps over passed {@code instrumenter},
* configured for usage with asynchronous computations that are instances of {@code asyncType}. If
* the result of the async computation ends up being an instance of {@code responseType} it will
- * be passed as the response to the {@code syncInstrumenter} call; otherwise {@code null} value
- * will be used as the response.
+ * be passed as the response to the {@code instrumenter} call; otherwise {@code null} value will
+ * be used as the response.
*/
public static AsyncOperationEndSupport create(
- Instrumenter syncInstrumenter,
+ Instrumenter instrumenter,
Class responseType,
Class> asyncType) {
return new AsyncOperationEndSupport<>(
- syncInstrumenter,
+ instrumenter::end,
responseType,
asyncType,
AsyncOperationEndStrategies.instance().resolveStrategy(asyncType));
}
- private final Instrumenter instrumenter;
+ /**
+ * Returns a new {@link AsyncOperationEndSupport} that wraps over passed {@code handler},
+ * configured for usage with asynchronous computations that are instances of {@code asyncType}. If
+ * the result of the async computation ends up being an instance of {@code responseType} it will
+ * be passed as the response to the {@code handler} call; otherwise {@code null} value will be
+ * used as the response.
+ */
+ public static AsyncOperationEndSupport create(
+ AsyncOperationCallback handler,
+ Class responseType,
+ Class> asyncType) {
+ return new AsyncOperationEndSupport<>(
+ handler,
+ responseType,
+ asyncType,
+ AsyncOperationEndStrategies.instance().resolveStrategy(asyncType));
+ }
+
+ private final AsyncOperationCallback handler;
private final Class responseType;
private final Class> asyncType;
@Nullable private final AsyncOperationEndStrategy asyncOperationEndStrategy;
private AsyncOperationEndSupport(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Class responseType,
Class> asyncType,
@Nullable AsyncOperationEndStrategy asyncOperationEndStrategy) {
- this.instrumenter = instrumenter;
+ this.handler = handler;
this.responseType = responseType;
this.asyncType = asyncType;
this.asyncOperationEndStrategy = asyncOperationEndStrategy;
@@ -68,18 +87,18 @@ public ASYNC asyncEnd(
Context context, REQUEST request, @Nullable ASYNC asyncValue, @Nullable Throwable throwable) {
// we can end early if an exception was thrown
if (throwable != null) {
- instrumenter.end(context, request, null, throwable);
+ handler.onEnd(context, request, null, throwable);
return asyncValue;
}
// use the configured strategy to compose over the asyncValue
if (asyncOperationEndStrategy != null && asyncType.isInstance(asyncValue)) {
return (ASYNC)
- asyncOperationEndStrategy.end(instrumenter, context, request, asyncValue, responseType);
+ asyncOperationEndStrategy.end(handler, context, request, asyncValue, responseType);
}
// fall back to sync end() if asyncValue type doesn't match
- instrumenter.end(context, request, tryToGetResponse(responseType, asyncValue), null);
+ handler.onEnd(context, request, tryToGetResponse(responseType, asyncValue), null);
return asyncValue;
}
diff --git a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/Jdk8AsyncOperationEndStrategy.java b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/Jdk8AsyncOperationEndStrategy.java
index c1eea3a419ed..84b6dcf9b9d9 100644
--- a/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/Jdk8AsyncOperationEndStrategy.java
+++ b/instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/async/Jdk8AsyncOperationEndStrategy.java
@@ -8,7 +8,6 @@
import static io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport.tryToGetResponse;
import io.opentelemetry.context.Context;
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -22,20 +21,20 @@ public boolean supports(Class> asyncType) {
@Override
public Object end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
Object asyncValue,
Class responseType) {
if (asyncValue instanceof CompletableFuture) {
CompletableFuture> future = (CompletableFuture>) asyncValue;
- if (tryToEndSynchronously(instrumenter, context, request, future, responseType)) {
+ if (tryToEndSynchronously(handler, context, request, future, responseType)) {
return future;
}
- return endWhenComplete(instrumenter, context, request, future, responseType);
+ return endWhenComplete(handler, context, request, future, responseType);
}
CompletionStage> stage = (CompletionStage>) asyncValue;
- return endWhenComplete(instrumenter, context, request, stage, responseType);
+ return endWhenComplete(handler, context, request, stage, responseType);
}
/**
@@ -44,7 +43,7 @@ public Object end(
* notification of completion.
*/
private static boolean tryToEndSynchronously(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
CompletableFuture> future,
@@ -56,9 +55,9 @@ private static boolean tryToEndSynchronously(
try {
Object potentialResponse = future.join();
- instrumenter.end(context, request, tryToGetResponse(responseType, potentialResponse), null);
+ handler.onEnd(context, request, tryToGetResponse(responseType, potentialResponse), null);
} catch (Throwable t) {
- instrumenter.end(context, request, null, t);
+ handler.onEnd(context, request, null, t);
}
return true;
}
@@ -68,13 +67,13 @@ private static boolean tryToEndSynchronously(
* span will be ended.
*/
private static CompletionStage> endWhenComplete(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
CompletionStage> stage,
Class responseType) {
return stage.whenComplete(
(result, exception) ->
- instrumenter.end(context, request, tryToGetResponse(responseType, result), exception));
+ handler.onEnd(context, request, tryToGetResponse(responseType, result), exception));
}
}
diff --git a/instrumentation/guava-10.0/library/src/main/java/io/opentelemetry/instrumentation/guava/v10_0/GuavaAsyncOperationEndStrategy.java b/instrumentation/guava-10.0/library/src/main/java/io/opentelemetry/instrumentation/guava/v10_0/GuavaAsyncOperationEndStrategy.java
index dbdbff784728..62a47bd1a5d3 100644
--- a/instrumentation/guava-10.0/library/src/main/java/io/opentelemetry/instrumentation/guava/v10_0/GuavaAsyncOperationEndStrategy.java
+++ b/instrumentation/guava-10.0/library/src/main/java/io/opentelemetry/instrumentation/guava/v10_0/GuavaAsyncOperationEndStrategy.java
@@ -12,8 +12,8 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategy;
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
public final class GuavaAsyncOperationEndStrategy implements AsyncOperationEndStrategy {
// Visible for testing
@@ -41,19 +41,19 @@ public boolean supports(Class> returnType) {
@Override
public Object end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
Object asyncValue,
Class responseType) {
ListenableFuture> future = (ListenableFuture>) asyncValue;
- end(instrumenter, context, request, future, responseType);
+ end(handler, context, request, future, responseType);
return future;
}
private void end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
ListenableFuture> future,
@@ -63,18 +63,17 @@ private void end(
if (captureExperimentalSpanAttributes) {
Span.fromContext(context).setAttribute(CANCELED_ATTRIBUTE_KEY, true);
}
- instrumenter.end(context, request, null, null);
+ handler.onEnd(context, request, null, null);
} else {
try {
Object response = Uninterruptibles.getUninterruptibly(future);
- instrumenter.end(context, request, tryToGetResponse(responseType, response), null);
+ handler.onEnd(context, request, tryToGetResponse(responseType, response), null);
} catch (Throwable exception) {
- instrumenter.end(context, request, null, exception);
+ handler.onEnd(context, request, null, exception);
}
}
} else {
- future.addListener(
- () -> end(instrumenter, context, request, future, responseType), Runnable::run);
+ future.addListener(() -> end(handler, context, request, future, responseType), Runnable::run);
}
}
}
diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts
index 3e1be05c41ff..8d937d750112 100644
--- a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts
+++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts
@@ -30,7 +30,7 @@ dependencies {
implementation("org.ow2.asm:asm-tree")
implementation("org.ow2.asm:asm-util")
- implementation(project(":instrumentation:opentelemetry-instrumentation-annotations-1.16:javaagent"))
+ implementation(project(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-common:javaagent"))
testInstrumentation(project(":instrumentation:opentelemetry-extension-kotlin-1.0:javaagent"))
testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent"))
diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts
index 755a2a9fa85f..1aa3ece053e8 100644
--- a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts
+++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts
@@ -12,6 +12,7 @@ dependencies {
compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compileOnly(project(":instrumentation-api"))
+ compileOnly(project(":instrumentation-annotations-support"))
}
kotlin {
diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt
index 402251782cba..84994f957fbe 100644
--- a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt
+++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt
@@ -6,10 +6,10 @@
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow
import io.opentelemetry.context.Context
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onCompletion
-fun onComplete(flow: Flow<*>, instrumenter: Instrumenter, context: Context, request: REQUEST & Any): Flow<*> = flow.onCompletion { cause: Throwable? ->
- instrumenter.end(context, request, null, cause)
+fun onComplete(flow: Flow<*>, handler: AsyncOperationCallback, context: Context, request: REQUEST & Any): Flow<*> = flow.onCompletion { cause: Throwable? ->
+ handler.onEnd(context, request, null, cause)
}
diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java
index 02102afe01d0..fcc83dfa446c 100644
--- a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java
+++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java
@@ -6,9 +6,9 @@
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow;
import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategies;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategy;
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import kotlinx.coroutines.flow.Flow;
public final class FlowInstrumentationHelper {
@@ -32,13 +32,13 @@ public boolean supports(Class> returnType) {
@Override
public Object end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
Object asyncValue,
Class responseType) {
Flow> flow = (Flow>) asyncValue;
- return FlowUtilKt.onComplete(flow, instrumenter, context, request);
+ return FlowUtilKt.onComplete(flow, handler, context, request);
}
}
}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md b/instrumentation/opentelemetry-instrumentation-annotations/README.md
similarity index 90%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md
rename to instrumentation/opentelemetry-instrumentation-annotations/README.md
index aa7476f0a5b9..172efe278037 100644
--- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md
+++ b/instrumentation/opentelemetry-instrumentation-annotations/README.md
@@ -1,7 +1,6 @@
# Settings for the OpenTelemetry Instrumentation Annotations integration
-Instruments methods annotated with OpenTelemetry instrumentation annotations, such as @WithSpan and
-@SpanAttribute.
+Instruments methods annotated with OpenTelemetry instrumentation annotations, such as @WithSpan, @SpanAttribute, @Counted and @Timed.
| Environment variable | Type | Default | Description |
| -------------------------------------------------------------------------------- | ------ | ------- | --------------------------------------------------------------------------------- |
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/build.gradle.kts b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/build.gradle.kts
similarity index 91%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/build.gradle.kts
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/build.gradle.kts
index 6ea1548e4354..e9f6b111a94c 100644
--- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/build.gradle.kts
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/build.gradle.kts
@@ -16,8 +16,8 @@ muzzle {
dependencies {
compileOnly(project(":instrumentation-annotations-support"))
-
compileOnly(project(":javaagent-tooling"))
+ implementation(project(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-common:javaagent"))
// this instrumentation needs to do similar shading dance as opentelemetry-api-1.0 because
// the @WithSpan annotation references the OpenTelemetry API's SpanKind class
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AddingSpanAttributesInstrumentation.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AddingSpanAttributesInstrumentation.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AddingSpanAttributesInstrumentation.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AddingSpanAttributesInstrumentation.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationInstrumentationModule.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationInstrumentationModule.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationInstrumentationModule.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationInstrumentationModule.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationSingletons.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodCodeAttributesGetter.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodCodeAttributesGetter.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodCodeAttributesGetter.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodCodeAttributesGetter.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequestCodeAttributesGetter.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequestCodeAttributesGetter.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequestCodeAttributesGetter.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequestCodeAttributesGetter.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanInstrumentation.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanParameterAttributeNamesExtractor.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanParameterAttributeNamesExtractor.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanParameterAttributeNamesExtractor.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/WithSpanParameterAttributeNamesExtractor.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/AddingSpanAttributesInstrumentationTest.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/AddingSpanAttributesInstrumentationTest.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/AddingSpanAttributesInstrumentationTest.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/AddingSpanAttributesInstrumentationTest.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/ExtractAttributesUsingAddingSpanAttributes.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/ExtractAttributesUsingAddingSpanAttributes.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/ExtractAttributesUsingAddingSpanAttributes.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/ExtractAttributesUsingAddingSpanAttributes.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/TracedWithSpan.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/javaagent/src/test/java/io/opentelemetry/test/annotation/WithSpanInstrumentationTest.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/metadata.yaml b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/metadata.yaml
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/metadata.yaml
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-1.16/metadata.yaml
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/build.gradle.kts b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..8c365ac278a8
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+dependencies {
+ compileOnly(project(":instrumentation-annotations-support"))
+
+ compileOnly(project(":javaagent-tooling"))
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationExcludedMethods.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationExcludedMethods.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationExcludedMethods.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/AnnotationExcludedMethods.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/KotlinCoroutineUtil.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/KotlinCoroutineUtil.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/KotlinCoroutineUtil.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/KotlinCoroutineUtil.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequest.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequest.java
similarity index 100%
rename from instrumentation/opentelemetry-instrumentation-annotations-1.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequest.java
rename to instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/MethodRequest.java
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/build.gradle.kts b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..f5e3d05ae1f4
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/build.gradle.kts
@@ -0,0 +1,41 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+// note that muzzle is not run against the current SNAPSHOT instrumentation-annotations, but this is
+// ok because the tests are run against the current SNAPSHOT instrumentation-annotations which will
+// catch any muzzle issues in SNAPSHOT instrumentation-annotations
+
+muzzle {
+ pass {
+ group.set("io.opentelemetry")
+ module.set("opentelemetry-instrumentation-annotations-incubator")
+ versions.set("(,)")
+ }
+}
+
+dependencies {
+ compileOnly(project(":instrumentation-annotations-support"))
+ compileOnly(project(":javaagent-tooling"))
+ implementation(project(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-common:javaagent"))
+
+ // this instrumentation needs to do similar shading dance as opentelemetry-api-1.0 because
+ // the @WithSpan annotation references the OpenTelemetry API's SpanKind class
+ //
+ // see the comment in opentelemetry-api-1.0.gradle for more details
+ compileOnly(project(":opentelemetry-instrumentation-annotations-shaded-for-instrumenting", configuration = "shadow"))
+
+ testImplementation(project(":instrumentation-annotations-incubator"))
+ testImplementation(project(":instrumentation-annotations-support"))
+}
+
+tasks {
+ compileTestJava {
+ options.compilerArgs.add("-parameters")
+ }
+ test {
+ jvmArgs(
+ "-Dotel.instrumentation.opentelemetry-instrumentation-annotations.exclude-methods=io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted.CountedExample[exampleIgnore];io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.timed.TimedExample[exampleIgnore]"
+ )
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/CountedHelper.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/CountedHelper.java
new file mode 100644
index 000000000000..72b9f929a292
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/CountedHelper.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator;
+
+import application.io.opentelemetry.instrumentation.annotations.incubator.Counted;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport;
+import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.MethodRequest;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class CountedHelper extends MetricsAnnotationHelper {
+
+ private static final ClassValue> counters =
+ new ClassValue>() {
+ @Override
+ protected Map computeValue(Class> type) {
+ return new ConcurrentHashMap<>();
+ }
+ };
+
+ public static Object recordWithAttributes(
+ MethodRequest methodRequest, Object returnValue, Throwable throwable) {
+ return record(methodRequest.method(), returnValue, throwable, methodRequest.args());
+ }
+
+ public static Object record(Method method, Object returnValue, Throwable throwable) {
+ return record(method, returnValue, throwable, null);
+ }
+
+ private static Object record(
+ Method method, Object returnValue, Throwable throwable, Object[] arguments) {
+ AsyncOperationEndSupport operationEndSupport =
+ AsyncOperationEndSupport.create(
+ (context, m, object, error) -> getMethodCounter(m).record(object, arguments, error),
+ Object.class,
+ method.getReturnType());
+ return operationEndSupport.asyncEnd(Context.current(), method, returnValue, throwable);
+ }
+
+ private static MethodCounter getMethodCounter(Method method) {
+ return counters.get(method.getDeclaringClass()).computeIfAbsent(method, MethodCounter::new);
+ }
+
+ private static class MethodCounter {
+ private final LongCounter counter;
+ private final MetricAttributeHelper attributeHelper;
+
+ MethodCounter(Method method) {
+ Counted countedAnnotation = method.getAnnotation(Counted.class);
+ counter =
+ METER
+ .counterBuilder(countedAnnotation.name())
+ .setDescription(countedAnnotation.description())
+ .setUnit(countedAnnotation.unit())
+ .build();
+ attributeHelper = new MetricAttributeHelper(method);
+ }
+
+ void record(Object returnValue, Object[] arguments, Throwable throwable) {
+ counter.add(1, attributeHelper.getAttributes(returnValue, arguments, throwable));
+ }
+ }
+
+ private CountedHelper() {}
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/CountedInstrumentation.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/CountedInstrumentation.java
new file mode 100644
index 000000000000..025a711579af
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/CountedInstrumentation.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator;
+
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.KotlinCoroutineUtil.isKotlinSuspendMethod;
+import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
+import static net.bytebuddy.matcher.ElementMatchers.hasParameters;
+import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.whereAny;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.AnnotationExcludedMethods;
+import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.MethodRequest;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class CountedInstrumentation implements TypeInstrumentation {
+
+ private final ElementMatcher.Junction annotatedMethodMatcher;
+ private final ElementMatcher.Junction annotatedParametersMatcher;
+ // this matcher matches all methods that should be excluded from transformation
+ private final ElementMatcher.Junction excludedMethodsMatcher;
+
+ CountedInstrumentation() {
+ annotatedMethodMatcher =
+ isAnnotatedWith(
+ named("application.io.opentelemetry.instrumentation.annotations.incubator.Counted"));
+ annotatedParametersMatcher =
+ hasParameters(
+ whereAny(
+ isAnnotatedWith(
+ named(
+ "application.io.opentelemetry.instrumentation.annotations.incubator.Attribute"))));
+ // exclude all kotlin suspend methods, these are handle in kotlinx-coroutines instrumentation
+ excludedMethodsMatcher =
+ AnnotationExcludedMethods.configureExcludedMethods().or(isKotlinSuspendMethod());
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return declaresMethod(annotatedMethodMatcher);
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ ElementMatcher.Junction countedMethods =
+ annotatedMethodMatcher.and(not(excludedMethodsMatcher));
+
+ ElementMatcher.Junction timedMethodsWithParameters =
+ countedMethods.and(annotatedParametersMatcher);
+
+ ElementMatcher.Junction timedMethodsWithoutParameters =
+ countedMethods.and(not(annotatedParametersMatcher));
+
+ transformer.applyAdviceToMethod(
+ timedMethodsWithoutParameters, CountedInstrumentation.class.getName() + "$CountedAdvice");
+
+ // Only apply advice for tracing parameters as attributes if any of the parameters are annotated
+ // with @MetricsAttribute to avoid unnecessarily copying the arguments into an array.
+ transformer.applyAdviceToMethod(
+ timedMethodsWithParameters,
+ CountedInstrumentation.class.getName() + "$CountedAttributesAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class CountedAttributesAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Origin Method method,
+ @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args,
+ @Advice.Local("otelRequest") MethodRequest request) {
+ // Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
+ // to local variable so that there would be only one call to Class.getMethod.
+ request = new MethodRequest(method, args);
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Local("otelRequest") MethodRequest request,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
+ @Advice.Thrown Throwable throwable) {
+ returnValue = CountedHelper.recordWithAttributes(request, returnValue, throwable);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CountedAdvice {
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Origin Method method,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
+ @Advice.Thrown Throwable throwable) {
+ returnValue = CountedHelper.record(method, returnValue, throwable);
+ }
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/MetricsAnnotationHelper.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/MetricsAnnotationHelper.java
new file mode 100644
index 000000000000..f3b78dac02cb
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/MetricsAnnotationHelper.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator;
+
+import application.io.opentelemetry.instrumentation.annotations.incubator.Attribute;
+import application.io.opentelemetry.instrumentation.annotations.incubator.ReturnValueAttribute;
+import application.io.opentelemetry.instrumentation.annotations.incubator.StaticAttribute;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.instrumentation.api.annotation.support.MethodBinder;
+import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor;
+import io.opentelemetry.semconv.CodeAttributes;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.function.BiConsumer;
+import javax.annotation.Nullable;
+
+public abstract class MetricsAnnotationHelper {
+ private static final String INSTRUMENTATION_NAME =
+ "io.opentelemetry.opentelemetry-instrumentation-annotations-incubator";
+ static final Meter METER = GlobalOpenTelemetry.get().getMeter(INSTRUMENTATION_NAME);
+ static final ParameterAttributeNamesExtractor PARAMETER_ATTRIBUTE_NAMES_EXTRACTOR =
+ (method, parameters) -> {
+ String[] attributeNames = new String[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ attributeNames[i] = attributeName(parameters[i]);
+ }
+ return attributeNames;
+ };
+
+ static void addStaticAttributes(Method method, AttributesBuilder attributesBuilder) {
+ attributesBuilder.put(
+ CodeAttributes.CODE_FUNCTION_NAME,
+ method.getDeclaringClass().getName() + "." + method.getName());
+
+ StaticAttribute[] staticAttributes = method.getDeclaredAnnotationsByType(StaticAttribute.class);
+ for (StaticAttribute staticAttribute : staticAttributes) {
+ attributesBuilder.put(staticAttribute.name(), staticAttribute.value());
+ }
+ }
+
+ @Nullable
+ private static String attributeName(Parameter parameter) {
+ Attribute annotation = parameter.getDeclaredAnnotation(Attribute.class);
+ if (annotation == null) {
+ return null;
+ }
+ String name = annotation.name();
+ if (!name.isEmpty()) {
+ return name;
+ } else if (parameter.isNamePresent()) {
+ return parameter.getName();
+ } else {
+ return null;
+ }
+ }
+
+ static class MetricAttributeHelper {
+ private final BiConsumer bindParameters;
+ private final BiConsumer bindReturn;
+ private final Attributes staticAttributes;
+
+ MetricAttributeHelper(Method method) {
+ bindParameters = MethodBinder.bindParameters(method, PARAMETER_ATTRIBUTE_NAMES_EXTRACTOR);
+ ReturnValueAttribute returnValueAttribute = method.getAnnotation(ReturnValueAttribute.class);
+ bindReturn =
+ returnValueAttribute != null
+ ? MethodBinder.bindReturnValue(method, returnValueAttribute.value())
+ : null;
+
+ AttributesBuilder attributesBuilder = Attributes.builder();
+ addStaticAttributes(method, attributesBuilder);
+ staticAttributes = attributesBuilder.build();
+ }
+
+ Attributes getAttributes(Object returnValue, Object[] arguments, Throwable throwable) {
+ AttributesBuilder attributesBuilder = Attributes.builder();
+ attributesBuilder.putAll(staticAttributes);
+ if (arguments != null && bindParameters != null) {
+ bindParameters.accept(attributesBuilder, arguments);
+ }
+ if (returnValue != null && bindReturn != null) {
+ bindReturn.accept(attributesBuilder, returnValue);
+ }
+ if (throwable != null) {
+ attributesBuilder.put("error.type", throwable.getClass().getName());
+ }
+ return attributesBuilder.build();
+ }
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/MetricsAnnotationInstrumentationModule.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/MetricsAnnotationInstrumentationModule.java
new file mode 100644
index 000000000000..2a4c6e76ab62
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/MetricsAnnotationInstrumentationModule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static java.util.Arrays.asList;
+
+import application.io.opentelemetry.instrumentation.annotations.incubator.Attribute;
+import application.io.opentelemetry.instrumentation.annotations.incubator.Counted;
+import application.io.opentelemetry.instrumentation.annotations.incubator.Timed;
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * Instrumentation for methods annotated with {@link Counted}, {@link Timed} and {@link Attribute}
+ * annotations.
+ */
+@AutoService(InstrumentationModule.class)
+public class MetricsAnnotationInstrumentationModule extends InstrumentationModule {
+
+ public MetricsAnnotationInstrumentationModule() {
+ super("opentelemetry-instrumentation-annotations-incubator", "metrics-annotations");
+ }
+
+ @Override
+ public int order() {
+ // Run first to ensure other automatic instrumentation is added after and therefore is executed
+ // earlier in the instrumented method and create the span to attach attributes to.
+ return -1000;
+ }
+
+ @Override
+ public ElementMatcher.Junction classLoaderMatcher() {
+ return hasClassesNamed(
+ "application.io.opentelemetry.instrumentation.annotations.incubator.Counted");
+ }
+
+ @Override
+ public boolean isIndyModule() {
+ // TimedInstrumentation does not work with indy
+ return false;
+ }
+
+ @Override
+ public List typeInstrumentations() {
+ return asList(new CountedInstrumentation(), new TimedInstrumentation());
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/TimedHelper.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/TimedHelper.java
new file mode 100644
index 000000000000..6c8ba93780cf
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/TimedHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import application.io.opentelemetry.instrumentation.annotations.incubator.Timed;
+import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport;
+import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.MethodRequest;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public final class TimedHelper extends MetricsAnnotationHelper {
+
+ private static final ClassValue> timers =
+ new ClassValue>() {
+ @Override
+ protected Map computeValue(Class> type) {
+ return new ConcurrentHashMap<>();
+ }
+ };
+
+ public static Object recordWithAttributes(
+ MethodRequest methodRequest, Object returnValue, Throwable throwable, long startNanoTime) {
+ return record(
+ methodRequest.method(), returnValue, throwable, startNanoTime, methodRequest.args());
+ }
+
+ public static Object record(
+ Method method, Object returnValue, Throwable throwable, long startNanoTime) {
+ return record(method, returnValue, throwable, startNanoTime, null);
+ }
+
+ private static Object record(
+ Method method,
+ Object returnValue,
+ Throwable throwable,
+ long startNanoTime,
+ Object[] arguments) {
+ AsyncOperationEndSupport operationEndSupport =
+ AsyncOperationEndSupport.create(
+ (context, m, object, error) ->
+ getMethodTimer(m).record(object, arguments, error, startNanoTime),
+ Object.class,
+ method.getReturnType());
+ return operationEndSupport.asyncEnd(Context.current(), method, returnValue, throwable);
+ }
+
+ private static MethodTimer getMethodTimer(Method method) {
+ return timers.get(method.getDeclaringClass()).computeIfAbsent(method, MethodTimer::new);
+ }
+
+ private static double getDurationInSecond(long startNanoTime) {
+ long nanoDelta = System.nanoTime() - startNanoTime;
+ return (double) nanoDelta / NANOSECONDS.convert(1, TimeUnit.SECONDS);
+ }
+
+ private static class MethodTimer {
+ private final DoubleHistogram histogram;
+ private final MetricAttributeHelper attributeHelper;
+
+ MethodTimer(Method method) {
+ Timed timedAnnotation = method.getAnnotation(Timed.class);
+ histogram =
+ METER
+ .histogramBuilder(timedAnnotation.name())
+ .setDescription(timedAnnotation.description())
+ .setUnit("s")
+ .build();
+ attributeHelper = new MetricAttributeHelper(method);
+ }
+
+ void record(Object returnValue, Object[] arguments, Throwable throwable, long startNanoTime) {
+ double durationInSecond = getDurationInSecond(startNanoTime);
+ histogram.record(
+ durationInSecond, attributeHelper.getAttributes(returnValue, arguments, throwable));
+ }
+ }
+
+ private TimedHelper() {}
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/TimedInstrumentation.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/TimedInstrumentation.java
new file mode 100644
index 000000000000..7ef2e2af5ab9
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/TimedInstrumentation.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator;
+
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.KotlinCoroutineUtil.isKotlinSuspendMethod;
+import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
+import static net.bytebuddy.matcher.ElementMatchers.hasParameters;
+import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.whereAny;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.AnnotationExcludedMethods;
+import io.opentelemetry.javaagent.instrumentation.instrumentationannotations.MethodRequest;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class TimedInstrumentation implements TypeInstrumentation {
+
+ private final ElementMatcher.Junction annotatedMethodMatcher;
+ private final ElementMatcher.Junction annotatedParametersMatcher;
+ // this matcher matches all methods that should be excluded from transformation
+ private final ElementMatcher.Junction excludedMethodsMatcher;
+
+ TimedInstrumentation() {
+ annotatedMethodMatcher =
+ isAnnotatedWith(
+ named("application.io.opentelemetry.instrumentation.annotations.incubator.Timed"));
+ annotatedParametersMatcher =
+ hasParameters(
+ whereAny(
+ isAnnotatedWith(
+ named(
+ "application.io.opentelemetry.instrumentation.annotations.incubator.Attribute"))));
+ // exclude all kotlin suspend methods, these are handle in kotlinx-coroutines instrumentation
+ excludedMethodsMatcher =
+ AnnotationExcludedMethods.configureExcludedMethods().or(isKotlinSuspendMethod());
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return declaresMethod(annotatedMethodMatcher);
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ ElementMatcher.Junction timedMethods =
+ annotatedMethodMatcher.and(not(excludedMethodsMatcher));
+
+ ElementMatcher.Junction timedMethodsWithParameters =
+ timedMethods.and(annotatedParametersMatcher);
+
+ ElementMatcher.Junction timedMethodsWithoutParameters =
+ timedMethods.and(not(annotatedParametersMatcher));
+
+ transformer.applyAdviceToMethod(
+ timedMethodsWithoutParameters, TimedInstrumentation.class.getName() + "$TimedAdvice");
+
+ // Only apply advice for tracing parameters as attributes if any of the parameters are annotated
+ // with @MetricsAttribute to avoid unnecessarily copying the arguments into an array.
+ transformer.applyAdviceToMethod(
+ timedMethodsWithParameters,
+ TimedInstrumentation.class.getName() + "$TimedAttributesAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class TimedAttributesAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Origin Method method,
+ @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args,
+ @Advice.Local("otelRequest") MethodRequest request,
+ @Advice.Local("startNanoTime") long startNanoTime) {
+ // Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
+ // to local variable so that there would be only one call to Class.getMethod.
+ request = new MethodRequest(method, args);
+ startNanoTime = System.nanoTime();
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Local("otelRequest") MethodRequest request,
+ @Advice.Local("startNanoTime") long startNanoTime,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
+ @Advice.Thrown Throwable throwable) {
+ returnValue =
+ TimedHelper.recordWithAttributes(request, returnValue, throwable, startNanoTime);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TimedAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Origin Method originMethod,
+ @Advice.Local("otelMethod") Method method,
+ @Advice.Local("startNanoTime") long startNanoTime) {
+ // Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
+ // to local variable so that there would be only one call to Class.getMethod.
+ method = originMethod;
+ startNanoTime = System.nanoTime();
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Local("otelMethod") Method method,
+ @Advice.Local("startNanoTime") long startNanoTime,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
+ @Advice.Thrown Throwable throwable) {
+ returnValue = TimedHelper.record(method, returnValue, throwable, startNanoTime);
+ }
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/counted/CountedExample.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/counted/CountedExample.java
new file mode 100644
index 000000000000..a349b91bed31
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/counted/CountedExample.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted;
+
+import io.opentelemetry.instrumentation.annotations.incubator.Attribute;
+import io.opentelemetry.instrumentation.annotations.incubator.Counted;
+import io.opentelemetry.instrumentation.annotations.incubator.ReturnValueAttribute;
+import io.opentelemetry.instrumentation.annotations.incubator.StaticAttribute;
+import java.util.concurrent.CompletableFuture;
+
+public class CountedExample {
+
+ public static final String METRIC_NAME = "name.count";
+ public static final String METRIC_DESCRIPTION = "I am the description.";
+ public static final String METRIC_UNIT = "ms";
+ public static final String TO_STRING = "I am a to string object.";
+
+ @Counted(name = METRIC_NAME)
+ public void exampleWithName() {}
+
+ @Counted(name = "example.with.description.count", description = METRIC_DESCRIPTION)
+ public void exampleWithDescription() {}
+
+ @Counted(name = "example.with.unit.count", unit = METRIC_UNIT)
+ public void exampleWithUnit() {}
+
+ @Counted(name = "example.with.static.attributes.count")
+ @StaticAttribute(name = "key1", value = "value1")
+ @StaticAttribute(name = "key2", value = "value3")
+ @StaticAttribute(name = "key2", value = "value2")
+ public void exampleWithStaticAttributes() {}
+
+ @Counted(name = "example.with.attributes.count")
+ public void exampleWithAttributes(
+ @Attribute String attribute1,
+ @Attribute(name = "custom_attr1") long attribute2,
+ @Attribute(name = "custom_attr2") ToStringObject toStringObject) {}
+
+ @Counted(name = "example.with.return.count")
+ @ReturnValueAttribute("returnValue")
+ public ToStringObject exampleWithReturnValueAttribute() {
+ return new ToStringObject();
+ }
+
+ @Counted(name = "example.with.exception.count")
+ public void exampleWithException() {
+ throw new IllegalStateException("test exception.");
+ }
+
+ @Counted(name = "example.ignore.count")
+ public void exampleIgnore() {}
+
+ @Counted(name = "example.completable.future.count")
+ @ReturnValueAttribute("returnValue")
+ public CompletableFuture completableFuture(CompletableFuture future) {
+ return future;
+ }
+
+ public static class ToStringObject {
+ @Override
+ public String toString() {
+ return TO_STRING;
+ }
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/counted/CountedInstrumentationTest.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/counted/CountedInstrumentationTest.java
new file mode 100644
index 000000000000..bed1b811ba64
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/counted/CountedInstrumentationTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted;
+
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted.CountedExample.METRIC_DESCRIPTION;
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted.CountedExample.METRIC_NAME;
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted.CountedExample.METRIC_UNIT;
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.counted.CountedExample.TO_STRING;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import java.util.concurrent.CompletableFuture;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class CountedInstrumentationTest {
+
+ @RegisterExtension
+ private static final AgentInstrumentationExtension testing =
+ AgentInstrumentationExtension.create();
+
+ private static final String INSTRUMENTATION_NAME =
+ "io.opentelemetry.opentelemetry-instrumentation-annotations-incubator";
+
+ @Test
+ void testExampleWithAnotherName() {
+ new CountedExample().exampleWithName();
+ testing.waitAndAssertMetrics(INSTRUMENTATION_NAME, metric -> metric.hasName(METRIC_NAME));
+ }
+
+ @Test
+ void testExampleWithDescription() {
+ new CountedExample().exampleWithDescription();
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric ->
+ metric.hasName("example.with.description.count").hasDescription(METRIC_DESCRIPTION));
+ }
+
+ @Test
+ void testExampleWithUnit() {
+ new CountedExample().exampleWithUnit();
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric -> metric.hasName("example.with.unit.count").hasUnit(METRIC_UNIT));
+ }
+
+ @Test
+ void testExampleWithStaticAttributes() {
+ new CountedExample().exampleWithStaticAttributes();
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.static.attributes.count")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ "value1"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("key1")))
+ && "value2"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("key2"))))));
+ }
+
+ @Test
+ void testExampleWithAttributes() {
+ new CountedExample().exampleWithAttributes("attr1", 2, new CountedExample.ToStringObject());
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.attributes.count")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ Long.valueOf(2)
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.longKey("custom_attr1")))
+ && TO_STRING.equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("custom_attr2")))
+ && "attr1"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("attribute1"))))));
+ }
+
+ @Test
+ void testExampleWithReturnAttribute() {
+ new CountedExample().exampleWithReturnValueAttribute();
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.return.count")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ TO_STRING.equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("returnValue"))))));
+ }
+
+ @Test
+ void testExampleWithException() {
+ try {
+ new CountedExample().exampleWithException();
+ } catch (IllegalStateException e) {
+ // noop
+ }
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.exception.count")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ IllegalStateException.class
+ .getName()
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("error.type"))))));
+ }
+
+ @Test
+ void testExampleIgnore() throws Exception {
+ new CountedExample().exampleIgnore();
+ Thread.sleep(500); // sleep a bit just to make sure no metric is captured
+ assertThat(testing.metrics()).isEmpty();
+ }
+
+ @Test
+ void testCompletableFuture() throws Exception {
+ CompletableFuture future = new CompletableFuture<>();
+ new CountedExample().completableFuture(future);
+
+ Thread.sleep(500); // sleep a bit just to make sure no metric is captured
+ assertThat(testing.metrics()).isEmpty();
+
+ future.complete("Done");
+
+ testing.waitAndAssertMetrics(
+ INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.completable.future.count")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ "Done"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("returnValue"))))));
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/timed/TimedExample.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/timed/TimedExample.java
new file mode 100644
index 000000000000..924316144139
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/timed/TimedExample.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.timed;
+
+import io.opentelemetry.instrumentation.annotations.incubator.Attribute;
+import io.opentelemetry.instrumentation.annotations.incubator.ReturnValueAttribute;
+import io.opentelemetry.instrumentation.annotations.incubator.StaticAttribute;
+import io.opentelemetry.instrumentation.annotations.incubator.Timed;
+import java.util.concurrent.CompletableFuture;
+
+public class TimedExample {
+ public static final String METRIC_NAME = "name.duration";
+ public static final String METRIC_DESCRIPTION = "I am the description.";
+ public static final String TO_STRING = "I am a to string object.";
+
+ @Timed(name = METRIC_NAME)
+ public void exampleWithName() {}
+
+ @Timed(name = "example.with.description.duration", description = METRIC_DESCRIPTION)
+ public void exampleWithDescription() {}
+
+ @Timed(name = "example.with.static.attributes.duration")
+ @StaticAttribute(name = "key1", value = "value1")
+ @StaticAttribute(name = "key2", value = "value3")
+ @StaticAttribute(name = "key2", value = "value2")
+ public void exampleWithStaticAttributes() {}
+
+ @Timed(name = "example.with.attributes.duration")
+ public void exampleWithAttributes(
+ @Attribute String attribute1,
+ @Attribute(name = "custom_attr1") long attribute2,
+ @Attribute(name = "custom_attr2") TimedExample.ToStringObject toStringObject) {}
+
+ @Timed(name = "example.ignore.duration")
+ public void exampleIgnore() {}
+
+ @Timed(name = "example.with.exception.duration")
+ public void exampleWithException() {
+ throw new IllegalStateException("test");
+ }
+
+ @Timed(name = "example.with.return.duration")
+ @ReturnValueAttribute("returnValue")
+ public ToStringObject exampleWithReturnValueAttribute() {
+ return new ToStringObject();
+ }
+
+ @Timed(name = "example.completable.future.duration")
+ @ReturnValueAttribute("returnValue")
+ public CompletableFuture completableFuture(CompletableFuture future) {
+ return future;
+ }
+
+ public static class ToStringObject {
+ @Override
+ public String toString() {
+ return TO_STRING;
+ }
+ }
+}
diff --git a/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/timed/TimedInstrumentationTest.java b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/timed/TimedInstrumentationTest.java
new file mode 100644
index 000000000000..e4c42463934f
--- /dev/null
+++ b/instrumentation/opentelemetry-instrumentation-annotations/opentelemetry-instrumentation-annotations-incubator/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationannotations/incubator/timed/TimedInstrumentationTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.timed;
+
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.timed.TimedExample.METRIC_DESCRIPTION;
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.timed.TimedExample.METRIC_NAME;
+import static io.opentelemetry.javaagent.instrumentation.instrumentationannotations.incubator.timed.TimedExample.TO_STRING;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import java.util.concurrent.CompletableFuture;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class TimedInstrumentationTest {
+
+ @RegisterExtension
+ private static final AgentInstrumentationExtension testing =
+ AgentInstrumentationExtension.create();
+
+ private static final String TIMED_INSTRUMENTATION_NAME =
+ "io.opentelemetry.opentelemetry-instrumentation-annotations-incubator";
+
+ @Test
+ void testExampleWithName() {
+ new TimedExample().exampleWithName();
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME, metric -> metric.hasName(METRIC_NAME).hasUnit("s"));
+ }
+
+ @Test
+ void testExampleWithDescription() {
+ new TimedExample().exampleWithDescription();
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME,
+ metric ->
+ metric.hasName("example.with.description.duration").hasDescription(METRIC_DESCRIPTION));
+ }
+
+ @Test
+ void testExampleWithStaticAttributes() {
+ new TimedExample().exampleWithStaticAttributes();
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.static.attributes.duration")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ "value1"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("key1")))
+ && "value2"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("key2"))))));
+ }
+
+ @Test
+ void testExampleWithAttributes() {
+ new TimedExample().exampleWithAttributes("attr1", 2, new TimedExample.ToStringObject());
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.attributes.duration")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ Long.valueOf(2)
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.longKey("custom_attr1")))
+ && TO_STRING.equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("custom_attr2")))
+ && "attr1"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("attribute1"))))));
+ }
+
+ @Test
+ void testExampleIgnore() throws Exception {
+ new TimedExample().exampleIgnore();
+ Thread.sleep(500);
+ assertThat(testing.metrics()).isEmpty();
+ }
+
+ @Test
+ void testExampleWithException() {
+ try {
+ new TimedExample().exampleWithException();
+ } catch (IllegalStateException e) {
+ // noop
+ }
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.exception.duration")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ IllegalStateException.class
+ .getName()
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("error.type"))))));
+ }
+
+ @Test
+ void testExampleWithReturnNameAttribute() {
+ new TimedExample().exampleWithReturnValueAttribute();
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.with.return.duration")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ TimedExample.TO_STRING.equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("returnValue"))))));
+ }
+
+ @Test
+ void testCompletableFuture() throws Exception {
+ CompletableFuture future = new CompletableFuture<>();
+ new TimedExample().completableFuture(future);
+
+ Thread.sleep(500); // sleep a bit just to make sure no metric is captured
+ assertThat(testing.metrics()).isEmpty();
+
+ future.complete("Done");
+
+ testing.waitAndAssertMetrics(
+ TIMED_INSTRUMENTATION_NAME,
+ metric ->
+ metric
+ .hasName("example.completable.future.duration")
+ .satisfies(
+ metricData ->
+ assertThat(metricData.getData().getPoints())
+ .allMatch(
+ p ->
+ "Done"
+ .equals(
+ p.getAttributes()
+ .get(AttributeKey.stringKey("returnValue"))))));
+ }
+}
diff --git a/instrumentation/reactor/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/v3_1/ReactorAsyncOperationEndStrategy.java b/instrumentation/reactor/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/v3_1/ReactorAsyncOperationEndStrategy.java
index 5e77ffffd9c8..2c44c5728a32 100644
--- a/instrumentation/reactor/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/v3_1/ReactorAsyncOperationEndStrategy.java
+++ b/instrumentation/reactor/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/v3_1/ReactorAsyncOperationEndStrategy.java
@@ -10,8 +10,8 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategy;
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import org.reactivestreams.Publisher;
@@ -44,7 +44,7 @@ public boolean supports(Class> returnType) {
@Override
public Object end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
Object asyncValue,
@@ -54,7 +54,7 @@ public Object end(
new EndOnFirstNotificationConsumer(context) {
@Override
protected void end(Object result, Throwable error) {
- instrumenter.end(context, request, tryToGetResponse(responseType, result), error);
+ handler.onEnd(context, request, tryToGetResponse(responseType, result), error);
}
};
diff --git a/instrumentation/rxjava/rxjava-2.0/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v2_0/RxJava2AsyncOperationEndStrategy.java b/instrumentation/rxjava/rxjava-2.0/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v2_0/RxJava2AsyncOperationEndStrategy.java
index caf78662f71b..308447014d0f 100644
--- a/instrumentation/rxjava/rxjava-2.0/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v2_0/RxJava2AsyncOperationEndStrategy.java
+++ b/instrumentation/rxjava/rxjava-2.0/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v2_0/RxJava2AsyncOperationEndStrategy.java
@@ -10,8 +10,8 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategy;
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
@@ -55,7 +55,7 @@ public boolean supports(Class> returnType) {
@Override
public Object end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
Object asyncValue,
@@ -65,7 +65,7 @@ public Object end(
new EndOnFirstNotificationConsumer(context) {
@Override
protected void end(Object response, Throwable error) {
- instrumenter.end(context, request, tryToGetResponse(responseType, response), error);
+ handler.onEnd(context, request, tryToGetResponse(responseType, response), error);
}
};
diff --git a/instrumentation/rxjava/rxjava-3-common/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v3/common/RxJava3AsyncOperationEndStrategy.java b/instrumentation/rxjava/rxjava-3-common/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v3/common/RxJava3AsyncOperationEndStrategy.java
index a6dc8fa69f1b..faea53fa947d 100644
--- a/instrumentation/rxjava/rxjava-3-common/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v3/common/RxJava3AsyncOperationEndStrategy.java
+++ b/instrumentation/rxjava/rxjava-3-common/library/src/main/java/io/opentelemetry/instrumentation/rxjava/v3/common/RxJava3AsyncOperationEndStrategy.java
@@ -10,8 +10,8 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategy;
-import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
@@ -55,7 +55,7 @@ public boolean supports(Class> returnType) {
@Override
public Object end(
- Instrumenter instrumenter,
+ AsyncOperationCallback handler,
Context context,
REQUEST request,
Object asyncValue,
@@ -65,7 +65,7 @@ public Object end(
new EndOnFirstNotificationConsumer(context) {
@Override
protected void end(Object response, Throwable error) {
- instrumenter.end(context, request, tryToGetResponse(responseType, response), error);
+ handler.onEnd(context, request, tryToGetResponse(responseType, response), error);
}
};
diff --git a/javaagent/build.gradle.kts b/javaagent/build.gradle.kts
index fb3ebe8cf449..9940b8a1d6cd 100644
--- a/javaagent/build.gradle.kts
+++ b/javaagent/build.gradle.kts
@@ -91,7 +91,8 @@ dependencies {
baseJavaagentLibs(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.50:javaagent"))
baseJavaagentLibs(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.52:javaagent"))
baseJavaagentLibs(project(":instrumentation:opentelemetry-instrumentation-api:javaagent"))
- baseJavaagentLibs(project(":instrumentation:opentelemetry-instrumentation-annotations-1.16:javaagent"))
+ baseJavaagentLibs(project(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-1.16:javaagent"))
+ baseJavaagentLibs(project(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-incubator:javaagent"))
baseJavaagentLibs(project(":instrumentation:executors:javaagent"))
baseJavaagentLibs(project(":instrumentation:internal:internal-application-logger:javaagent"))
baseJavaagentLibs(project(":instrumentation:internal:internal-class-loader:javaagent"))
diff --git a/opentelemetry-instrumentation-annotations-shaded-for-instrumenting/build.gradle.kts b/opentelemetry-instrumentation-annotations-shaded-for-instrumenting/build.gradle.kts
index 177ebb817e6d..83bc55719112 100644
--- a/opentelemetry-instrumentation-annotations-shaded-for-instrumenting/build.gradle.kts
+++ b/opentelemetry-instrumentation-annotations-shaded-for-instrumenting/build.gradle.kts
@@ -8,6 +8,7 @@ group = "io.opentelemetry.javaagent"
dependencies {
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")
+ implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations-incubator")
}
// OpenTelemetry Instrumentation Annotations shaded so that it can be used in instrumentation of
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a8aa7a7a51fd..0c31db544ea2 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -93,6 +93,7 @@ include(":bom-alpha")
include(":instrumentation-api")
include(":instrumentation-api-incubator")
include(":instrumentation-annotations")
+include(":instrumentation-annotations-incubator")
include(":instrumentation-annotations-support")
include(":instrumentation-annotations-support-testing")
@@ -460,7 +461,9 @@ include(":instrumentation:opentelemetry-api:opentelemetry-api-1.50:javaagent")
include(":instrumentation:opentelemetry-api:opentelemetry-api-1.52:javaagent")
include(":instrumentation:opentelemetry-extension-annotations-1.0:javaagent")
include(":instrumentation:opentelemetry-extension-kotlin-1.0:javaagent")
-include(":instrumentation:opentelemetry-instrumentation-annotations-1.16:javaagent")
+include(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-1.16:javaagent")
+include(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-common:javaagent")
+include(":instrumentation:opentelemetry-instrumentation-annotations:opentelemetry-instrumentation-annotations-incubator:javaagent")
include(":instrumentation:opentelemetry-instrumentation-api:javaagent")
include(":instrumentation:opentelemetry-instrumentation-api:testing")
include(":instrumentation:oracle-ucp-11.2:javaagent")