Recording JMX data into JFR

I recently saw a question on Stack Overflow regarding recording JMX data into JFR. I proposed that one could use the JFR Java API to record MBean attributes, but I realized I never did a blog post on dynamic events. So this blog post will be about dynamic JFR event generation, and how to write a configurable agent which you can use to periodically record declared JMX attributes into the Flight Recorder.

If you couldn’t care less about the implementation details, you can just download the agent jar and the example configuration file at the end of this article. 😉

And here is the usual disclaimer:

The following blog entry will describe UNSUPPORTED functionality. This means that relying on the described APIs or functionality may BREAK your code/plugin with any given update of the JDK and/or Mission Control.

 

Dynamic JFR Events

The JFR event API can be very useful to record custom events into the Java Flight Recorder. But what do you do when you do not know beforehand what fields the event needs to contain? Or if you wish to programmatically generate event types based on some input?

Dynamic JFR events to the rescue. You will, as usual, need a Producer:

Producer p = new Producer("Dynamic Event Example", "This example will generate a dynamic event over and over", "http://www.hirt.se/example/dynamic/");

Next you need to create your DynamicEventToken. This token is what you will later use to create your events.

token = p.createDynamicInstantEvent("Dynamic Event Name", "Dynamic event description", "dynamic/path", true, true,
    new DynamicValue("rndstr", "Random String", "Contains a random string", ContentType.None, String.class),
    new DynamicValue("fraction", "Random Fraction", "Contains a random fraction between 0 and 1", ContentType.Percentage, Float.TYPE),
    new DynamicValue("timestamp", "Timestamp", "Contains a timestamp", ContentType.Timestamp, Long.TYPE));

Don’t forget to register the producer! Also note that there is one DynamicValue per attribute to record. The ContentType is used to provide the data consumer (usually JMC) with a hints for helpful visualization of the data. Check out the jmxprobes.xml file for more information on the ContentTypes.

Here is the full source for an example recording dynamic events of the same event type, but different payloads, over and over again:

package se.hirt.jmx2jfr;

import java.util.Random;

import com.oracle.jrockit.jfr.ContentType;
import com.oracle.jrockit.jfr.DynamicEventToken;
import com.oracle.jrockit.jfr.DynamicValue;
import com.oracle.jrockit.jfr.InstantEvent;
import com.oracle.jrockit.jfr.Producer;

@SuppressWarnings("deprecation")
public class DynamicExample implements Runnable {
	private static final Random RND = new Random();
	private final DynamicEventToken token;

	public DynamicExample() throws Exception {
		Producer p = new Producer("Dynamic Event Example", "This example will generate a dynamic event over and over", "http://www.hirt.se/example/dynamic/");
		token = p.createDynamicInstantEvent("Dynamic Event Name", "Dynamic event description", "dynamic/path", /* record thread */ true, /* record stack trace */true,
				new DynamicValue("rndstr", "Random String", "Contains a random string", ContentType.None, String.class), 
				new DynamicValue("fraction", "Random Fraction", "Contains a random fraction between 0 and 1", ContentType.Percentage, Float.TYPE),
				new DynamicValue("timestamp", "Timestamp", "Contains a timestamp", ContentType.Timestamp, Long.TYPE));
		p.register();
	}

	public void run() {
		while (true) {
			InstantEvent e = token.newInstantEvent();
			e.setValue("rndstr", randomString());
			e.setValue("fraction", RND.nextDouble());
			e.setValue("timestamp", System.currentTimeMillis());
			e.commit();
			sleep(1000);
		}
	}
	
	public final static void main(String[] args) throws Exception {		
		Thread t = new Thread(new DynamicExample(), "Event generation thread");
		t.setDaemon(true);
		t.start();
		System.out.println("Press enter to exit!");
		System.in.read();
	}
	
	private static String randomString() {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < RND.nextInt(10) + 1; i++) {
			builder.append(Character.toString((char) (RND.nextInt(26) + 64)));
		}
		return builder.toString();
	}
	
	private void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
		}
	}
}

To run the example, especially if on a JDK prior to 8u40, remember to start with:

-XX:+UnlockCommercialFeatures –XX:+FlightRecorder

To make a recording, start JMC and hook up to the running process. Don’t forget to enable the events!

enable_dyn_events

If you do a 10 second recording, you should have recorded about 10 events. Notice that properly setting the content type allows JMC to present the information in a better way. See, for example, the timestamp field:

generatedevents

 

 

Using the Agent

The agent allows you to configure and record JMX data into JFR without having to change any code. There is a small test snippet that you can run in the agent jar to try it out (download the agent and the settings file from the end of this article):

java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -javaagent:jmx2jfr.jar=jmxprobes.xml -cp jmx2jfr.jar se.hirt.jmx2jfr.test.HelloWorldTest

Next hook up to the HelloWorldTest process from JMC and create a 10s recording. Don’t forget to enable the MBean events:

enablembeanevents

When the recording finishes, go to the Events | Log tab. Select to only see the MBean events in the Event Types view, and select an event. You will see that MBean data has been recorded from the platform MBean server.

mbeanslog

Use the configurations file to configure what MBeans and attributes to record, and what content types to use for the different attributes. There will be one Event Type per MBean, filled with whatever attributes you have selected:

<jmx2jfr>
        <!-- Time to wait before retrying getting an MBean attribute in ms -->
	<retrytime>60000</retrytime>
	
	<!-- Time to wait until starting in ms --> 
	<delay>10000</delay>
	
	<!-- How often to get the attributes and emit events for them, in ms -->
	<period>1000</period>
	
	<!-- clean: Use name or type keys for event name. Leads to cleaner event 
	     type names, but can result in name collisions if you have MBeans with 
	     the same name in the same domain.
	     canonical: ugly event type names, but guaranteed not to collide -->
	<namingmode>clean</namingmode>
	
	<!-- objectname:    MBean name - look it up in JMC
	     attributename: Attribute name - look it up in JMC
	     contenttype:   [None | Bytes | Timestamp | Millis | Nanos | Ticks | Address] -->
	<attributes>
		<attribute>
			<objectname>java.lang:type=OperatingSystem</objectname>
			<attributename>FreeSwapSpaceSize</attributename>
			<contenttype>Bytes</contenttype>
		</attribute>
		<attribute>
			<objectname>java.lang:type=OperatingSystem</objectname>
			<attributename>TotalSwapSpaceSize</attributename>
			<contenttype>Bytes</contenttype>
		</attribute>
...

This should be fairly self explanatory. Edit the file to get the MBean attributes that you are interested in.

Summary

TL;DR – agent to easily record data from the platform MBean server into the Flight Recorder. Just edit the xml settings file, add the agent to the command line and point it to your edited settings file.

Downloads: