How I generate the JavaFlight Recorder Docu


From a tweet von Gunnar Morling

I learned today that my work on JFR-Doc is used for JfrUnit. In a follow up Gunnar asked me how those JSON-Files are generated and I promised to write up a blog what I hacked together in an evening to get this going.

Get the input

The first and most important thing is to get all JFR-Events (those commands are executed eg in your Java-17-Install-Dir)

./java -XX:StartFlightRecording:filename=/tmp/out-bin.jfr \
-version # Dump JFR Data
./jfr metadata /tmp/out-bin.jfr > /tmp/openjdk-17.jfr

The final file holds content like this

class boolean {
}
class byte {
}
class char {
}
class double {
}
class float {
}
class int {
}
class long {
}
class short {
}

@Name("java.lang.Class")
@Label("Java Class")
class Class {
  @Label("Class Loader")
  ClassLoader classLoader;

  @Label("Name")
  String name;

  @Label("Package")
  Package package;

  @Label("Access Modifiers")
  int modifiers;

  @Label("Hidden")
  boolean hidden;
}
...

Looks like these are Java-Classes so one strategy could be to just compile those and use Reflection to extract meta informations but I went another route

Parsing the .jfr-File

Handcrafting a parser is certainly not the way to go. I needed something that could provide me a fairly simple Logical-AST. There are BNF-Definitions for Java but I wanted something much simpler so I fired up my Eclipse IDE and created an Xtext-Project using the wizards and replaced the content in the .xtext-File with

grammar at.bestsolution.jfr.JFRMeta with org.eclipse.xtext.common.Terminals

generate jFRMeta "http://www.bestsolution.at/jfr/JFRMeta"

Model:
  classes+=Clazz*;

Clazz:
  annotations+=Annotation*
  'class' name=ID ( 'extends' super=QualifiedName )? '{'
    attributes += Attribute*
  '}';

Attribute:
  annotations+=Annotation*
  type=[Clazz|ID] array?='[]'? name=ID ';'
;

Annotation:
  '@' type=[Clazz|ID] ('(' (values+=AnnotationValue |
   ('{' values+=AnnotationValue
   (',' values += AnnotationValue)* '}')) ')')?
;

AnnotationValue:
  valueString=STRING | valueBoolean=Boolean | valueNum=INT
;

enum Boolean:
  TRUE="true" | FALSE="false"
;

QualifiedName:
  ID ('.' ID)*;

That’s all required because the .jfr-File is extremly simple so we don’t need a more complex definition.

How to convert

Well although Xtext is primarily used to develop DSL-Editors for the Eclipse IDE one can run the generated parser in plain old Java. So all now needed is to write a generator who parses the .jfr-File(s) and generate different output from it (HTML, JSON, …) and because although Java now has multiline strings Xtend is the much better choice to write a “code”-generator.

package at.bestsolution.jfr

import org.eclipse.xtext.resource.XtextResourceSet
import org.eclipse.xtext.resource.XtextResource
import java.util.ArrayList
import org.eclipse.emf.common.util.URI
import java.nio.file.Files
import java.nio.file.Paths
import at.bestsolution.jfr.jFRMeta.Model
import java.nio.file.StandardOpenOption
import at.bestsolution.jfr.jFRMeta.Clazz

import static extension at.bestsolution.jfr.GenUtil.*
import at.bestsolution.jfr.jFRMeta.Attribute

class JSONGen {
     def static void main(String[] args) {
         val versions = createVersionList(Integer.parseInt(args.get(0)))
         val injector = new JFRMetaStandaloneSetup().createInjectorAndDoEMFRegistration();
         val resourceSet = injector.getInstance(XtextResourceSet);
         resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);

         val models = new ArrayList
         for( v : versions ) {
             val resource = resourceSet.getResource(
                 URI.createURI("file:/Users/tomschindl/git/jfr-doc/openjdk-"+v+".jfr"), true);
             val model = resource.getContents().head as Model;
             models.add(model)
         }

         for( pair : models.indexed ) {
             val model = pair.value
             var version = versions.get(pair.key)
             val preModel = pair.key == 0 ? null : models.get(pair.key - 1)
             Files.writeString(Paths.get("/Users/tomschindl/git/jfr-doc/openjdk-"+version+".json"),model.generate(preModel,version), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)
         }
     }

     def static generate(Model model, Model prevModel, String ver) '''
         {
             "version": "«ver»",
             "distribution": "openjdk",
             "events": [
                 «val evts = model.classes.filter[c|c.super == "jdk.jfr.Event"]»
                 «FOR e : evts»
                     «e.generateEvent»«IF e !== evts.last»,«ENDIF»
                 «ENDFOR»
             ],
             "types": [
                 «val types = model.classes.filter[c|c.super === null]»
                 «FOR t : types»
                     «t.generateType»«IF t !== types.last»,«ENDIF»
                 «ENDFOR»
             ]
         }
     '''

     def static generateEvent(Clazz clazz) '''
         {
             "name": "«clazz.name»",
             "description": "«clazz.description»",
             "label": "«clazz.label»",
             "categories": [
                 «val cats = clazz.categories»
                 «FOR cat : cats»
                     "«cat»"«IF cat !== cats.last»,«ENDIF»
                 «ENDFOR»
             ],
             "attributes": [
                 «FOR a : clazz.attributes»
                     «a.generateAttribute»«IF a !== clazz.attributes.last»,«ENDIF»
                 «ENDFOR»
             ]
         }
     '''

     def static generateType(Clazz clazz) '''
         {
             "name": "«clazz.name»",
             "attributes": [
                 «FOR a : clazz.attributes»
                     «a.generateAttribute»«IF a !== clazz.attributes.last»,«ENDIF»
                 «ENDFOR»
             ]
         }
     '''

     def static generateAttribute(Attribute a) '''
         {
             "name": "«a.name»",
             "type": "«a.type.name»",
             "contentType": "«a.contentType»",
             "description": "«a.description»"
         }
     '''
}

All sources are available at https://github.com/BestSolution-at/jfr-doc if you look at this code keep in mind that it was hacked together in an evening

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.