├── .gitignore ├── README.md ├── src └── main │ └── java │ └── com │ └── wilb0t │ └── flamegraph │ ├── JfrStackTraceWriter.java │ ├── JfrToFolded.java │ └── JfrStackTraceReader.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | ## IntelliJ 3 | .idea 4 | *.iml 5 | 6 | ## Temp 7 | dependency-reduced-pom.xml 8 | 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flame-graph-jfr 2 | 3 | Convert java 11 JFR to folded format for https://github.com/brendangregg/FlameGraph 4 | 5 | ## Usage 6 | 7 | Requires java 11 jdk or higher. 8 | 9 | `mvn clean package` 10 | 11 | `java -jar target/flame-graph-1.0-SNAPSHOT-shaded.jar -i some-j11-flight-recorder.flr -o output.fld` 12 | 13 | Clone https://github.com/brendangregg/FlameGraph 14 | 15 | In FlameGraph dir 16 | 17 | `./flamegraph.pl /output.fld > flamegraph.svg` 18 | 19 | Open flamegraph.svg in your browser to explore! 20 | 21 | Should see something like 22 | [![Example](http://www.brendangregg.com/FlameGraphs/cpu-bash-flamegraph.svg)](http://www.brendangregg.com/FlameGraphs/cpu-bash-flamegraph.svg) 23 | -------------------------------------------------------------------------------- /src/main/java/com/wilb0t/flamegraph/JfrStackTraceWriter.java: -------------------------------------------------------------------------------- 1 | package com.wilb0t.flamegraph; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.io.Writer; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.Map; 10 | 11 | public class JfrStackTraceWriter { 12 | 13 | public void write(File outputFile, Map traceCounts) throws IOException { 14 | try (Writer writer = new FileWriter(outputFile, StandardCharsets.UTF_8); 15 | BufferedWriter bufferedWriter = new BufferedWriter(writer) 16 | ) { 17 | traceCounts.forEach( 18 | (trace, count) -> 19 | { 20 | try { 21 | bufferedWriter.write(String.format("%s %d%n", trace, count)); 22 | } catch (IOException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | ); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/wilb0t/flamegraph/JfrToFolded.java: -------------------------------------------------------------------------------- 1 | package com.wilb0t.flamegraph; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class JfrToFolded { 10 | 11 | public static void main(String[] args) { 12 | try { 13 | var processedArgs = processArgs(args); 14 | if (!processedArgs.keySet().containsAll(List.of("-o", "-i"))) { 15 | throw new IllegalArgumentException("Must pass -i -o "); 16 | } 17 | 18 | var jfr = new File(processedArgs.get("-i")); 19 | var folded = new File(processedArgs.get("-o")); 20 | 21 | jfrToFolded(jfr, folded); 22 | } catch (IOException e) { 23 | System.err.println(e.getMessage()); 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | private static void jfrToFolded(File jfr, File folded) throws IOException { 29 | var reader = new JfrStackTraceReader(); 30 | var writer = new JfrStackTraceWriter(); 31 | 32 | var traceCounts = reader.getStackTraces(jfr.toPath()); 33 | writer.write(folded, traceCounts); 34 | } 35 | 36 | static Map processArgs(String[] args) { 37 | var processed = new HashMap(); 38 | for (int i = 0; i <= args.length - 2; i += 2) { 39 | processed.put(args[i], args[i+1]); 40 | } 41 | return processed; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.wilb0t 6 | flame-graph-jfr 7 | flame-graph-jfr 8 | 1.0-SNAPSHOT 9 | jar 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | org.junit.jupiter 21 | junit-jupiter-api 22 | 5.4.0 23 | test 24 | 25 | 26 | org.junit.jupiter 27 | junit-jupiter-engine 28 | 5.4.0 29 | test 30 | 31 | 32 | org.hamcrest 33 | java-hamcrest 34 | 2.0.0.0 35 | test 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.8.1 45 | 46 | 11 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-shade-plugin 52 | 3.2.1 53 | 54 | 55 | 56 | shade 57 | 58 | 59 | true 60 | 61 | 62 | com.wilb0t.flamegraph.JfrToFolded 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/java/com/wilb0t/flamegraph/JfrStackTraceReader.java: -------------------------------------------------------------------------------- 1 | package com.wilb0t.flamegraph; 2 | 3 | import static java.util.stream.Collectors.summingLong; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Path; 7 | import java.util.Collections; 8 | import java.util.Map; 9 | import java.util.function.Function; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | import jdk.jfr.consumer.RecordedEvent; 13 | import jdk.jfr.consumer.RecordedFrame; 14 | import jdk.jfr.consumer.RecordedMethod; 15 | import jdk.jfr.consumer.RecordedStackTrace; 16 | import jdk.jfr.consumer.RecordingFile; 17 | 18 | public class JfrStackTraceReader { 19 | 20 | private static final String EVENT_TYPE = "jdk.ExecutionSample"; 21 | 22 | public Map getStackTraces(Path input) throws IOException { 23 | try (var recording = new RecordingFile(input)) { 24 | return asStream(recording) 25 | .filter(event -> event.getEventType().getName().equalsIgnoreCase(EVENT_TYPE)) 26 | .map(JfrStackTraceReader::getTrace) 27 | .collect(Collectors.groupingBy(Function.identity(), summingLong(s -> 1))); 28 | } 29 | } 30 | 31 | private static Stream asStream(RecordingFile recording) throws IOException { 32 | if (recording.hasMoreEvents()) { 33 | RecordedEvent event = recording.readEvent(); 34 | return Stream.iterate( 35 | event, 36 | e -> recording.hasMoreEvents(), 37 | e -> { 38 | try { 39 | return recording.readEvent(); 40 | } catch (IOException ex) { 41 | throw new RuntimeException(ex); 42 | } 43 | }); 44 | } 45 | return Stream.empty(); 46 | } 47 | 48 | private static String getTrace(RecordedEvent event) { 49 | RecordedStackTrace trace = event.getStackTrace(); 50 | if (trace == null) { 51 | return ""; 52 | } 53 | var frames = trace.getFrames().stream() 54 | .filter(RecordedFrame::isJavaFrame) 55 | .flatMap(JfrStackTraceReader::getFrameName) 56 | .collect(Collectors.toList()); 57 | Collections.reverse(frames); 58 | 59 | return String.join(";", frames); 60 | } 61 | 62 | private static Stream getFrameName(RecordedFrame frame) { 63 | StringBuilder methodBuilder = new StringBuilder(); 64 | RecordedMethod method = frame.getMethod(); 65 | if (method == null) { 66 | return Stream.empty(); 67 | } 68 | 69 | methodBuilder 70 | .append(method.getType().getName()) 71 | .append("::") 72 | .append(method.getName()); 73 | 74 | return Stream.of(methodBuilder.toString()); 75 | } 76 | 77 | } 78 | --------------------------------------------------------------------------------