New Relic for Java (agent version 3.37 or higher) includes an API to instrument asynchronous activity. For supported frameworks, the agent usually instruments async work automatically. However, the async API can still be useful to add detail. This document provides examples of using tokens and segments to instrument your app.
The Java agent API provides two ways to trace asynchronous activity:
Tokens: Tokens are passed between threads to link asynchronous units of work to a specific transaction. They do not perform any timing directly.
Segments: Segments are used to measure an arbitrary piece of asynchronous application code, not necessarily associated with a method or thread. Segments are typically used to track external calls that are completed by a callback mechanism.
Tokens: Connect async threads
Use tokens to link arbitrary units of work that are on separate threads. This section describes how to use the token-related calls together to instrument async work. For detailed information on classes and methods, see the Javadoc.
To use tokens, you first need to create the token, then link another call to the originating transaction. You should link the token as soon as possible within the other call. If you do not link the token immediately, you risk losing any methods that contain an @Trace below the call you are trying to link. You can also expire the token in the original call. The Java agent will then link the work in the New Relic UI. These examples illustrate how to use the token-related calls together:
Consider the method parallelStream() in the code snippet below. Because some of the calls to requestItemAsync() will occur on a separate thread, a token is created and passed to link that asynchronous work back to the requesting thread.
/**
* Example showing multi-threaded implementation of requesting data using a parallel {@link Stream}.
.map(id ->requestItemAsync(id, token))// requestItemAsync represents an example of work being passed to another thread. The token is passed along to allow linking the work on the new thread back to the thread that the token was originally created on.
@Trace(dispatcher = true): Tells the agent to start a transaction. For more on this method, see the Javadoc.
getToken(): Creates the token which will link the work together. For more on this method, see the Javadoc.
token.expire(): Expires the token. This allows the transaction to end. For more on this method, see the Javadoc.
The following code example shows requestItemAsync, which might execute on a separate thread from the requesting thread. For this reason, the token that was created in the previous code example is linked to the Transaction in requestItemAsync. Note that requestItemAsync() has the @Trace(async=true) annotation, which tells the agent to trace this method if it's linked to an existing transaction.
After parallelStream() collects all results, the token is expired. This is important because it makes sure the transaction doesn't stay open after parallelStream() completes.
@Trace(async =true)
privateItemrequestItemAsync(long id,Token token){
token.link();
returnrequestItem(id);
}
The agent API calls in this sample are:
@Trace(async = true): Starts a transaction. For more on this method, see the Javadoc.
token.link(): Links the work being done in requestItemAsync() (which is executing on a different thread) to the requesting thread. For more on this method, see the Javadoc.
To view transaction trace details, go to: one.newrelic.com APM & services > (select an app) > Transactions > Transaction trace > Trace details.
Asynchronous activity is shown in the trace waterfall view by segments that overlap each other horizontally, in the time dimension.
It's not necessary to link methods that are on the same thread, but doing so will have no negative effect. It's often the case that a single token can be shared, like in the parallelStream() example.
Tip
By default, a transaction can create a maximum of 3000 tokens and each token has a default timeout of 180s. You can change the former limit with the token_limit config option and the latter with the token_timeout config option. Traces for transactions that exceed the token_limit will contain a token_clamp attribute. Increasing either config option may increase agent memory usage.
Segments: Time arbitrary async activity
Segments are used to measure an arbitrary piece of asynchronous application code, not necessarily associated with a method or thread. This is most commonly used to time connections to external services. Use segments if you want to:
Time code that completes via a callback
Time an asynchronous call that spans many methods
Measure the time between when work is created and when it executed (for example, in a thread pool)
The method below makes a call to an external service (in this case a database) using the method storeItem():
/**
* Example showing single threaded implementation of inserting data into a datasource.
The goal in this case it to find out how long the Callable in the Lambda statement is waiting in the thread pool before executing, rather than determining how long storeItem() runs. For this reason, a segment is used instead of a token, and @Trace(async = true) is not necessary like it was for a token.
The agent API call in this sample is:
@Trace(dispatcher = true): Starts a transaction. For more on this method, see the Javadoc.
The following code example shows a segment starting in the storeItem method to measure how long the Lambda statement is waiting in the thread pool. To stop timing the segment, you must call either .end() or .ignore(). If you don't want to report the segment as part of its parent transaction, call .ignore(). Otherwise, to report the segment as part of its parent transaction, call .end().
startSegment(...): Begins the segment that will time the code. For more on this method, see the Javadoc.
reportAsExternal(DatastoreParameters()): Associates the time with a datastore external call This will show up in APM with datastore data. For more information, see reportAsExternal API.
segment.end(): Stops timing this segment. For more on this method, see the Javadoc.
When the method completes, APM displays a transaction trace with one external call.
Tip
By default, the agent can track a maximum of 1000 segments during a given transaction. You can change this limit with the segment_timeout config option. Traces of transactions that exceed this limit will contain a segment_clamp attribute. Increasing this limit may increase agent memory usage.