Skip to content

Conversation

@Duncan-tree-zhou
Copy link

@Duncan-tree-zhou Duncan-tree-zhou commented May 14, 2024

refer to #7030
add metric annotation instrumentation

@Duncan-tree-zhou Duncan-tree-zhou requested a review from a team May 14, 2024 15:45
@steverao
Copy link
Contributor

There are some CI failures, firstly you can solve them by referring to https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/contributing/running-tests.md#troubleshooting-ci-test-failures

@Duncan-tree-zhou Duncan-tree-zhou marked this pull request as draft May 15, 2024 15:00
@Duncan-tree-zhou Duncan-tree-zhou force-pushed the add_metric_annotation_instrument branch 2 times, most recently from f1797c8 to f7dd6b9 Compare May 20, 2024 13:27
@Duncan-tree-zhou
Copy link
Author

Hi @steverao, I found that there are lots of fails "to connect to localhost/127.0.0.1:4318". So I want to rerun the task, but I can't see the rerun button. Is it disabled for contributors?

@steverao
Copy link
Contributor

Is it disabled for contributors?

Yes, you can fix the problems firstly and push relevant commits. It will trigger to rerun the CI tasks.

@Duncan-tree-zhou
Copy link
Author

Duncan-tree-zhou commented May 22, 2024

I mean I guest the failure is caused by the CI run time environment crush..... if it's able to rerun a task, It might save some time....

@Duncan-tree-zhou Duncan-tree-zhou force-pushed the add_metric_annotation_instrument branch 2 times, most recently from 815bbfd to 95f0f0d Compare May 24, 2024 14:29
@github-actions github-actions bot requested a review from theletterf May 29, 2024 16:36
@Duncan-tree-zhou Duncan-tree-zhou force-pushed the add_metric_annotation_instrument branch from f9657ec to 5ab650b Compare May 30, 2024 14:13
@Duncan-tree-zhou Duncan-tree-zhou marked this pull request as ready for review May 30, 2024 14:58
@Duncan-tree-zhou
Copy link
Author

Duncan-tree-zhou commented May 30, 2024

I turn it ready for reviewing, and get some questions for discuss:

  1. the module naming of the @Counted and @Timed instrumentation.
    • I avoid using the opentelemetry-instrumentation-annotations prefix because it's already been used for @Withspan. I am not sure if using opentelemetry-instrumentation-annotations-coutned would cause some problem somewhere.
    • I have no idea if there is a better module naming rule for metrics annotations instrumentations.
  2. I am not sure if we should put the instance of meter, LongCounter and DoubleHistogram in another module where can be loaded without javaagent?
  3. I am doubted about allowing the return value to be an attributes because most of time the return value is unpredictable. would it be possible to design some static api to allow user to post the attributes to the metrics via threadlocal?

@Duncan-tree-zhou Duncan-tree-zhou force-pushed the add_metric_annotation_instrument branch 5 times, most recently from 5024350 to 54866bf Compare June 9, 2024 04:27
@Duncan-tree-zhou Duncan-tree-zhou force-pushed the add_metric_annotation_instrument branch from 54866bf to 21cbf96 Compare June 9, 2024 04:30
@Override
public <REQUEST, RESPONSE> Object end(
Instrumenter<REQUEST, RESPONSE> instrumenter,
AsyncOperationCallback<REQUEST, RESPONSE> handler,
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for continuing of this.
I still have doubts about these async interfaces and how generic their names are.
This line is a perfect example of the problem. The interface is AsyncOperationCallback but we end up calling the attribute handler because it's more convenient but too abstract to actually mean anything in the code. What is it handling? business logic, telemetry?

If we cannot use the name of the interface as a name of an instance in the code, something is wrong.

On this comment #11354 (comment),
@Duncan-tree-zhou suggested AsyncEndHandler or AsyncCallback#onEnd and we ended up with AsyncOperationCallback#onEnd. In my mind I always ask: AsyncOperationCallback why? To where?

We also have AsyncOperationEndStrategy.java, AsyncOperationEndSupport

If we look at the package name:
io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationCallback, it already includes async.
Probably we need some brainstorm around these names and what they actually do. Imagine some newbie looking at code using this... At first glance it doesn't seem related with telemetry.

What if we use one of:

  • InstrumentationCallback
  • TelemetryCallback
  • TelemetryObserver

I think InstrumentationCallback better aligns with the current OTel practice and also answer: why we do the callback and to where.

Copy link
Author

@Duncan-tree-zhou Duncan-tree-zhou Sep 21, 2025

Choose a reason for hiding this comment

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

Thank you @brunobat for raising it again, I get your point. I think both InstrumentationCallback and TelemetryCallback work for me in the code context, it's clear for new comer to understand the usage of the interface. What do you think @laurit and @trask ?

Copy link
Member

Choose a reason for hiding this comment

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

how about changing the interface name and the method name:

public interface AsyncOperationCallback<REQUEST, RESPONSE> {
  void onEnd(
      Context context, REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error);
}

to

@FunctionalInterface
public interface AsyncOperationOnEnd<REQUEST, RESPONSE> {
  void accept(
      Context context, REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error);
}

and changing the name of the var here:

Suggested change
AsyncOperationCallback<REQUEST, RESPONSE> handler,
AsyncOperationOnEnd<REQUEST, RESPONSE> onEnd,

* <p>By default, the Counter instrument will have the following attributes:
*
* <ul>
* <li><b>code.namespace:</b> The fully qualified name of the class whose method is invoked.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm really looking forward for this to be merged. However I've noticed that code.namespace and code.function have been deprecated in the semantic conventions and has been replaced by code.function.name in the which is the fully qualified name. Should this be updated in this PR as well?

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for pointing out. the code.namespace and code.function are already replaced by code.function.name. check MetricsAnnotationHelper.java line 38.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds metric annotation instrumentation for @Counted and @Timed annotations, which allow developers to automatically record metrics for method invocations using annotations. The implementation also refactors the existing instrumentation-annotations module structure to better support code reuse between different annotation types.

Key Changes:

  • Introduces new incubator annotations (@Counted, @Timed, @Attribute, @ReturnValueAttribute, @StaticAttribute) for declarative metrics instrumentation
  • Restructures opentelemetry-instrumentation-annotations modules into a hierarchical structure with common, 1.16, and incubator submodules
  • Introduces AsyncOperationCallback interface to decouple async operation handling from Instrumenter dependency

Reviewed changes

Copilot reviewed 41 out of 56 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
settings.gradle.kts Adds new module includes for restructured instrumentation-annotations hierarchy
javaagent/build.gradle.kts Updates dependency paths for restructured modules
opentelemetry-instrumentation-annotations-shaded-for-instrumenting/build.gradle.kts Adds dependency on new annotations-incubator module
instrumentation-annotations-incubator/src/main/java/.../Counted.java Defines @Counted annotation for counter metrics on methods
instrumentation-annotations-incubator/src/main/java/.../Timed.java Defines @Timed annotation for histogram metrics measuring duration
instrumentation-annotations-incubator/src/main/java/.../Attribute.java Defines @Attribute for marking method parameters as metric attributes
instrumentation-annotations-incubator/src/main/java/.../ReturnValueAttribute.java Defines annotation for capturing return value as metric attribute
instrumentation-annotations-incubator/src/main/java/.../StaticAttribute.java Defines annotation for adding static attributes to metrics
.../incubator/CountedInstrumentation.java Implements bytecode instrumentation for @Counted annotation
.../incubator/CountedHelper.java Helper class for recording counter metrics
.../incubator/TimedInstrumentation.java Implements bytecode instrumentation for @Timed annotation
.../incubator/TimedHelper.java Helper class for recording histogram metrics
.../incubator/MetricsAnnotationHelper.java Shared base class for metric annotation helpers
.../incubator/MetricAttributeHelper.java Helper for extracting and binding metric attributes
.../common/javaagent/MethodRequest.java Common data class holding method and arguments
.../common/javaagent/KotlinCoroutineUtil.java Utility for detecting Kotlin suspend methods
.../common/javaagent/AnnotationExcludedMethods.java Configuration-based method exclusion matcher
.../annotations-1.16/.../WithSpanInstrumentation.java Refactored @WithSpan instrumentation using common utilities
.../annotations-1.16/.../AddingSpanAttributesInstrumentation.java Refactored @AddingSpanAttributes instrumentation
.../async/AsyncOperationCallback.java New interface for async operation completion callbacks
.../async/AsyncOperationEndStrategy.java Updated to support both Instrumenter and AsyncOperationCallback
.../async/AsyncOperationEndSupport.java Refactored to use AsyncOperationCallback
.../async/Jdk8AsyncOperationEndStrategy.java Updated for AsyncOperationCallback support
.../support/MethodBinder.java New helper for binding method parameters and return values to attributes
rxjava-3-common/.../RxJava3AsyncOperationEndStrategy.java Updated to use AsyncOperationCallback
rxjava-2.0/.../RxJava2AsyncOperationEndStrategy.java Updated to use AsyncOperationCallback
reactor-3.1/.../ReactorAsyncOperationEndStrategy.java Updated to use AsyncOperationCallback
guava-10.0/.../GuavaAsyncOperationEndStrategy.java Updated to use AsyncOperationCallback
kotlinx-coroutines/.../FlowUtil.kt Updated to use AsyncOperationCallback
instrumentation-annotations/README.md Updated documentation to mention @Counted and @Timed

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 41 out of 56 changed files in this pull request and generated 5 comments.

static void addStaticAttributes(Method method, AttributesBuilder attributesBuilder) {
attributesBuilder.put(
CodeAttributes.CODE_FUNCTION_NAME,
method.getDeclaringClass().getName() + "." + method.getName());
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider using method.getDeclaringClass().getCanonicalName() instead of getName() to provide a more readable fully qualified class name, especially for nested classes where getName() returns names with $ separators.

Suggested change
method.getDeclaringClass().getName() + "." + method.getName());
method.getDeclaringClass().getCanonicalName() + "." + method.getName());

