From 036c87e50d43e2bd2f34fb8a04f10a89ee48df44 Mon Sep 17 00:00:00 2001 From: Andrei Pangin <1749416+apangin@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:00:48 +0000 Subject: [PATCH] Differential Flame Graphs (#1553) --- .assets/html/flamegraph.html | 16 ++++- .assets/images/flamegraph_diff.png | Bin 0 -> 80069 bytes .licenserc.yaml | 1 + docs/ConverterUsage.md | 36 +++++++++- src/converter/one/convert/Arguments.java | 1 + src/converter/one/convert/FlameGraph.java | 67 ++++++++++++++++-- src/converter/one/convert/Frame.java | 6 +- src/converter/one/convert/Main.java | 32 ++++++++- src/res/flame.html | 16 ++++- test/test/jfrconverter/JfrconverterTests.java | 41 ++++++++++- test/test/jfrconverter/sample1.collapsed | 17 +++++ test/test/jfrconverter/sample2.collapsed | 17 +++++ 12 files changed, 229 insertions(+), 21 deletions(-) create mode 100644 .assets/images/flamegraph_diff.png create mode 100644 test/test/jfrconverter/sample1.collapsed create mode 100644 test/test/jfrconverter/sample2.collapsed diff --git a/.assets/html/flamegraph.html b/.assets/html/flamegraph.html index 77c1485f..9da318e9 100644 --- a/.assets/html/flamegraph.html +++ b/.assets/html/flamegraph.html @@ -75,9 +75,11 @@ // SPDX-License-Identifier: Apache-2.0 'use strict'; let root, px, pattern; - let level0 = 0, left0 = 0, width0 = 0; + let level0 = 0, left0 = 0, width0 = 0, d = 0; let nav = [], navIndex, matchval; let inverted = false; + const U = undefined; + const maxdiff = -1; const levels = Array(36); for (let h = 0; h < levels.length; h++) { levels[h] = []; @@ -111,10 +113,18 @@ return '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16); } + function getDiffColor(diff) { + if (diff === U) return '#ffdd33'; + if (diff === 0) return '#e0e0e0'; + const v = Math.round(128 * (maxdiff - Math.abs(diff)) / maxdiff) + 96; + return diff > 0 ? 'rgb(255,' + v + ',' + v + ')' : 'rgb(' + v + ',' + v + ',255)'; + } + function f(key, level, left, width, inln, c1, int) { levels[level0 = level].push({level, left: left0 += left, width: width0 = width || width0, - color: getColor(palette[key & 7]), title: cpool[key >>> 3], - details: (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '') + color: maxdiff >= 0 ? getDiffColor(d) : getColor(palette[key & 7]), + title: cpool[key >>> 3], + details: (d ? (d > 0 ? ', +' : ', ') + d : '') + (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '') }); } diff --git a/.assets/images/flamegraph_diff.png b/.assets/images/flamegraph_diff.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9af87a8800bb96dec417e4f4f2a4b2df8806fd GIT binary patch literal 80069 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

e!fUb9lE(LPD%FqbudL=ZKEl!;%-=3*PSx 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 Ln_MV4XBYM#>?ZL# literal 0 HcmV?d00001 diff --git a/.licenserc.yaml b/.licenserc.yaml index a4c0bec9..d2618e06 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -15,6 +15,7 @@ header: - 'src/jattach' - 'src/res' - '**/MANIFEST.MF' + - 'test/**/*.collapsed' license: content: | Copyright The async-profiler authors diff --git a/docs/ConverterUsage.md b/docs/ConverterUsage.md index 18cc40d6..3d9ecb94 100644 --- a/docs/ConverterUsage.md +++ b/docs/ConverterUsage.md @@ -43,6 +43,8 @@ Conversion options: # otlp: OpenTelemetry profile format. +Differential Flame Graph: + --diff JFR options: --cpu Generate only CPU profile during conversion @@ -120,7 +122,7 @@ jfrconv --cpu foo.jfr for HTML output as HTML is the default format for conversion from JFR. -#### Flame Graph options +### Flame Graph options To add a custom title to the generated Flame Graph, use `--title`, which has the default value `Flame Graph`: @@ -128,9 +130,37 @@ To add a custom title to the generated Flame Graph, use `--title`, which has the jfrconv --cpu foo.jfr foo.html -r --title "Custom Title" ``` -### Other formats +### Differential Flame Graph -`jfrconv` supports converting a JFR file to `collapsed`, `pprof`, `pb.gz` and `heatmap` formats as well. +To find performance regressions, it may be useful to compare current profile +to a previous one that serves as a baseline. Differential Flame Graph +visualizes such a comparsion with a special color scheme: + +- Red color denotes frames with more samples comparing to the baseline (i.e. regression); +- Blue is for frames with less samples; +- Yellow are new frames that were absent in the baseline. + +The more intense the color, the larger the delta. +For each different frame, the delta value is displayed in a tooltip. + +![](/.assets/images/flamegraph_diff.png) + +Differential Flame Graph takes the shape of the current profile: +all frames have exactly the same size as in the normal Flame Graph. +This means, frames that exist only in the base profile will not be visible. +To see such frames, create another differential Flame Graph, +swapping the base and the current input file. + +To create differential Flame Graph, run `jfrconv --diff` with two input files: +basline profile and new profile. Both files can be in JFR, HTML, or collapsed format. +Other converter options work as usual. + +``` +jfrconv --cpu --diff baseline.jfr new.jfr diff.html +``` + +Output file name is optional. If omitted, `jfrconv` takes the name +of the second input file, replacing its extension with `.diff.html`. ## Standalone converter examples diff --git a/src/converter/one/convert/Arguments.java b/src/converter/one/convert/Arguments.java index 5b1a01b6..04895a06 100644 --- a/src/converter/one/convert/Arguments.java +++ b/src/converter/one/convert/Arguments.java @@ -24,6 +24,7 @@ public class Arguments { public boolean help; public boolean reverse; public boolean inverted; + public boolean diff; public boolean cpu; public boolean cpuTime; public boolean wall; diff --git a/src/converter/one/convert/FlameGraph.java b/src/converter/one/convert/FlameGraph.java index 0a6ee43a..066a9271 100644 --- a/src/converter/one/convert/FlameGraph.java +++ b/src/converter/one/convert/FlameGraph.java @@ -20,6 +20,7 @@ public class FlameGraph implements Comparator { private static final String[] FRAME_SUFFIX = {"_[0]", "_[j]", "_[i]", "", "", "_[k]", "_[1]"}; private static final byte HAS_SUFFIX = (byte) 0x80; private static final int FLUSH_THRESHOLD = 15000; + private static final long NEW_FRAME_DIFF = Long.MIN_VALUE; private static final Pattern TID_FRAME_PATTERN = Pattern.compile("\\[(.* )?tid=\\d+]"); private final Arguments args; @@ -29,11 +30,14 @@ public class FlameGraph implements Comparator { private String title = "Flame Graph"; private int[] order; + private int[] cpoolMap; private int depth; private int lastLevel; private long lastX; private long lastTotal; + private long lastDiff; private long mintotal; + private long maxdiff = -1; public FlameGraph(Arguments args) { this.args = args; @@ -90,6 +94,8 @@ public class FlameGraph implements Comparator { while (!br.readLine().isEmpty()) ; for (String line; !(line = br.readLine()).isEmpty(); ) { + if (line.startsWith("d=")) continue; // artifact of a differential flame graph + StringTokenizer st = new StringTokenizer(line.substring(2, line.length() - 1), ","); int nameAndType = Integer.parseInt(st.nextToken()); @@ -109,12 +115,10 @@ public class FlameGraph implements Comparator { int titleIndex = nameAndType >>> 3; byte type = (byte) (nameAndType & 7); - if (st.hasMoreTokens() && (type <= TYPE_INLINED || type >= TYPE_C1_COMPILED)) { - type = TYPE_JIT_COMPILED; - } + byte normalizedType = type <= TYPE_INLINED || type >= TYPE_C1_COMPILED ? TYPE_JIT_COMPILED : type; - Frame f = level > 0 || needRebuild ? new Frame(titleIndex, type) : root; - f.self = f.total = total; + Frame f = level > 0 || needRebuild ? new Frame(titleIndex, normalizedType) : root; + fillFrameCounters(f, type, total); if (st.hasMoreTokens()) f.inlined = Long.parseLong(st.nextToken()); if (st.hasMoreTokens()) f.c1 = Long.parseLong(st.nextToken()); if (st.hasMoreTokens()) f.interpreted = Long.parseLong(st.nextToken()); @@ -177,6 +181,26 @@ public class FlameGraph implements Comparator { depth = Math.max(depth, stack.size); } + public void diff(FlameGraph base) { + // Build a map that translates this cpool keys to the base flamegraph's cpool keys + cpoolMap = Arrays.stream(cpool.keys()).mapToInt(title -> base.cpool.getOrDefault(title, -1)).toArray(); + diff(base.root, root); + } + + private void diff(Frame base, Frame current) { + current.diff = base == null ? NEW_FRAME_DIFF : current.self - base.self; + maxdiff = Math.max(maxdiff, Math.abs(current.diff)); + + for (Frame child : current.values()) { + Frame baseChild = base == null ? null : base.get(translateKey(child.key)); + diff(baseChild, child); + } + } + + private int translateKey(int key) { + return cpoolMap[key & TITLE_MASK] | (key & ~TITLE_MASK); + } + public void dump(OutputStream out) throws IOException { try (PrintStream ps = new PrintStream(out, false, "UTF-8")) { dump(ps); @@ -205,6 +229,9 @@ public class FlameGraph implements Comparator { tail = printTill(out, tail, "/*inverted:*/false"); out.print(args.reverse ^ args.inverted); + tail = printTill(out, tail, "/*maxdiff:*/-1"); + out.print(maxdiff); + tail = printTill(out, tail, "/*depth:*/0"); out.print(depth); @@ -239,6 +266,15 @@ public class FlameGraph implements Comparator { } private void printFrame(PrintStream out, Frame frame, int level, long x) { + StringBuilder sb = outbuf; + if (frame.diff != lastDiff) { + if (frame.diff == NEW_FRAME_DIFF) { + sb.append("d=U\n"); + } else { + sb.append("d=").append(frame.diff).append('\n'); + } + } + int nameAndType = order[frame.getTitleIndex()] << 3 | frame.getType(); boolean hasExtraTypes = (frame.inlined | frame.c1 | frame.interpreted) != 0 && frame.inlined < frame.total && frame.interpreted < frame.total; @@ -250,7 +286,7 @@ public class FlameGraph implements Comparator { func = 'n'; } - StringBuilder sb = outbuf.append(func).append('(').append(nameAndType); + sb.append(func).append('(').append(nameAndType); if (func == 'f') { sb.append(',').append(level).append(',').append(x - lastX); } @@ -270,6 +306,7 @@ public class FlameGraph implements Comparator { lastLevel = level; lastX = x; lastTotal = frame.total; + lastDiff = frame.diff; Frame[] children = frame.values().toArray(EMPTY_FRAME_ARRAY); Arrays.sort(children, this); @@ -291,6 +328,9 @@ public class FlameGraph implements Comparator { sb.append(strings[frame.getTitleIndex()]).append(FRAME_SUFFIX[frame.getType()]); if (frame.self > 0) { int tmpLength = sb.length(); + if (maxdiff >= 0) { + sb.append(' ').append(frame.diff == NEW_FRAME_DIFF ? 0 : frame.self - frame.diff); + } out.print(sb.append(' ').append(frame.self).append('\n')); sb.setLength(tmpLength); } @@ -328,6 +368,21 @@ public class FlameGraph implements Comparator { return include != null; } + private static void fillFrameCounters(Frame frame, byte type, long ticks) { + frame.self = frame.total = ticks; + switch (type) { + case TYPE_INTERPRETED: + frame.interpreted = ticks; + break; + case TYPE_INLINED: + frame.inlined = ticks; + break; + case TYPE_C1_COMPILED: + frame.c1 = ticks; + break; + } + } + private Frame addChild(Frame frame, String title, byte type, long ticks) { frame.total += ticks; diff --git a/src/converter/one/convert/Frame.java b/src/converter/one/convert/Frame.java index 4af9315a..29086d99 100644 --- a/src/converter/one/convert/Frame.java +++ b/src/converter/one/convert/Frame.java @@ -16,11 +16,13 @@ public class Frame extends HashMap { public static final byte TYPE_KERNEL = 5; public static final byte TYPE_C1_COMPILED = 6; - private static final int TYPE_SHIFT = 28; + static final int TYPE_SHIFT = 28; + static final int TITLE_MASK = (1 << TYPE_SHIFT) - 1; final int key; long total; long self; + long diff; long inlined, c1, interpreted; private Frame(int key) { @@ -36,7 +38,7 @@ public class Frame extends HashMap { } int getTitleIndex() { - return key & ((1 << TYPE_SHIFT) - 1); + return key & TITLE_MASK; } byte getType() { diff --git a/src/converter/one/convert/Main.java b/src/converter/one/convert/Main.java index d48ef199..4e5eefd0 100644 --- a/src/converter/one/convert/Main.java +++ b/src/converter/one/convert/Main.java @@ -7,6 +7,7 @@ package one.convert; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; public class Main { @@ -18,7 +19,7 @@ public class Main { return; } - if (args.files.size() == 1) { + if (args.files.size() == (args.diff ? 2 : 1)) { args.files.add("."); } @@ -35,6 +36,34 @@ public class Main { } } + if (args.diff) { + if (fileCount != 2) { + throw new IllegalArgumentException("--diff option requires two input files"); + } + if (!"html".equals(args.output) && !"collapsed".equals(args.output)) { + throw new IllegalArgumentException("--diff option requires html or collapsed output format"); + } + + args.norm = true; // don't let random IDs in class names spoil comparison + + String input1 = args.files.get(0); + String input2 = args.files.get(1); + String output = isDirectory ? new File(lastFile, replaceExt(input2, "diff." + args.output)).getPath() : lastFile; + + System.out.print("Converting " + getFileName(input2) + " vs " + getFileName(input1) + " -> " + getFileName(output) + " "); + System.out.flush(); + + long startTime = System.nanoTime(); + FlameGraph base = parseFlameGraph(input1, args); + FlameGraph current = parseFlameGraph(input2, args); + current.diff(base); + current.dump(new FileOutputStream(output)); + long endTime = System.nanoTime(); + + System.out.print("# " + (endTime - startTime) / 1000000 / 1000.0 + " s\n"); + return; + } + for (int i = 0; i < fileCount; i++) { String input = args.files.get(i); String output = isDirectory ? new File(lastFile, replaceExt(input, args.output)).getPath() : lastFile; @@ -106,6 +135,7 @@ public class Main { " -o --output FORMAT Output format: html, collapsed, pprof, pb.gz, heatmap, otlp\n" + " -I --include REGEX Include only stacks with the specified frames\n" + " -X --exclude REGEX Exclude stacks with the specified frames\n" + + " --diff Create differential Flame Graph from two input files\n" + "\n" + "JFR options:\n" + " --cpu CPU profile (ExecutionSample)\n" + diff --git a/src/res/flame.html b/src/res/flame.html index 108a4225..c3a9b70a 100644 --- a/src/res/flame.html +++ b/src/res/flame.html @@ -75,9 +75,11 @@ // SPDX-License-Identifier: Apache-2.0 'use strict'; let root, px, pattern; - let level0 = 0, left0 = 0, width0 = 0; + let level0 = 0, left0 = 0, width0 = 0, d = 0; let nav = [], navIndex, matchval; let inverted = /*inverted:*/false; + const U = undefined; + const maxdiff = /*maxdiff:*/-1; const levels = Array(/*depth:*/0); for (let h = 0; h < levels.length; h++) { levels[h] = []; @@ -111,10 +113,18 @@ return '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16); } + function getDiffColor(diff) { + if (diff === U) return '#ffdd33'; + if (diff === 0) return '#e0e0e0'; + const v = Math.round(128 * (maxdiff - Math.abs(diff)) / maxdiff) + 96; + return diff > 0 ? 'rgb(255,' + v + ',' + v + ')' : 'rgb(' + v + ',' + v + ',255)'; + } + function f(key, level, left, width, inln, c1, int) { levels[level0 = level].push({level, left: left0 += left, width: width0 = width || width0, - color: getColor(palette[key & 7]), title: cpool[key >>> 3], - details: (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '') + color: maxdiff >= 0 ? getDiffColor(d) : getColor(palette[key & 7]), + title: cpool[key >>> 3], + details: (d ? (d > 0 ? ', +' : ', ') + d : '') + (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '') }); } diff --git a/test/test/jfrconverter/JfrconverterTests.java b/test/test/jfrconverter/JfrconverterTests.java index 5cd5bda0..7afa0124 100644 --- a/test/test/jfrconverter/JfrconverterTests.java +++ b/test/test/jfrconverter/JfrconverterTests.java @@ -5,13 +5,19 @@ package test.jfrconverter; -import test.otlp.CpuBurner; import one.convert.*; import one.jfr.JfrReader; +import one.jfr.StackTrace; import one.jfr.event.Event; import one.jfr.event.EventCollector; -import one.jfr.StackTrace; -import one.profiler.test.*; +import one.profiler.test.Output; +import one.profiler.test.Test; +import one.profiler.test.TestProcess; +import test.otlp.CpuBurner; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; // Simple smoke tests for JFR converter. The output is not inspected for errors, // we only verify that the conversion completes successfully. @@ -72,4 +78,33 @@ public class JfrconverterTests { assert !found[3]; } } + + @Test(mainClass = Main.class, args = "--diff test/test/jfrconverter/sample1.collapsed test/test/jfrconverter/sample2.collapsed %diff.collapsed") + public void diffCollapsed(TestProcess p) throws Exception { + Output out = p.waitForExit("%diff"); + assert out.containsExact("BusyClient.run_[j] 4 1"); + assert out.containsExact("BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j] 2 2"); + assert out.containsExact("ByteBuffer.get_[i];ByteBuffer.getArray_[i] 0 1"); + assert out.samples("ByteBuffer.get") == 2; + } + + @Test(mainClass = Main.class, args = "--diff test/test/jfrconverter/sample1.collapsed test/test/jfrconverter/sample2.collapsed %diff.html") + public void diffHtml(TestProcess p) throws Exception { + Output out = p.waitForExit("%diff"); + assert out.containsExact("d=-3"); + assert out.containsExact("d=0"); + assert out.containsExact("d=U"); + + // It should be possible to reconstruct original FlameGraph from the differential one + byte[] original = buildFlameGraph("test/test/jfrconverter/sample2.collapsed"); + byte[] reconstructed = buildFlameGraph(p.getFilePath("%diff")); + assert Arrays.equals(original, reconstructed); + } + + private static byte[] buildFlameGraph(String input) throws IOException { + FlameGraph fg = FlameGraph.parse(input, new Arguments()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + fg.dump(baos); + return baos.toByteArray(); + } } diff --git a/test/test/jfrconverter/sample1.collapsed b/test/test/jfrconverter/sample1.collapsed new file mode 100644 index 00000000..b84c8ffd --- /dev/null +++ b/test/test/jfrconverter/sample1.collapsed @@ -0,0 +1,17 @@ +BusyClient.run_[j] 4 +BusyClient.run_[j];InputStream.read_[j] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j] 2 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j] 3 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.beginRead_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.beginRead_[i];NativeThread.current_[i];NativeThread.current0_[j] 3 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.endRead_[i] 3 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];Util.getTemporaryDirectBuffer_[i];CarrierThreadLocal.get_[i];System$2.getCarrierThreadLocal_[i];ThreadLocal.getCarrierThreadLocal_[i];jlong_disjoint_arraycopy 15 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];Util.getTemporaryDirectBuffer_[i];Util$BufferCache.get_[i];Buffer.capacity_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];SocketDispatcher.read_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];SocketDispatcher.read_[i];SocketDispatcher.read0_[j] 3 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];SocketDispatcher.read_[i];SocketDispatcher.read0_[j];Java_sun_nio_ch_SocketDispatcher_read0;read 143 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];ReentrantLock.lock_[i];ReentrantLock$Sync.lock_[i];ReentrantLock$NonfairSync.initialTryLock_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];ReentrantLock.lock_[i];ReentrantLock$Sync.lock_[i];ReentrantLock$NonfairSync.initialTryLock_[i];AbstractQueuedSynchronizer.compareAndSetState_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];ReentrantLock.unlock_[i];AbstractQueuedSynchronizer.release_[i];ReentrantLock$Sync.tryRelease_[i];AbstractQueuedSynchronizer.setState_[i] 1 diff --git a/test/test/jfrconverter/sample2.collapsed b/test/test/jfrconverter/sample2.collapsed new file mode 100644 index 00000000..20ccb7a4 --- /dev/null +++ b/test/test/jfrconverter/sample2.collapsed @@ -0,0 +1,17 @@ +BusyClient.run_[j] 1 +BusyClient.run_[j];InputStream.read_[j] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j] 2 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j] 3 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.beginRead_[i] 3 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.beginRead_[i];NativeThread.current_[i];NativeThread.current0_[j] 4 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.endRead_[i] 4 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];ByteBuffer.get_[i];ByteBuffer.getArray_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];ByteBuffer.get_[i];Buffer.position_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];Util.getTemporaryDirectBuffer_[i];CarrierThreadLocal.get_[i];System$2.getCarrierThreadLocal_[i];ThreadLocal.getCarrierThreadLocal_[i];jlong_disjoint_arraycopy 6 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];SocketDispatcher.read_[i];SocketDispatcher.read0_[j] 4 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];SocketDispatcher.read_[i];SocketDispatcher.read0_[j];Java_sun_nio_ch_SocketDispatcher_read0 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];NioSocketImpl.implRead_[j];NioSocketImpl.tryRead_[j];SocketDispatcher.read_[i];SocketDispatcher.read0_[j];Java_sun_nio_ch_SocketDispatcher_read0;read 151 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];ReentrantLock.lock_[i] 1 +BusyClient.run_[j];InputStream.read_[j];Socket$SocketInputStream.read_[j];NioSocketImpl$1.read_[j];NioSocketImpl.read_[j];ReentrantLock.unlock_[i];AbstractQueuedSynchronizer.release_[i];ReentrantLock$Sync.tryRelease_[i];AbstractQueuedSynchronizer.setState_[i] 3