Search code examples
javamultithreadingplayframeworknewrelic

Tracing transaction details with New Relic when passed to another thread


We are using New Relic Java agent with Play framework and having some troubles with tracking the transaction segments / details when the execution is passed on to another thread.

Consider this code:

public class SomeController {
   private final com.google.common.util.concurrent.TimeLimiter timeLimiter = SimpleTimeLimiter.create(
       Executors.newCachedThreadPool(
           new ThreadFactoryBuilder()
               .setNameFormat("SomePrefix-%d")
               .setDaemon(true)
               .build()
       )
   );

   @Trace(async = true)
   private Result someMethodInternal(Http.Request request, com.newrelic.api.agent.Token token) {
      token.link();
      ... business logic here ...
   }

   public Result someMethod(Http.Request request) {
      var token = NewRelic.getAgent().getTransaction().getToken();
      try {
         return timeLimiter.callWithTimeout(
            () -> someMethodInternal(token),
            1L, TimeUnit.MINUTES
         );
      } finally {
         token.expire();
      }
   }

}

The someMethod transaction, when viewed in New Relic is visible as follows (notice the absence of segments / details).

Without segment breakdown

If timeLimiter isn't used, this is showing different segments of how much time is spent performing internal calls such as database etc:

With segment breakdown

The documentation of NewRelic's Token::link suggests @Trace(async=true) is required for internal method which is present in the above code.

TimeLimiter has its own thread which controls the execution but to me it seems it should be fine as we're passing the token correctly.

What else could be missing here?


Solution

  • Try removing the token.expire() in the finally block of someMethod, and change the token.link() in someMethodInternal to token.linkAndExpire().

    When you expire a token, it can no longer be used for linking threaded code. And the finally block is most likely executing before someMethodInternal.

    You are correct that you need the @Trace(async = true).

    When you don't use the TimeLimiter the code executes in a single thread, and the agent is able to track all the activity. Now,TimeLimiter is not supported, and it moves the execution to a different thread. When this happens the agent is not informed and thus do not track the activity.

    A reusable way to make this work is to create a wrapper for your runnable.

    public class TokenRunnable implements Runnable {
      private Token token;
      private Runnable runnable;
    
      public TokenRunnable(Runnable runnable) {
        this.runnable = runnable;
        this.token = NewRelic.getAgent().getTransaction().getToken();
      }
    
      @Trace(async = true)
      public void run() {
        token.linkAndExpire();
    
    
        // might want to add null check on runnable
    
        // this sets the name of the segment to your runnable class
        NewRelic.getAgent().getTracedMethod().setMetricName("Java", runnable.getClass().getName(), "run"); 
    
        this.runnable.run();
      }
    }
    

    and then don't need the @Trace nor the token link, just call new TokenRunnable(...)

    timeLimiter.callWithTimeout(
      new TokenRunnable(() -> someMethodInternal(token)),
      1L, TimeUnit.MINUTES
    );