This blog post is the third in a series of posts on using unsupported functionality in the Oracle JDK and/or Java Mission Control.
I will use the JMC flight recorder parser in this example, and since the JMC parser is unsupported I’ll just go ahead and use my 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.
I strongly recommend against using these APIs in production code. There will be supported APIs for reading reading recordings eventually and they may be quite different to what I show here. Also note that older versions of the parsers are not guaranteed to be forward compatible with newer versions of the binary format. In short, this is NOT supported and WILL CHANGE!
You may want to first read the introduction to the JFR parsers and about the relational key before continuing.
Ready? Here we go…
A Flight Recording
First off we need a recording to parse. If you have your own favourite recording, you’re all set. If you don’t you can have mine. The recording I linked to is a recording of WLS running the MedRec example app with some load. The benefit of using that recording is that it, aside from the standard events, also contains events provided by the Java API for creating third party events (by the WebLogic Diagnostics Framework). Quite useful events, all bound together using the relational key in various ways. Don’t forget to unzip the recording before use.
The recording is pretty new and really requires a JMC 5.3.0 with the WLS plug-in for the best result. JMC 5.3.0 has not been released yet, but don’t worry; this blog entry will not even require you to start JMC.
That said, since all blog posts require a pretty picture, here is one of the servlet invocations in the recording:
Looking at Metadata
Let’s first write a little program that lists the available event types and their metadata. Use the knowledge obtained in the JFR parser blog entry to obtain a copy of the JMC Flight Recorder Parser.
Next, use the example below to iterate through the metadata and print out the event type name, all the attributes and the relational key for the the individual attributes:
import java.io.File;
import java.util.Collection;
import com.jrockit.mc.flightrecorder.FlightRecording;
import com.jrockit.mc.flightrecorder.FlightRecordingLoader;
import com.jrockit.mc.flightrecorder.spi.IEventType;
import com.jrockit.mc.flightrecorder.spi.IField;
public class FlightRecorderMetaData {
public static void main(String[] args) throws ClassNotFoundException {
FlightRecording recording = FlightRecordingLoader.loadFile(new File("C:\\demo\\wldf.jfr"));
for (IEventType type : recording.getEventTypes()) {
System.out.println(type.getName());
printAttributes(type.getFields());
}
}
private static void printAttributes(Collection<IField> fields) {
for (IField field : fields) {
System.out.println(String.format(" %s (relkey: %s)", field.getName(), field.getRelationalKey()));
}
}
}
Here is a slightly more convoluted example, printing everything out in a nice tree, also showing how many events were found per event type plus some additional stats. It takes a flight recording as its only argument.
Using the Relational Key
Looking at the metadata for the WLDF recording, it can be seen that a lot of the event attributes provided with the WLDF specific events are actually using the relational key for various different things. There is a relational key for servlet URIs, there is another one for the ECID (Enterprise Context ID) and so on. Let’s say we wanted to find all the events with a relational key for ECID, and then group them on ECID (effectively grouping the events per transaction). That could look something like this:
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.jrockit.mc.flightrecorder.FlightRecording;
import com.jrockit.mc.flightrecorder.FlightRecordingLoader;
import com.jrockit.mc.flightrecorder.spi.IEvent;
import com.jrockit.mc.flightrecorder.spi.IEventType;
import com.jrockit.mc.flightrecorder.spi.IField;
import com.jrockit.mc.flightrecorder.spi.IView;
public class FlightRecorderECID {
private static final String KEY_ECID = "http://www.oracle.com/fmw/ECID";
public static void main(String[] args) throws ClassNotFoundException {
FlightRecording recording = FlightRecordingLoader.loadFile(new File("C:\\demo\\wldf.jfr"));
Collection<IEventType> ecidTypes = new LinkedList<>();
for (IEventType type : recording.getEventTypes()) {
if (type.getPath().startsWith("wls/")) { // just optimization, can remove
if (hasECID(type)) {
ecidTypes.add(type);
}
}
}
IView ecidView = recording.createView();
ecidView.setEventTypes(ecidTypes);
Map<String, List<IEvent>> eventMap = buildEventMap(ecidView);
System.out.println("ECIDS:");
for (Entry<String, List<IEvent>> ecid : eventMap.entrySet()) {
System.out.println(String.format("%s [%d events]", ecid.getKey(), ecid.getValue().size()));
}
}
private static Map<String, List<IEvent>> buildEventMap(IView ecidView) {
Map<String, List<IEvent>> map = new HashMap<>();
for (IEvent event : ecidView) {
String ecid = getEcid(event);
if (ecid != null) {
addToMap(map, ecid, event);
}
}
return map;
}
private static void addToMap(Map<String, List<IEvent>> map, String ecid, IEvent event) {
List<IEvent> eventList = map.get(ecid);
if (eventList == null) {
eventList = new ArrayList<>();
map.put(ecid, eventList);
}
eventList.add(event);
}
private static String getEcid(IEvent event) {
IField ecidField = getEcidField(event.getEventType());
if (ecidField == null) {
return null;
}
return String.valueOf(ecidField.getValue(event));
}
private static boolean hasECID(IEventType type) {
return getEcidField(type) != null;
}
private static IField getEcidField(IEventType type) {
for (IField field : type.getFields()) {
if (field.isRelational() && KEY_ECID.equals(field.getRelationalKey())) {
return field;
}
}
return null;
}
}
The output should look something like this:
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-00000ed0 [1 events]
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-00000237 [45 events]
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-00000236 [2 events]
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-00000d58 [2 events]
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-0000022a [2 events]
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-0000022d [2 events]
8ec006a7-30e9-4fac-be7f-716f42d3cbc8-0000022c [12 events]
Running the same example but using the relational key for URI (http://www.oracle.com/wls/Servlet/servletName) will look something like this:
/console/index.jsp [10 events]
/_async/AsyncResponseService [195 events]
/medrec/registerPatient.action [90 events]
/physician-web/physician/addPrescription.action [448 events]
/console/images/button_bg_mo.png [7 events]
/physician-web/physician/viewPatients.action [432 events]
/medrec/admin/home.action [48 events]
/console/jsp/changemgmt/ChangeManager.jsp [9 events]
/physician-web/physician/createRecord.action [720 events]
/medrec-jaxws-services/PatientFacadeService [275 events]
/medrec-jaxrpc-services/JaxRpcRecordCreationFacadeBroker [235 events]
Conclusion
This blog provided some examples on how to read out metadata from a flight recording and a simple example showing the use of the relational key.