Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next attempt to extract common logic from instrumentations. #523

Merged
merged 7 commits into from
Jun 17, 2020

Conversation

iNikem
Copy link
Contributor

@iNikem iNikem commented Jun 15, 2020

I tried to reduce code repetition in DB client instrumentations, jdbc and mongo ones.

@trask please take a look at io.opentelemetry.auto.instrumentation.jdbc.JDBCDecorator#startSpan method. I currently don't see a good way (without repetitive code) to avoid returning null from it.

I tried to reduce code repetition in DB client instrumentations, jdbc and mongo ones. This time I experimented with another approach, different from HttpServerTracer. Namely, I have extracted that common `startSpan` method into `DatabaseClientDecorator` itself. The idea is still the same as before, but I wanted to see if we can migrate by smaller steps, reusing much of the existing code and just incrementally reducing public API.
@iNikem iNikem marked this pull request as draft June 15, 2020 15:28
@iNikem iNikem marked this pull request as ready for review June 16, 2020 05:19
@trask
Copy link
Member

trask commented Jun 17, 2020

I think it's safer for the CallDepth thread local management to be more explicit, for similar reasons as for the scope thread local management (that it's easy to miss decrementing/closing under all conditions).

Also this would keep the CallDepth story consistent whether it's sync or async (as opposed to resetting the CallDepth inside of TRACER.end(span) which only works if it's sync).

What do you think about the template below for advice that needs to track CallDepth?

The template makes sure we always increment/decrement the CallDepth first thing, making sure we never miss that.

It also passes CallDepth between @OnMethodEnter/@OnMethodExit which saves a thread local lookup in @OnMethodExit which is nice.

It would require adding a few new methods to the CallDepthThreadLocalMap API, but the existing methods could stay and we could migrate over slowly.

The point of TRACER.getCallDepth() below is just to encapsulate the call depth key inside of the Tracer, instead of calling CallDepthThreadLocalMap.getCallDepth(Statement.class) directly from the advice.

  @Advice.OnMethodEnter
  public static CallDepth onEnter(
      ...
      @Advice.Local("otelSpan") Span span,
      @Advice.Local("otelScope") Scope scope) {

    CallDepth callDepth = TRACER.getCallDepth();
    if (callDepth.increment() && TRACER.shouldCreateSpan(...)) {
      span = TRACER.startSpan(...);
      scope = TRACER.withSpan(span);
    }
    return callDepth;
  }
  @Advice.OnMethodExit
  public static void onExit(
      ...
      @Advice.Thrown Throwable throwable,
      @Advice.Local("otelSpan") Span span,
      @Advice.Local("otelScope") Scope scope,
      @Advice.Enter CallDepth callDepth) {

    if (depth.decrement() && scope != null) {
      scope.close();
      if (throwable == null) {
        TRACER.end(...);
      } else {
        TRACER.endExceptionally(...);
      }
    }
  }

This doesn't solve the problem in this PR of returning null from startSpan, but i'm realizing that doesn't bother me so much, and i can see the value in this PR, in which case maybe we should change the general @OnMethodEnter template above to remove the shouldCreateSpan method and integrate that decision into startSpan, e.g.

  @Advice.OnMethodEnter
  public static CallDepth onEnter(
      ...
      @Advice.Local("otelSpan") Span span,
      @Advice.Local("otelScope") Scope scope) {

    CallDepth callDepth = TRACER.getCallDepth();
    if (callDepth.increment()) {
      span = TRACER.startSpan(...);
      if (span != null) {
        scope = TRACER.withSpan(span);
      }
    }
    return callDepth;
  }

@iNikem
Copy link
Contributor Author

iNikem commented Jun 17, 2020

First, how does returning null not bother you any more? :) This essentially rollbacks your #499 .

Second, it is an open question for me, btw, which instrumentations require this CallDepth management? Looks like we have 2 similar mechanism: CallDepth and the one used by HttpServer instrumentations (attaching span to requests). Is there some hidden abstraction?

Third, what is the difference between span, scope and callDepth? Why we store two of them in local variables but return the third?

Forth, 😭 for so much copy-paste.

Fifth, yeah, that may work :)

@trask
Copy link
Member

trask commented Jun 17, 2020

First, how does returning null not bother you any more? :) This essentially rollbacks your #499 .

I didn't say it doesn't bother me at all 😂

Second, it is an open question for me, btw, which instrumentations require this CallDepth management? Looks like we have 2 similar mechanism: CallDepth and the one used by HttpServer instrumentations (attaching span to requests). Is there some hidden abstraction?

CallDepth is primarily needed when dealing with overloaded methods that delegate to each other, in order to prevent duplicate spans. It doesn't work for HttpServer instrumentation b/c in that case the calls are across different threads.

We can probably reduce the number of places that CallDepth tracking is needed once we implement #465.

Third, what is the difference between span, scope and callDepth? Why we store two of them in local variables but return the third?

Good question, let's make them all @Advice.Local parameters.

Forth, 😭 for so much copy-paste.

I think it's a good compromise, but let's see how it looks as we start spreading it across various instrumentation, we can always revisit.

Fifth, yeah, that may work :)

👍

Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

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

i like this, minor suggestions below

Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

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

👍

@trask trask merged commit c1c02ac into open-telemetry:master Jun 17, 2020
@iNikem iNikem deleted the db-client-decorator branch June 17, 2020 19:26
iNikem added a commit to iNikem/opentelemetry-java-instrumentation that referenced this pull request Jun 19, 2020
…emetry#523)

* Next attempt to extract common logic from instrumentations.

I tried to reduce code repetition in DB client instrumentations, jdbc and mongo ones. This time I experimented with another approach, different from HttpServerTracer. Namely, I have extracted that common `startSpan` method into `DatabaseClientDecorator` itself. The idea is still the same as before, but I wanted to see if we can migrate by smaller steps, reusing much of the existing code and just incrementally reducing public API.

* Extracted separate Tracer after all

* More explicit call depth handling

* Fix format

* More reusable method overloads
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.

2 participants