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).
If timeLimiter
isn't used, this is showing different segments of how much time is spent performing internal calls such as database etc:
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?
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
);