Copilot uses AI. Check for mistakes.
@Test
void testExampleIgnore() throws Exception {
new TimedExample().exampleIgnore();
Thread.sleep(500);
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

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

Using Thread.sleep() for timing assertions can lead to flaky tests. Consider using test utilities that wait for conditions with timeouts instead of fixed sleep durations.

Copilot uses AI. Check for mistakes.
@Test
void testExampleIgnore() throws Exception {
new CountedExample().exampleIgnore();
Thread.sleep(500); // sleep a bit just to make sure no metric is captured
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

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

Using Thread.sleep() for timing assertions can lead to flaky tests. Consider using test utilities that wait for conditions with timeouts instead of fixed sleep durations.

Copilot uses AI. Check for mistakes.
CompletableFuture<String> future = new CompletableFuture<>();
new CountedExample().completableFuture(future);

Thread.sleep(500); // sleep a bit just to make sure no metric is captured
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

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

Using Thread.sleep() for timing assertions can lead to flaky tests. Consider using test utilities that wait for conditions with timeouts instead of fixed sleep durations.

Copilot uses AI. Check for mistakes.
CompletableFuture<String> future = new CompletableFuture<>();
new TimedExample().completableFuture(future);

Thread.sleep(500); // sleep a bit just to make sure no metric is captured
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

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

Using Thread.sleep() for timing assertions can lead to flaky tests. Consider using test utilities that wait for conditions with timeouts instead of fixed sleep durations.

Copilot uses AI. Check for mistakes.
* <li><b>error.type:</b> This is only present if an Exception is thrown, and contains the {@link
* Class#getName name} of the Exception class.
* </ul>
*
Copy link
Member

Choose a reason for hiding this comment

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

can you document what the default is for buckets?

Comment on lines +16 to +18
// 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.
Copy link
Member

Choose a reason for hiding this comment

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

I'd suggest add a test that just loads and calls those example usages. It will essentially be a no-op test, but that's ok.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants