diff --git a/.gitignore b/.gitignore index ff07568f..712221ac 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ visualization/node_modules/ /protobuf/src/main/gen/* /visualization/static/js/generated/* protobuf/generated/main/ +/src/gen/ diff --git a/build.gradle b/build.gradle index d241d328..7ff6dfc9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import io.netifi.flatbuffers.plugin.tasks.FlatBuffers + buildscript { ext.kotlin_version = '1.2.51' repositories { @@ -28,6 +30,8 @@ plugins { id 'com.google.protobuf' version '0.8.6' apply false id 'com.moowork.node' version '1.2.0' id 'com.liferay.soy' version '3.1.6' + id "io.netifi.flatbuffers" version "1.0.4" + id 'idea' } allprojects { @@ -56,6 +60,7 @@ configurations { dependencies { compile group: 'com.google.code.gson', name: 'gson', version: '2.8.1' + compile group: 'com.github.davidmoten', name: 'flatbuffers-java', version: '1.9.0.1' compile fileTree(dir: 'lib', include: ['com.jrockit.mc.flightrecorder_5.5.1.172852.jar']) compile fileTree(dir: 'lib', include: ['com.jrockit.mc.common_5.5.1.172852.jar']) compile fileTree(dir: 'lib', include: ['flight-recorder-parser-for-java-9.jar']) @@ -126,6 +131,23 @@ compileTestKotlin { } } +flatbuffers { + flatcPath = '/usr/local/bin/flatc' + language = 'java' +} + +task compileFlatBuffers(type: FlatBuffers) { + inputDir = file("src/main/flatbuffers") + outputDir = file("src/gen/java") +} + +sourceSets.main.java.srcDir file('src/gen/java') +idea { + module { + generatedSourceDirs += file('src/gen/java') + } +} + /* if have some problems with jmh - execute clean first */ jmh { /* jmh Jar warns about duplicate dependencies diff --git a/src/main/flatbuffers/cflamegraph_schema.fbs b/src/main/flatbuffers/cflamegraph_schema.fbs new file mode 100644 index 00000000..65f4a823 --- /dev/null +++ b/src/main/flatbuffers/cflamegraph_schema.fbs @@ -0,0 +1,22 @@ +namespace com.github.kornilova_l.flamegraph.cflamegraph; + +table Names { + class_names:[string]; + method_names:[string]; + descriptions:[string]; +} + +struct Node { + class_name_id:int; // -1 if class name is not set + method_name_id:int; + description_id:int; // -1 if description is not set + width:int; + depth:int; +} + +table Tree { + names:Names; + nodes:[Node]; +} + +root_type Tree; diff --git a/src/main/java/com/github/kornilova_l/flamegraph/plugin/server/converters/file_to_call_traces/cflamegraph/CompressedFlamegraphToCallTracesConverter.kt b/src/main/java/com/github/kornilova_l/flamegraph/plugin/server/converters/file_to_call_traces/cflamegraph/CompressedFlamegraphToCallTracesConverter.kt index 87e28dbc..7b1629da 100644 --- a/src/main/java/com/github/kornilova_l/flamegraph/plugin/server/converters/file_to_call_traces/cflamegraph/CompressedFlamegraphToCallTracesConverter.kt +++ b/src/main/java/com/github/kornilova_l/flamegraph/plugin/server/converters/file_to_call_traces/cflamegraph/CompressedFlamegraphToCallTracesConverter.kt @@ -7,26 +7,13 @@ import java.io.File /** - * Original flamegraph format consumes a lot of memory on disk and it takes more time to parse - * because it has lots of duplicate parts of call traces. + * Original flamegraph format consumes a lot of memory on disk and it takes more + * time to parse because it has lots of duplicate parts of call traces. * cflamegraph (compressed flamegraph) solves this problem. + * It uses FlatBuffers for data serialization and does not duplicate + * class names, method names and descriptions. * - * Header of file contains ids of class names, method names and descriptions. - * For example: - * C - * ClassName1 0 - * ClassName2 1 - * M - * method1 0 - * method2 1 - * D - * () 0 - * (int)int 1 - * - * Each line of cflamegraph contains information in following format: - * C= M= D= w= d= - * - * C and D are optional. + * See scheme in /src/main/flatbuffers/cflamegraph_schema.fbs * * Example: * ._ _ @@ -34,37 +21,34 @@ import java.io.File * |b()____|e|_______ _ * |Class.a__________|f| * - * --C-- 1 - * Class 0 - * --M-- 6 - * a 0 - * b 1 - * c 2 - * d 3 - * e 4 - * f 5 - * --D-- 1 - * () 0 - * C0M0w100d1 - * M1w40d2 - * M2w5d3 - * M3w5d3 - * M4w5d2 - * M5w5d1 - * - * As you can see order of lines matters - * because if a call has bigger depth than previous it means that - * the call is a child of previous call. - * * In original flamegraph format this example would look like this: - * a;b;c 5 - * a;b;d 5 - * a;b 10 + * a;b();c 5 + * a;b();d 5 + * a;b() 10 * a;e 5 * a 50 * f 5 * - * In cflamegraph files strings are not duplicated and equal parts of stacktraces are also not duplicated. + * Serialized with flatbuffers tree will have following structure: + * Names: { + * classNames: ["Class"], + * methodNames: ["a", "b", "c", "d", "e", "f"], + * descriptions: ["()"] + * } + * Tree: { + * nodes: [ + * { class_name_id: 0, method_name_id: 0, width: 100, depth: 1 }, + * { method_name_id: 1, description_id: 0, width: 40, depth: 2 }, + * { method_name_id: 2, width: 5, depth: 3 }, + * { method_name_id: 3, width: 5, depth: 3 }, + * { method_name_id: 4, width: 5, depth: 2 }, + * { method_name_id: 5, width: 5, depth: 1 } + * ] + * } + * + * Order of nodes matters. + * If a call has bigger depth than previous it means that the call is a child + * of previous call. * * Converter merges unmerged stacktraces. */