Since version 7u4 of the Hotspot JDK, there is a Java API for adding flight recording events to the Java Flight Recorder (JFR). Using this API to add your own event producers is quite unsupported, but very useful. It can, for instance, be used to add application specific events, such as a transaction event, to provide a context for all the other events being recorded by the flight recorder.
Now, in the interest of full disclosure, the API entered the Hotspot code base as @deprecated, since:
- It is in the com.oracle.jrockit namespace.
- The API will eventually be superceded by an improved “2.0” version.
So, the API is deprecated and unsupported. It is subject to change without notice. Don’t come complaining if it changes radically between releases! That being said, since the API is in use by WLS and others, it will likely remain available in its current form for the foreseeable future.
This article will show how user defined events can be put into the Flight Recorder. More specifically, how to:
- Add a new event producer,
- add a new event type, and,
- record events of the newly created event type.
In later articles I will show different ways to dump data from the flight recorder, different ways to get programmatic access to the events, as well as how to use the Mission Control client to analyse your events and how to build custom user interfaces in Mission Control for them.
Step 1 – Adding a Producer
To record events you need to add metadata about your event producer. As with all metadata, it is important to spend some time to ensure that the metadata is correct and useful. In my example we will create an event to track the trading of Smurfberries among the Smurfs on the fictional Smurfberry Exchange market. (Yes, I do know that Smurfs are fictional too, but please don’t tell my daughter.)
Here is a picture of papa smurf carrying smurfberries, for reference:
To create your producer you need to do something along the following:
private static final String PRODUCER_URI = "http://www.smx.com/smx/"; private static final Producer PRODUCER; static { PRODUCER = createProducer(); } private static Producer createProducer() { try { return new Producer("Smurfberry Exchange producer", "A demo event producer for the fictional Smurfberry Exchange.", PRODUCER_URI); } catch (Exception e) { // Add proper exception handling. e.printStackTrace(); } return null; }
Step 2 – Adding Event Type(s)
Next we declare the event types to be produced by our producer. In our case we want an event to be generated for each Smurfberry transaction.
We need to:
- Create our new event class.
- Register our new event type with the producer.
There are several different types of events that we can create.
- Duration events
These are events that last over a period of time, or, in other words, events that have a start and end time. The Garbage Collection event is an example of a duration event. - Timed events
These are events for which a threshold can be configured in the recording engine. If the event duration is less than the threshold, the event will not be recorded. Java Sleep and Java Wait events are examples of timed events. - Instant events
These events only have the time when the event took place. The Exception and Event Settings Changed are examples of instant events. - Requestable events
These events can be configured to be polled periodically by the recording engine. The event implements a callback. An example of a requestable event is the CPU Load Sample event.
Conceptually Duration and Timed events are very similar, and from now on I will interchangebly refer to them collectively as duration events. Duration events are among the most useful ones, since they allow us to time things going on in our application. Since we want to time the transactions, we will use a duration event in the example. And since we want the end user creating a recording of our system to be able to decide how slow transactions to record, we will be using a TimedEvent.
Creating the event is easily done by subclassing the TimedEvent class, and annotating the class with the metadata for the event:
import com.oracle.jrockit.jfr.ContentType; import com.oracle.jrockit.jfr.EventDefinition; import com.oracle.jrockit.jfr.EventToken; import com.oracle.jrockit.jfr.TimedEvent; import com.oracle.jrockit.jfr.ValueDefinition; @EventDefinition(path="smx/transaction", name = "SMX Transaction", description="An Smurfberry Exchange transaction.", stacktrace=true, thread=true) public class TransactionEvent extends TimedEvent { @ValueDefinition(name="Price", description="The price at which the transaction was made.", contentType=ContentType.None) private float price; @ValueDefinition(name="Quantity", description="The quantity of smurfberries transferred.", contentType=ContentType.None) private int quantity; public TransactionEvent(EventToken eventToken) { super(eventToken); } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } }
A few notes on the code above:
- The path is where the event will be “mounted” in the overall EventType tree. Choose carefully.
- The metadata will be used by all users to understand your event. Take care to explain your event properly.
- The metadata about stacktrace and thread lets flightrecorder know that we want to capture the stacktrace from where the event is originating and thread information.
- In this case, no appropriate content types are available, but if you are exposing things like time in nanos, bytes, Class names or similar, use the correct content type, and you will get a lot for free in the user interfaces for analysing your events.
- If you have a field that will be shared among several events, and where the value can be used to join events from the different types, the field should also have a relational key. A relation key is a unique URI signifying a relationship between different events based on the values of specific fields, and work much like keys in a relational database. While these relations have no significance in the running application, they tell a renderer of flight recorder data that certain events should be associated and grouped together, typically to form an event chain. Here is an example:
@ValueDefinition(name="transactionId", description="The transaction id for a fictive transaction.", contentType=ContentType.None, relationKey="http://example.com/myserver/transactionId") private String transactionId;
Next we need to register the event type, and our producer, propely. Here is the extended code from Example Snippet 1:
private final static String PRODUCER_URI = "http://www.smx.com/smx/"; private static final Producer PRODUCER; private static final EventToken TRANSACTION_EVENT_TOKEN; static { PRODUCER = createProducer(); TRANSACTION_EVENT_TOKEN = createToken(); PRODUCER.register(); } /** * Creates our producer. */ private static Producer createProducer() { try { return new Producer("Smurfberry Exchange producer", "A demo event producer for the fictional Smurfberry Exchange.", PRODUCER_URI); } catch (Exception e) { // Add proper exception handling. e.printStackTrace(); } return null; } /** * Creates our event token. */ private static EventToken createToken() { try { return PRODUCER.addEvent(TransactionEvent.class); } catch (Exception e) { // Add proper exception handling. e.printStackTrace(); } return null; }
Step 3 – Recording Events
Now we can start recording the smurfy transactions! It is quite simple – for a duration event, create the event, set the properties, end the event and then commit it to the flight recorder:
public void doTransaction(...) { TransactionEvent event = new TransactionEvent(SmurfBerryExchange.TRANSACTION_EVENT_TOKEN); event.begin(); doWork(); float price = getPrice(); int quantity = getQuantity(); event.setPrice(price); event.setQuantity(quantity); event.end(); event.commit(); }
That’s all there is to it. If you know you’re thread safe, you can dispens with the event object creation and simply reuse the event like this:
private TransactionEvent event = new TransactionEvent(SmurfBerryExchange.TRANSACTION_EVENT_TOKEN); public void doTransaction(...) { event.reset(); event.begin(); doWork(); float price = getPrice(); int quantity = getQuantity(); event.setPrice(price); event.setQuantity(quantity); event.end(); event.commit(); }
Troubleshooting
If you can’t get it to work, you may want to check the following:
- In JRockit the flight recorder was always enabled. Not so in Hotspot. Don’t forget to enable the flight recorder: -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
- You need a 7u4 or later to use this with a Hotspot JDK.
- Did you forget to keep a reference to the Producer?
- Once you start recording your new events, you need to enable them. Easiest way is to setup a template in Mission Control (5.0 and later) and use that template for your recordings. If you are using pre-7u12 JVMs the server and client side templates are different, and you will need another type of template for jcmd to work. In 7u12 (JMC 5.2) and later they are the same.
For more information on using the Flight Recorder, see the following book.