diff --git a/core/src/main/java/com/devexperts/aprof/AProfTools.java b/core/src/main/java/com/devexperts/aprof/AProfTools.java index e07a29d..d02e8e5 100644 --- a/core/src/main/java/com/devexperts/aprof/AProfTools.java +++ b/core/src/main/java/com/devexperts/aprof/AProfTools.java @@ -28,7 +28,7 @@ import java.net.URL; import java.util.Locale; -import com.devexperts.aprof.dump.DumpFormatter; +import com.devexperts.aprof.dump.TextDumpFormatter; import com.devexperts.aprof.dump.SnapshotRoot; import com.devexperts.aprof.util.FastOutputStreamWriter; import com.devexperts.aprof.util.InnerJarClassLoader; @@ -119,7 +119,7 @@ private static void runDumpCommand(String[] args) throws IOException, ClassNotFo SnapshotRoot totalSnapshot = (SnapshotRoot)ois.readObject(); ois.close(); socket.close(); - DumpFormatter formatter = new DumpFormatter(new Configuration()); + TextDumpFormatter formatter = new TextDumpFormatter(new Configuration()); PrintWriter out = new PrintWriter(System.out); formatter.dumpSnapshot(out, totalSnapshot, "DUMP"); out.flush(); diff --git a/core/src/main/java/com/devexperts/aprof/Configuration.java b/core/src/main/java/com/devexperts/aprof/Configuration.java index 96a5d32..87fbd28 100644 --- a/core/src/main/java/com/devexperts/aprof/Configuration.java +++ b/core/src/main/java/com/devexperts/aprof/Configuration.java @@ -133,6 +133,9 @@ public class Configuration { @Description("Port to listen on.") private int port = 0; + @Description("Dump format either folded or text. For folded format use './flamegraph.pl --color=java' to produce flamegraphs from report file") + private String dump_format = "text"; + private DetailsConfiguration detailsConfig; private HistogramConfiguration histogramConfig; @@ -277,6 +280,10 @@ public int getPort() { return port; } + public String getDumpFormat() { + return dump_format; + } + public Set getTrackedClasses() { return detailsConfig.getTrackedClasses(); } @@ -350,6 +357,12 @@ private boolean isCompileLogEnabled() { return args.contains(XX_UNLOCK_DIAGNOSTIC_VM_OPTIONS) && args.contains(XX_LOG_COMPILATION); } + public boolean isVerifierDisabled() { + List args = ManagementFactory.getRuntimeMXBean().getInputArguments(); + return args.contains("-noverify"); + + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/core/src/main/java/com/devexperts/aprof/dump/DumpFormatter.java b/core/src/main/java/com/devexperts/aprof/dump/DumpFormatter.java index 863c78b..9e95548 100644 --- a/core/src/main/java/com/devexperts/aprof/dump/DumpFormatter.java +++ b/core/src/main/java/com/devexperts/aprof/dump/DumpFormatter.java @@ -4,7 +4,7 @@ * #%L * Aprof Core * %% - * Copyright (C) 2002 - 2017 Devexperts, LLC + * Copyright (C) 2002 - 2020 Devexperts, LLC * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as @@ -23,297 +23,9 @@ */ import java.io.PrintWriter; -import java.util.Comparator; -import com.devexperts.aprof.AProfRegistry; -import com.devexperts.aprof.Configuration; -import com.devexperts.aprof.util.FastObjIntMap; - -import static com.devexperts.aprof.util.FastFmtUtil.*; - -/** - * Formats collected dump snapshots. - * This class is not thread-safe. - * - * @author Denis Davydov - */ -public class DumpFormatter { - private static final int MAX_DEPTH = 5; - - private final Configuration config; - - private final SnapshotShallow possiblyEliminatedAllocations = new SnapshotShallow(); - private final SnapshotShallow[] rest = new SnapshotShallow[MAX_DEPTH]; - private final FastObjIntMap classLevel = new FastObjIntMap(); - private final FastObjIntMap locationIndex = new FastObjIntMap(); - private final SnapshotDeep locations; - - public DumpFormatter(Configuration config) { - this.config = config; - for (int i = 0; i < MAX_DEPTH; i++) - rest[i] = new SnapshotShallow(); - locations = new SnapshotDeep(null, true, 0); // true to print avg size, 0 to skip printing histogram - } - - public void dumpSnapshot(PrintWriter out, SnapshotRoot ss, String kind) { - dumpSnapshotHeader(out, ss, kind); - out.println("Top allocation-inducing locations with data types allocated from them"); - printlnTearLine(out, '-'); - dumpSnapshotByLocations(out, ss, SnapshotDeep.UNKNOWN); - if (AProfRegistry.TRACK_TRANSFORM_DETAILS) { - out.println("Top allocation-inducing locations inside transform with data types allocated from them"); - printlnTearLine(out, '-'); - dumpSnapshotByLocations(out, ss, AProfRegistry.TRANSFORM_NAME); - } - out.println("Top allocated data types with reverse location traces"); - printlnTearLine(out, '-'); - ss.sortChildrenDeep(getOutputComparator()); - dumpSnapshotByDataTypes(out, ss); - } - - private Comparator getOutputComparator() { - return config.isSize() ? SnapshotShallow.COMPARATOR_SIZE : SnapshotShallow.COMPARATOR_COUNT; - } - - private void dumpSnapshotByLocations(PrintWriter out, SnapshotRoot ss, String insideOf) { - // rebuild locations - locations.clearDeep(); - locations.sortChildrenDeep(SnapshotShallow.COMPARATOR_NAME); - locationIndex.fill(-1); - for (int i = 0; i < ss.getUsed(); i++) { - SnapshotDeep cs = ss.getChild(i); - String dataTypeName = cs.getName(); - findLocationsDeep(cs, dataTypeName, cs.getHistoCountsLength(), insideOf); - } - locations.updateSnapshotSumDeep(); - // sort them and print - locations.sortChildrenDeep(getOutputComparator()); - printLocationsDeep(out, 0, locations, ss); - } - - private void findLocationsDeep(SnapshotDeep ss, String dataTypeName, int histoCountsLength, String insideOf) { - if (!ss.hasChildren() && insideOf.equals(SnapshotDeep.UNKNOWN)) { - processLeafLocation(ss, AProfRegistry.getLocationNameWithoutSuffix(ss.getName()), dataTypeName, histoCountsLength); - return; - } - // has children -- go recursive with a special treatment for UNKNOWN children -- attribute them this location's name - for (int i = 0; i < ss.getUsed(); i++) { - SnapshotDeep cs = ss.getChild(i); - if (cs.getName().equals(insideOf)) { - assert !cs.hasChildren() : insideOf + " location shall not have children"; - processLeafLocation(cs, AProfRegistry.getLocationNameWithoutSuffix(ss.getName()), dataTypeName, histoCountsLength); - } else - findLocationsDeep(cs, dataTypeName, histoCountsLength, insideOf); - } - } - - private void processLeafLocation(SnapshotDeep ss, String name, String dataTypeName, int histoCountsLength) { - // use hash index to find location (fast path) - int i = locationIndex.get(name, -1); - if (i < 0) { - // if that does not work, then binary-search existing node (or create new one) and remember index in hash index - i = locations.findOrCreateChildInSorted(name); - locationIndex.put(name, i); - } - SnapshotDeep cs = locations.getChild(i); - // append data type info for this location, true to always print average size - SnapshotDeep child = cs.getOrCreateChild(dataTypeName, true, histoCountsLength); - child.addShallow(ss); - // count possibly eliminated allocations separately - if (ss.isPossiblyEliminatedAllocation()) - child.getOrCreateChild("").addShallow(ss); - } - - public void dumpSnapshotHeader(PrintWriter out, SnapshotRoot ss, String kind) { - out.println(); - //------ start with tear line - printlnTearLine(out, '='); - //------ Line #1 - out.print(kind + " allocation dump for "); - printNum(out, ss.getTime()); - out.print(" ms ("); - printTimePeriod(out, ss.getTime()); - out.println(")"); - //------ Line #2 - out.print("Allocated "); - if (config.isSize()) { - printNum(out, ss.getSize()); - out.print(" bytes in "); - } - printNum(out, ss.getTotalCount()); - out.print(" objects of "); - printNum(out, ss.countNonEmptyChildrenShallow()); - out.print(" classes in "); - printNum(out, ss.countNonEmptyLeafs()); - out.println(" locations"); - //------ Line #3 (optional) - if (config.isCheckEliminateAllocation()) { - countPossibleEliminatedAllocations(ss); - out.print("HotSpot had possibly eliminated allocation of "); - if (config.isSize()) { - printNumPercent(out, possiblyEliminatedAllocations.getSize(), ss.getSize()); - out.print(" bytes in "); - } - printNumPercent(out, possiblyEliminatedAllocations.getTotalCount(), ss.getTotalCount()); - out.println(" objects"); - } - //------ end with tear line - printlnTearLine(out, '='); - out.println(); - } - - private void countPossibleEliminatedAllocations(SnapshotRoot ss) { - possiblyEliminatedAllocations.clearShallow(); - countPossibleEliminatedAllocationsRec(ss); - } - - private void countPossibleEliminatedAllocationsRec(SnapshotDeep ss) { - if (ss.isPossiblyEliminatedAllocation()) { - possiblyEliminatedAllocations.addShallow(ss); - return; - } - for (int i = 0; i < ss.getUsed(); i++) - countPossibleEliminatedAllocationsRec(ss.getChild(i)); - } - - public void dumpSnapshotByDataTypes(PrintWriter out, SnapshotRoot ss) { - // compute class levels -- classes of level 0 are classes that exceed threshold - classLevel.fill(Integer.MAX_VALUE); - for (int csi = 0; csi < ss.getUsed(); csi++) { - SnapshotDeep cs = ss.getChild(csi); - classLevel.put(cs.getName(), cs.exceedsThreshold(ss, config.getThreshold()) ? 0 : Integer.MAX_VALUE); - } - // compute progressive higher levels - for (int level = 0; level < config.getLevel(); level++) - for (int csi = 0; csi < ss.getUsed(); csi++) { - SnapshotDeep cs = ss.getChild(csi); - if (classLevel.get(cs.getName()) == level) - markClassLevelRec(cs, level); - } - - // dump classes - int cskipped = 0; - rest[0].clearShallow(); - for (int csi = 0; csi < ss.getUsed(); csi++) { - SnapshotDeep cs = ss.getChild(csi); - if (!cs.isEmpty() && classLevel.get(cs.getName()) <= config.getLevel()) { - out.print(cs.getName()); - printlnDetailsShallow(out, cs, ss, true, cs.isPossiblyEliminatedAllocation()); - printLocationsDeep(out, 1, cs, ss); - out.println(); - } else if (!cs.isEmpty()) { - cskipped++; - rest[0].addShallow(cs); - } - } - if (cskipped > 0) { - out.print("... "); - printNum(out, cskipped); - out.print(" more below threshold"); - printlnDetailsShallow(out, rest[0], ss, true, false); - } - } - - private void printlnDetailsShallow(PrintWriter out, SnapshotShallow item, SnapshotShallow total, boolean printAvg, - boolean possiblyEliminated) - { - out.print(": "); - if (config.isSize()) { - printNumPercent(out, item.getSize(), total.getSize()); - out.print(" bytes in "); - } - printNumPercent(out, item.getTotalCount(), total.getTotalCount()); - out.print(" objects"); - if (printAvg) { - out.print(" "); - printAvg(out, item.getSize(), item.getTotalCount()); - } - long[] counts = item.getHistoCounts(); - if (counts.length > 0 && item.getTotalCount() > 0) { - int lastNonZero = counts.length - 1; - while (lastNonZero > 0 && counts[lastNonZero] == 0) - lastNonZero--; - if (counts[lastNonZero] != 0) { - out.print(" [histogram: "); - printNum(out, item.getCount()); // smallest bracket first - for (int i = 0; i <= lastNonZero; i++) { - out.print(" "); - printNum(out, counts[i]); - } - out.print("]"); - } - } - if (possiblyEliminated) - out.print("; possibly eliminated"); - out.println(); - } - - private static void printIndent(PrintWriter out, int depth) { - for (int j = 0; j < depth; j++) - out.print("\t"); - } - - private void markClassLevelRec(SnapshotDeep ss, int level) { - for (int i = 0; i < ss.getUsed(); i++) { - SnapshotDeep item = ss.getChild(i); - String className = item.getName(); - if (item.exceedsThreshold(ss, config.getThreshold()) && className != null) { - int oldLevel = classLevel.get(className); - if (oldLevel > level + 1) - classLevel.put(className, level + 1); - } - if (item.hasChildren()) - markClassLevelRec(item, level); - } - } - - private void printLocationsDeep(PrintWriter out, int depth, SnapshotDeep ss, SnapshotShallow total) { - // count how many below threshold (1st pass) - int shown = 0; - int skipped = 0; - for (int i = 0; i < ss.getUsed(); i++) { - SnapshotDeep item = ss.getChild(i); - if (item.isEmpty()) - continue; // ignore empty items - // always show 1st item and all that exceed threshold - if (shown == 0 || item.exceedsThreshold(total, config.getThreshold())) - shown++; - else - skipped++; - } - boolean printAll = skipped <= 2; // avoid ... 1 more and ... 2 more messages - - // print (2nd pass) - shown = 0; - skipped = 0; - rest[depth].clearShallow(); - for (int i = 0; i < ss.getUsed(); i++) { - SnapshotDeep item = ss.getChild(i); - if (item.isEmpty()) - continue; // ignore empty items - if (shown == 0 || printAll || item.exceedsThreshold(total, config.getThreshold())) { - shown++; - printIndent(out, depth); - out.print(item.getName()); - printlnDetailsShallow(out, item, total, item.isArray(), item.isPossiblyEliminatedAllocation()); - if (item.hasChildren()) - printLocationsDeep(out, depth + 1, item, total); - if (depth == 0) - out.println(); // empty lines on top level - } else { - skipped++; - rest[depth].addShallow(item); - } - } - if (skipped > 0) { - printIndent(out, depth); - out.print("... "); - printNum(out, skipped); - out.print(" more below threshold"); - printlnDetailsShallow(out, rest[depth], total, ss.isArray(), false); - if (depth == 0) - out.println(); // empty lines on top level - } - } +public interface DumpFormatter { + void dumpSnapshot(PrintWriter out, SnapshotRoot ss, String kind); + long dumpReportHeader(PrintWriter out, long now, long start, String argsStr, int snapshotCount, int overflowCount); } diff --git a/core/src/main/java/com/devexperts/aprof/dump/Dumper.java b/core/src/main/java/com/devexperts/aprof/dump/Dumper.java index 57697c5..9934f0e 100644 --- a/core/src/main/java/com/devexperts/aprof/dump/Dumper.java +++ b/core/src/main/java/com/devexperts/aprof/dump/Dumper.java @@ -52,7 +52,8 @@ public Dumper(Configuration config, long start) { this.config = config; this.argsStr = config.toString(); this.start = start; - this.formatter = new DumpFormatter(config); + this.formatter = config.getDumpFormat().equals("folded") ? + new FoldedStacksDumpFormatter(config) : new TextDumpFormatter(config); } public synchronized void makeOverflowSnapshot() { @@ -137,7 +138,7 @@ private String resize(String str, int length) { private void dumpAll(PrintWriter out, boolean dumpAll) { long now = System.currentTimeMillis(); - dumpReportHeader(out, now); + formatter.dumpReportHeader(out, now, start, argsStr, snapshotCount, overflowCount); if (!dumpAll) { last.setTime(now - lastTime); @@ -147,44 +148,7 @@ private void dumpAll(PrintWriter out, boolean dumpAll) { total.setTime(now - start); formatter.dumpSnapshot(out, total, "TOTAL"); - out.println(); } - private long dumpReportHeader(PrintWriter out, long now) { - long uptime = now - start; - //------ start with tear line - printlnTearLine(out, '#'); - //------ Line #1 - out.println(Version.full()); - //------ Line #2 - out.print("Allocation dump at "); - printTimeAndDate(out, System.currentTimeMillis()); - out.print(". Uptime "); - printNum(out, uptime); - out.print(" ms ("); - printTimePeriod(out, uptime); - out.println(")"); - //------ Line #3 - out.print("Arguments "); - out.println(argsStr); - //------ Line #4 - out.print("Transformed "); - printNum(out, AProfRegistry.getCount()); - out.print(" classes and registered "); - printNum(out, AProfRegistry.getLocationCount()); - out.print(" locations in "); - FastFmtUtil.printNumPercent(out, AProfRegistry.getTime(), uptime); - out.print(" ms"); - out.println(); - //------ Line #4 - out.print("Snapshot of counters was made "); - printNum(out, snapshotCount); - out.print(" times to write file and "); - printNum(out, overflowCount); - out.println(" times to prevent overflow"); - //------ end with tear line - printlnTearLine(out, '#'); - return uptime; - } } diff --git a/core/src/main/java/com/devexperts/aprof/dump/FoldedStacksDumpFormatter.java b/core/src/main/java/com/devexperts/aprof/dump/FoldedStacksDumpFormatter.java new file mode 100644 index 0000000..68f0423 --- /dev/null +++ b/core/src/main/java/com/devexperts/aprof/dump/FoldedStacksDumpFormatter.java @@ -0,0 +1,113 @@ +package com.devexperts.aprof.dump; + +/*- + * #%L + * Aprof Core + * %% + * Copyright (C) 2002 - 2020 Devexperts, LLC + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.devexperts.aprof.Configuration; + +import java.io.PrintWriter; + +public class FoldedStacksDumpFormatter implements DumpFormatter { + + private final ThreadLocal buffer = new ThreadLocal() { + @Override + protected StringBuilder initialValue() { + return new StringBuilder(1024); + } + }; + + private final double threshold; + private final boolean collectSizes; + private final boolean skipTotal; + + + public FoldedStacksDumpFormatter(Configuration config) { + threshold = config.getThreshold(); + collectSizes = config.isSize(); + skipTotal = config.getFilecount() > 1; + } + + + public void dumpSnapshot(PrintWriter out, SnapshotRoot root, String kind) { + if (skipTotal && "TOTAL".equals(kind)) + return; + + StringBuilder sb = buffer.get(); + sb.setLength(0); + + printNode(out, root, sb, root, 0, kind); + } + + public long dumpReportHeader(PrintWriter out, long now, long start, String argsStr, int snapshotCount, int overflowCount) { + return 0; + } + + private long getSize(SnapshotDeep node) { + return collectSizes ? node.getSize() : node.getCount(); + } + + private long printNode(PrintWriter out, SnapshotDeep node, StringBuilder prefix, SnapshotRoot root, int depth, String rootName) { + if (node.isEmpty() || !node.exceedsThreshold(root, threshold)) + return 0; + + int prefixLen = prefix.length(); + try { + String suffix = "_[j]"; + if (depth == 1) { + suffix = "_[k]"; // it's a type + } + if (node.isPossiblyEliminatedAllocation()) { + suffix = "_[i]"; //todo: figure out whether it's top or last node + } + //todo: make sure is marked in red + + String nodeName = depth == 0 ? rootName : node.getName(); + if (depth == 0 || SnapshotDeep.UNKNOWN.equals(nodeName)) + suffix = ""; + + if (nodeName != null) + prefix.append(nodeName).append(suffix).append(';'); + + int ccnt = node.getUsed(); + long childrenSize = 0; + for (int i = 0; i < ccnt; i++) { + SnapshotDeep child = node.getChild(i); + childrenSize += printNode(out, child, prefix, root, depth + 1, rootName); + } + + long exclusiveSize = getSize(node) - childrenSize; + if (exclusiveSize > 0 && nodeName != null) { + prefix.setLength(prefixLen); + out.print(prefix); + out.print(nodeName); + out.print(suffix); + out.print(' '); + out.println(exclusiveSize); + } + + return getSize(node); + + } finally { + prefix.setLength(prefixLen); + } + } +} diff --git a/core/src/main/java/com/devexperts/aprof/dump/TextDumpFormatter.java b/core/src/main/java/com/devexperts/aprof/dump/TextDumpFormatter.java new file mode 100644 index 0000000..1ee53e6 --- /dev/null +++ b/core/src/main/java/com/devexperts/aprof/dump/TextDumpFormatter.java @@ -0,0 +1,365 @@ +package com.devexperts.aprof.dump; + +/*- + * #%L + * Aprof Core + * %% + * Copyright (C) 2002 - 2017 Devexperts, LLC + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.io.PrintWriter; +import java.util.Comparator; + +import com.devexperts.aprof.AProfRegistry; +import com.devexperts.aprof.Configuration; +import com.devexperts.aprof.Version; +import com.devexperts.aprof.util.FastFmtUtil; +import com.devexperts.aprof.util.FastObjIntMap; + +import static com.devexperts.aprof.util.FastFmtUtil.*; + +/** + * Formats collected dump snapshots. + * This class is not thread-safe. + * + * @author Denis Davydov + */ +public class TextDumpFormatter implements DumpFormatter { + private static final int MAX_DEPTH = 5; + + private final Configuration config; + + private final SnapshotShallow possiblyEliminatedAllocations = new SnapshotShallow(); + private final SnapshotShallow[] rest = new SnapshotShallow[MAX_DEPTH]; + private final FastObjIntMap classLevel = new FastObjIntMap(); + private final FastObjIntMap locationIndex = new FastObjIntMap(); + private final SnapshotDeep locations; + + public TextDumpFormatter(Configuration config) { + this.config = config; + for (int i = 0; i < MAX_DEPTH; i++) + rest[i] = new SnapshotShallow(); + locations = new SnapshotDeep(null, true, 0); // true to print avg size, 0 to skip printing histogram + } + + public void dumpSnapshot(PrintWriter out, SnapshotRoot ss, String kind) { + dumpSnapshotHeader(out, ss, kind); + out.println("Top allocation-inducing locations with data types allocated from them"); + printlnTearLine(out, '-'); + dumpSnapshotByLocations(out, ss, SnapshotDeep.UNKNOWN); + if (AProfRegistry.TRACK_TRANSFORM_DETAILS) { + out.println("Top allocation-inducing locations inside transform with data types allocated from them"); + printlnTearLine(out, '-'); + dumpSnapshotByLocations(out, ss, AProfRegistry.TRANSFORM_NAME); + } + out.println("Top allocated data types with reverse location traces"); + printlnTearLine(out, '-'); + ss.sortChildrenDeep(getOutputComparator()); + dumpSnapshotByDataTypes(out, ss); + out.println(); + } + + private Comparator getOutputComparator() { + return config.isSize() ? SnapshotShallow.COMPARATOR_SIZE : SnapshotShallow.COMPARATOR_COUNT; + } + + private void dumpSnapshotByLocations(PrintWriter out, SnapshotRoot ss, String insideOf) { + // rebuild locations + rebuildLocations(ss, insideOf); + // sort them and print + locations.sortChildrenDeep(getOutputComparator()); + printLocationsDeep(out, 0, locations, ss); + } + + protected SnapshotDeep rebuildLocations(SnapshotRoot ss, String insideOf) { + locations.clearDeep(); + locations.sortChildrenDeep(SnapshotShallow.COMPARATOR_NAME); + locationIndex.fill(-1); + for (int i = 0; i < ss.getUsed(); i++) { + SnapshotDeep cs = ss.getChild(i); + String dataTypeName = cs.getName(); + findLocationsDeep(cs, dataTypeName, cs.getHistoCountsLength(), insideOf); + } + locations.updateSnapshotSumDeep(); + return locations; + } + + private void findLocationsDeep(SnapshotDeep ss, String dataTypeName, int histoCountsLength, String insideOf) { + if (!ss.hasChildren() && insideOf.equals(SnapshotDeep.UNKNOWN)) { + processLeafLocation(ss, AProfRegistry.getLocationNameWithoutSuffix(ss.getName()), dataTypeName, histoCountsLength); + return; + } + // has children -- go recursive with a special treatment for UNKNOWN children -- attribute them this location's name + for (int i = 0; i < ss.getUsed(); i++) { + SnapshotDeep cs = ss.getChild(i); + if (cs.getName().equals(insideOf)) { + assert !cs.hasChildren() : insideOf + " location shall not have children"; + processLeafLocation(cs, AProfRegistry.getLocationNameWithoutSuffix(ss.getName()), dataTypeName, histoCountsLength); + } else + findLocationsDeep(cs, dataTypeName, histoCountsLength, insideOf); + } + } + + private void processLeafLocation(SnapshotDeep ss, String name, String dataTypeName, int histoCountsLength) { + // use hash index to find location (fast path) + int i = locationIndex.get(name, -1); + if (i < 0) { + // if that does not work, then binary-search existing node (or create new one) and remember index in hash index + i = locations.findOrCreateChildInSorted(name); + locationIndex.put(name, i); + } + SnapshotDeep cs = locations.getChild(i); + // append data type info for this location, true to always print average size + SnapshotDeep child = cs.getOrCreateChild(dataTypeName, true, histoCountsLength); + child.addShallow(ss); + // count possibly eliminated allocations separately + if (ss.isPossiblyEliminatedAllocation()) + child.getOrCreateChild("").addShallow(ss); + } + + public void dumpSnapshotHeader(PrintWriter out, SnapshotRoot ss, String kind) { + out.println(); + //------ start with tear line + printlnTearLine(out, '='); + //------ Line #1 + out.print(kind + " allocation dump for "); + printNum(out, ss.getTime()); + out.print(" ms ("); + printTimePeriod(out, ss.getTime()); + out.println(")"); + //------ Line #2 + out.print("Allocated "); + if (config.isSize()) { + printNum(out, ss.getSize()); + out.print(" bytes in "); + } + printNum(out, ss.getTotalCount()); + out.print(" objects of "); + printNum(out, ss.countNonEmptyChildrenShallow()); + out.print(" classes in "); + printNum(out, ss.countNonEmptyLeafs()); + out.println(" locations"); + //------ Line #3 (optional) + if (config.isCheckEliminateAllocation()) { + countPossibleEliminatedAllocations(ss); + out.print("HotSpot had possibly eliminated allocation of "); + if (config.isSize()) { + printNumPercent(out, possiblyEliminatedAllocations.getSize(), ss.getSize()); + out.print(" bytes in "); + } + printNumPercent(out, possiblyEliminatedAllocations.getTotalCount(), ss.getTotalCount()); + out.println(" objects"); + } + //------ end with tear line + printlnTearLine(out, '='); + out.println(); + } + + private void countPossibleEliminatedAllocations(SnapshotRoot ss) { + possiblyEliminatedAllocations.clearShallow(); + countPossibleEliminatedAllocationsRec(ss); + } + + private void countPossibleEliminatedAllocationsRec(SnapshotDeep ss) { + if (ss.isPossiblyEliminatedAllocation()) { + possiblyEliminatedAllocations.addShallow(ss); + return; + } + for (int i = 0; i < ss.getUsed(); i++) + countPossibleEliminatedAllocationsRec(ss.getChild(i)); + } + + public void dumpSnapshotByDataTypes(PrintWriter out, SnapshotRoot ss) { + // compute class levels -- classes of level 0 are classes that exceed threshold + classLevel.fill(Integer.MAX_VALUE); + for (int csi = 0; csi < ss.getUsed(); csi++) { + SnapshotDeep cs = ss.getChild(csi); + classLevel.put(cs.getName(), cs.exceedsThreshold(ss, config.getThreshold()) ? 0 : Integer.MAX_VALUE); + } + // compute progressive higher levels + for (int level = 0; level < config.getLevel(); level++) + for (int csi = 0; csi < ss.getUsed(); csi++) { + SnapshotDeep cs = ss.getChild(csi); + if (classLevel.get(cs.getName()) == level) + markClassLevelRec(cs, level); + } + + // dump classes + int cskipped = 0; + rest[0].clearShallow(); + for (int csi = 0; csi < ss.getUsed(); csi++) { + SnapshotDeep cs = ss.getChild(csi); + if (!cs.isEmpty() && classLevel.get(cs.getName()) <= config.getLevel()) { + out.print(cs.getName()); + printlnDetailsShallow(out, cs, ss, true, cs.isPossiblyEliminatedAllocation()); + printLocationsDeep(out, 1, cs, ss); + out.println(); + } else if (!cs.isEmpty()) { + cskipped++; + rest[0].addShallow(cs); + } + } + if (cskipped > 0) { + out.print("... "); + printNum(out, cskipped); + out.print(" more below threshold"); + printlnDetailsShallow(out, rest[0], ss, true, false); + } + } + + private void printlnDetailsShallow(PrintWriter out, SnapshotShallow item, SnapshotShallow total, boolean printAvg, + boolean possiblyEliminated) + { + out.print(": "); + if (config.isSize()) { + printNumPercent(out, item.getSize(), total.getSize()); + out.print(" bytes in "); + } + printNumPercent(out, item.getTotalCount(), total.getTotalCount()); + out.print(" objects"); + if (printAvg) { + out.print(" "); + printAvg(out, item.getSize(), item.getTotalCount()); + } + long[] counts = item.getHistoCounts(); + if (counts.length > 0 && item.getTotalCount() > 0) { + int lastNonZero = counts.length - 1; + while (lastNonZero > 0 && counts[lastNonZero] == 0) + lastNonZero--; + if (counts[lastNonZero] != 0) { + out.print(" [histogram: "); + printNum(out, item.getCount()); // smallest bracket first + for (int i = 0; i <= lastNonZero; i++) { + out.print(" "); + printNum(out, counts[i]); + } + out.print("]"); + } + } + if (possiblyEliminated) + out.print("; possibly eliminated"); + out.println(); + } + + private static void printIndent(PrintWriter out, int depth) { + for (int j = 0; j < depth; j++) + out.print("\t"); + } + + private void markClassLevelRec(SnapshotDeep ss, int level) { + for (int i = 0; i < ss.getUsed(); i++) { + SnapshotDeep item = ss.getChild(i); + String className = item.getName(); + if (item.exceedsThreshold(ss, config.getThreshold()) && className != null) { + int oldLevel = classLevel.get(className); + if (oldLevel > level + 1) + classLevel.put(className, level + 1); + } + if (item.hasChildren()) + markClassLevelRec(item, level); + } + } + + private void printLocationsDeep(PrintWriter out, int depth, SnapshotDeep ss, SnapshotShallow total) { + // count how many below threshold (1st pass) + int shown = 0; + int skipped = 0; + for (int i = 0; i < ss.getUsed(); i++) { + SnapshotDeep item = ss.getChild(i); + if (item.isEmpty()) + continue; // ignore empty items + // always show 1st item and all that exceed threshold + if (shown == 0 || item.exceedsThreshold(total, config.getThreshold())) + shown++; + else + skipped++; + } + boolean printAll = skipped <= 2; // avoid ... 1 more and ... 2 more messages + + // print (2nd pass) + shown = 0; + skipped = 0; + rest[depth].clearShallow(); + for (int i = 0; i < ss.getUsed(); i++) { + SnapshotDeep item = ss.getChild(i); + if (item.isEmpty()) + continue; // ignore empty items + if (shown == 0 || printAll || item.exceedsThreshold(total, config.getThreshold())) { + shown++; + printIndent(out, depth); + out.print(item.getName()); + printlnDetailsShallow(out, item, total, item.isArray(), item.isPossiblyEliminatedAllocation()); + if (item.hasChildren()) + printLocationsDeep(out, depth + 1, item, total); + if (depth == 0) + out.println(); // empty lines on top level + } else { + skipped++; + rest[depth].addShallow(item); + } + } + if (skipped > 0) { + printIndent(out, depth); + out.print("... "); + printNum(out, skipped); + out.print(" more below threshold"); + printlnDetailsShallow(out, rest[depth], total, ss.isArray(), false); + if (depth == 0) + out.println(); // empty lines on top level + } + } + + public long dumpReportHeader(PrintWriter out, long now, long start, String argsStr, int snapshotCount, int overflowCount) { + long uptime = now - start; + //------ start with tear line + printlnTearLine(out, '#'); + //------ Line #1 + out.println(Version.full()); + //------ Line #2 + out.print("Allocation dump at "); + printTimeAndDate(out, System.currentTimeMillis()); + out.print(". Uptime "); + printNum(out, uptime); + out.print(" ms ("); + printTimePeriod(out, uptime); + out.println(")"); + //------ Line #3 + out.print("Arguments "); + out.println(argsStr); + //------ Line #4 + out.print("Transformed "); + printNum(out, AProfRegistry.getCount()); + out.print(" classes and registered "); + printNum(out, AProfRegistry.getLocationCount()); + out.print(" locations in "); + FastFmtUtil.printNumPercent(out, AProfRegistry.getTime(), uptime); + out.print(" ms"); + out.println(); + //------ Line #4 + out.print("Snapshot of counters was made "); + printNum(out, snapshotCount); + out.print(" times to write file and "); + printNum(out, overflowCount); + out.println(" times to prevent overflow"); + //------ end with tear line + printlnTearLine(out, '#'); + return uptime; + } + + +} diff --git a/selftest/src/main/java/com/devexperts/aprof/selftest/TestSuite.java b/selftest/src/main/java/com/devexperts/aprof/selftest/TestSuite.java index fa96842..3f5320b 100644 --- a/selftest/src/main/java/com/devexperts/aprof/selftest/TestSuite.java +++ b/selftest/src/main/java/com/devexperts/aprof/selftest/TestSuite.java @@ -26,7 +26,7 @@ import com.devexperts.aprof.AProfTools; import com.devexperts.aprof.Configuration; import com.devexperts.aprof.Version; -import com.devexperts.aprof.dump.DumpFormatter; +import com.devexperts.aprof.dump.TextDumpFormatter; import com.devexperts.aprof.dump.SnapshotDeep; import com.devexperts.aprof.dump.SnapshotRoot; import com.devexperts.aprof.dump.SnapshotShallow; @@ -199,7 +199,7 @@ private static String snapshotToString(Configuration config, SnapshotRoot snapsh ByteArrayOutputStream bos = new ByteArrayOutputStream(); PrintWriter out = new PrintWriter(bos); snapshot.sortChildrenDeep(SnapshotShallow.COMPARATOR_NAME); - new DumpFormatter(config).dumpSnapshotByDataTypes(out, snapshot); + new TextDumpFormatter(config).dumpSnapshotByDataTypes(out, snapshot); out.flush(); return new String(bos.toByteArray()); } diff --git a/transformer/src/main/java/com/devexperts/aprof/transformer/Context.java b/transformer/src/main/java/com/devexperts/aprof/transformer/Context.java index 127acdb..97b674d 100644 --- a/transformer/src/main/java/com/devexperts/aprof/transformer/Context.java +++ b/transformer/src/main/java/com/devexperts/aprof/transformer/Context.java @@ -43,6 +43,7 @@ class Context { private final boolean objectInit; private final boolean intrinsicArraysCopyOf; private final String aprofOpsImpl; + private final boolean verifierDisabled; private String location; // lazily computed on first get @@ -65,6 +66,7 @@ public Context(Configuration config, ClassInfoCache ciCache, ClassLoader loader, this.objectInit = locationClass.equals(TransformerUtil.OBJECT_CLASS_NAME) && mname.equals(TransformerUtil.INIT); this.intrinsicArraysCopyOf = TransformerUtil.isIntrinsicArraysCopyOf(binaryClassName, mname, desc); this.aprofOpsImpl = isInternalLocation() ? TransformerUtil.APROF_OPS_INTERNAL : TransformerUtil.APROF_OPS; + this.verifierDisabled = config.isVerifierDisabled(); } /** @@ -110,7 +112,7 @@ public boolean isMethodInvocationTracked(String cname, int opcode, String owner, { if (owner.startsWith("[")) return false; // do not track array method invocations - if (transformedClassVersion >= Opcodes.V1_7 && TransformerUtil.INIT.equals(name)) + if (!verifierDisabled && transformedClassVersion >= Opcodes.V1_7 && TransformerUtil.INIT.equals(name)) return false; // invocations don't pass verifier in modern JVMs if (config.isMethodTracked(cname, name)) return true; // direct invocation of tracked method through its actual class