| /* MemoryAnalyze.java -- Analyzes a libgcj heap dump. |
| Copyright (C) 2007 Free Software Foundation |
| |
| This file is part of libgcj. |
| |
| This software is copyrighted work licensed under the terms of the |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for |
| details. */ |
| |
| package gnu.gcj.tools.gc_analyze; |
| |
| import gnu.classpath.tools.getopt.FileArgumentCallback; |
| import gnu.classpath.tools.getopt.Option; |
| import gnu.classpath.tools.getopt.OptionException; |
| import gnu.classpath.tools.getopt.Parser; |
| |
| import java.io.BufferedReader; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.text.NumberFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| class MemoryAnalyze |
| { |
| public MemoryAnalyze() |
| { |
| } |
| |
| private static NumberFormat numberFormat; |
| private static boolean verbose; |
| static String format(long number, int digits) |
| { |
| if (numberFormat == null) |
| { |
| numberFormat = NumberFormat.getNumberInstance(); |
| numberFormat.setGroupingUsed(true); |
| } |
| String temp = numberFormat.format(number); |
| int spaces = digits - temp.length(); |
| if (spaces < 0) |
| spaces = 0; |
| return " ".substring(0,spaces) + temp; |
| } |
| |
| static void sorted_report(String description, |
| int total_space, |
| ArrayList<String> list, |
| Comparator<String> comparator) |
| { |
| System.out.println("*** " + description + " ***"); |
| System.out.println(); |
| System.out.println(" Total Size Count Size Description"); |
| System.out.println("-------------- ----- -------- -----------------------------------"); |
| Collections.sort(list, comparator); |
| for (Iterator it = list.iterator(); it.hasNext(); ) |
| { |
| String v = (String)it.next(); |
| System.out.println(stripend(v)); |
| } |
| System.out.println("-------------- ----- -------- -----------------------------------"); |
| System.out.println(format(total_space, 14)); |
| System.out.println(); |
| System.out.println(); |
| } |
| |
| private static String stripend(String s) |
| { |
| int n = s.lastIndexOf(" /"); |
| if (n > 0) |
| return s.substring(0,n); |
| return s; |
| } |
| |
| static class SubstringComparator implements Comparator<String> |
| { |
| private int begin, end; |
| private boolean reverse; |
| |
| SubstringComparator(int begin, int end, boolean reverse) |
| { |
| this.begin = begin; |
| this.end = end; |
| this.reverse = reverse; |
| } |
| |
| public int compare(String s1, String s2) |
| { |
| if (end == 0) |
| s1 = s1.substring(begin); |
| else |
| s1 = s1.substring(begin, end); |
| |
| if (end == 0) |
| s2 = s2.substring(begin); |
| else |
| s2 = s2.substring(begin, end); |
| int i = s1.compareTo(s2); |
| if (reverse) |
| return -i; |
| return i; |
| } |
| } |
| |
| static class OptionParser extends Parser |
| { |
| int filesFound; |
| |
| OptionParser() |
| { |
| super("gc-analyze", |
| "gc-analyze (" + System.getProperty("java.vm.version") + ")"); |
| |
| add(new Option('d', |
| "Directory containing runtime objects", |
| "directory") |
| { |
| public void parsed(String argument) throws OptionException |
| { |
| ToolPrefix.pathPrefix = argument; |
| } |
| }); |
| |
| add(new Option('p', |
| "Binary tool prefix, prepended to nm and readelf to " |
| + "obtain target specific versions of these commands", |
| "prefix") |
| { |
| public void parsed(String argument) throws OptionException |
| { |
| ToolPrefix.toolPrefix = argument; |
| } |
| }); |
| |
| add(new Option("verbose", 'v', |
| "Verbose output; requires filename.bytes") |
| { |
| public void parsed(String argument) throws OptionException |
| { |
| verbose = true; |
| } |
| }); |
| |
| setHeader("usage: gc-analyze [-v] [-p tool-prefix] [-d <directory>] " |
| + "filename"); |
| } |
| |
| protected void validate() throws OptionException |
| { |
| if (filesFound != 1) |
| throw new OptionException("Must specify exactly one filename"); |
| } |
| |
| public String[] parse(String[] inArgs) |
| { |
| final ArrayList<String> fileResult = new ArrayList<String>(); |
| parse(inArgs, new FileArgumentCallback() |
| { |
| public void notifyFile(String fileArgument) |
| { |
| filesFound++; |
| fileResult.add(fileArgument); |
| } |
| }); |
| return fileResult.toArray(new String[1]); |
| } |
| } |
| |
| public static void main(String[] args) |
| { |
| class Info |
| { |
| int size; |
| int count; |
| } |
| int total_space = 0; |
| |
| Parser optionParser = new OptionParser(); |
| |
| String rest[] = optionParser.parse(args); |
| |
| String filename = rest[0]; |
| |
| try |
| { |
| BufferedReader reader = |
| new BufferedReader(new InputStreamReader(new FileInputStream(filename))); |
| SymbolLookup lookup = new SymbolLookup(reader, filename + ".bytes"); |
| ObjectMap objectMap = new ObjectMap(reader); |
| BlockMap blockMap = new BlockMap(reader); |
| reader.close(); |
| |
| // add info to item(s) |
| // add item.klass |
| for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap) |
| { |
| ObjectMap.ObjectItem item = me.getValue(); |
| |
| // try to get a klass (happens with intern'ed strings...) |
| if (item.klass==0) |
| { |
| BytePtr p = lookup.getBytePtr(item.ptr, item.size); |
| if (p!=null) |
| { |
| long vtable = p.getWord(0); |
| String sym = |
| lookup.getSymbolViaVtable(vtable - 2 * lookup.memoryMap.wordSize); |
| if (sym != null) |
| { |
| item.typeName = SymbolTable.demangleVTName(sym); |
| } |
| else if (vtable != 0) |
| { |
| // get klass from vtable |
| p = lookup.getBytePtr(vtable, |
| lookup.memoryMap.wordSize); |
| if (p != null) |
| { |
| long klass = p.getWord(0); |
| item.klass = klass; |
| } |
| } |
| } |
| } |
| |
| // figure out strings |
| String class_name; |
| if (null == item.typeName) |
| { |
| class_name = |
| MemoryAnalyze.getSymbolPretty(lookup, item, false); |
| item.typeName = class_name; |
| } |
| else |
| { |
| class_name = item.typeName; |
| } |
| System.out.print("class_name=[" + class_name + "]"); |
| |
| if (class_name.compareTo("_ZTVN4java4lang6StringE")==0 |
| || class_name.compareTo("java.lang.String")==0) |
| { |
| BytePtr p = lookup.getBytePtr(item.ptr, item.size); |
| long data = p.getWord(1); |
| int boffset = p.getInt(2 * p.intsPerWord()); |
| int count = p.getInt(1 + 2 * p.intsPerWord()); |
| int hash = p.getInt(2 + 2 * p.intsPerWord()); |
| BytePtr chars = lookup.getBytePtr(data+boffset, count * 2); |
| StringBuffer sb = new StringBuffer(count); |
| for (int qq = 0; qq<count; qq++) |
| sb.append((char)chars.getShort(qq)); |
| int newhash = sb.toString().hashCode(); |
| if (newhash!=hash) |
| { |
| p.setInt(4, newhash); |
| } |
| |
| item.string = sb.toString(); |
| System.out.println(" value = \"" + item.string + "\""); |
| if (data != item.ptr) |
| { |
| ObjectMap.ObjectItem next = objectMap.get(data); |
| if (next != null) |
| next.stringData = true; |
| else |
| System.out.println("String [" + item.string + "] at " |
| + Long.toHexString(item.ptr) |
| + " can't find array at " |
| + Long.toHexString(data)); |
| } |
| } |
| else if (null != item.string) |
| System.out.println(" value = \"" + item.string + "\""); |
| else |
| System.out.println(); |
| } |
| |
| |
| HashMap<String, Info> map = new HashMap<String, Info>(); |
| for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap) |
| { |
| ObjectMap.ObjectItem item = me.getValue(); |
| String name = getSymbolPretty(lookup, item, true); |
| Info info = map.get(name); |
| if (info == null) |
| { |
| info = new Info(); |
| info.count = 0; |
| info.size = item.size; |
| map.put(name, info); |
| } |
| info.count++; |
| total_space += item.size; |
| } |
| |
| ArrayList<String> list = new ArrayList<String>(); |
| for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) |
| { |
| Map.Entry me = (Map.Entry)it.next(); |
| String name = (String)me.getKey(); |
| Info info = (Info)me.getValue(); |
| |
| StringBuffer sb = new StringBuffer(); |
| sb.append(format(info.count * info.size * 100 / total_space, |
| 3)); |
| sb.append("%"); |
| sb.append(format(info.count * info.size, 10)); |
| sb.append(" = "); |
| sb.append(format(info.count, 7)); |
| sb.append(" * "); |
| sb.append(format(info.size, 9)); |
| sb.append(" - "); |
| sb.append(name); |
| list.add(sb.toString()); |
| } |
| |
| sorted_report("Memory Usage Sorted by Total Size", |
| total_space, list, new SubstringComparator(5,14,true)); |
| sorted_report("Memory Usage Sorted by Description", |
| total_space, list, new SubstringComparator(39,0,false)); |
| sorted_report("Memory Usage Sorted by Count", |
| total_space, list, new SubstringComparator(17,25,true)); |
| sorted_report("Memory Usage Sorted by Size", |
| total_space, list, new SubstringComparator(28,37,true)); |
| |
| blockMap.dump(); |
| |
| // dump raw memory |
| if (verbose) |
| { |
| // analyze references |
| for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap) |
| { |
| long ptr = me.getKey(); |
| ObjectMap.ObjectItem item = me.getValue(); |
| BytePtr p = lookup.getBytePtr(ptr, item.size); |
| if (p == null) |
| System.out.println("can't find ptr 0x" |
| + Long.toHexString(ptr)); |
| else if (item.kind != 0) // not GC_PTRFREE |
| for (int i = 1; |
| i < item.size / lookup.memoryMap.wordSize; i++) |
| { |
| long maybe_ptr = p.getWord(i); |
| ObjectMap.ObjectItem item2 = objectMap.get(maybe_ptr); |
| if (item2 != null) |
| { |
| item2.pointed_by.add(item); |
| item.points_to.add(item2); |
| } |
| } |
| } |
| System.out.println(); |
| System.out.println("*** All Objects ***"); |
| System.out.println(); |
| |
| for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap) |
| { |
| long ptr = me.getKey(); |
| ObjectMap.ObjectItem item = me.getValue(); |
| String name = getSymbolPretty(lookup, item, false); |
| System.out.print("0x" + Long.toHexString(ptr) + " - " + name |
| + " (" + item.size + ")"); |
| if (item.string != null) |
| System.out.println(" \"" + item.string + "\""); |
| else |
| System.out.println(); |
| |
| BytePtr p = lookup.getBytePtr(ptr, item.size); |
| |
| if (p == null) |
| System.out.println( |
| "can't find memory; recently allocated from free list?"); |
| else |
| p.dump(); |
| |
| item.points_to.dump(" points to:", lookup); |
| item.pointed_by.dump(" pointed to by:", lookup); |
| System.out.println(); |
| } |
| } |
| } |
| catch (IOException e) |
| { |
| e.printStackTrace(); |
| } |
| } |
| |
| public static String kindToName(int kind) |
| { |
| String name; |
| switch (kind) |
| { |
| case 0: |
| name = "GC_PTRFREE"; |
| break; |
| case 1: |
| name = "GC_NORMAL"; |
| break; |
| case 2: |
| name = "GC_UNCOLLECTABLE"; |
| break; |
| case 3: |
| name = "GC_AUUNCOLLCTABLE"; |
| break; |
| case 4: |
| name = "(Java)"; |
| break; |
| case 5: |
| name = "(Java Debug)"; |
| break; |
| case 6: |
| name = "(Java Array)"; |
| break; |
| default: |
| name = "(Kind " + kind + ")"; |
| break; |
| } |
| return name; |
| } |
| |
| public static String getSymbolPretty(SymbolLookup lookup, |
| ObjectMap.ObjectItem item, |
| boolean bsize) |
| throws IOException |
| { |
| |
| String name = item.typeName; |
| |
| if (name == null) |
| name = lookup.getSymbol(item.klass); |
| |
| if (name == null) |
| { |
| String v = lookup.decodeUTF8(item.ptr, item.size); |
| if (null != v) |
| { |
| name = "UTF8Const"; |
| item.string = v; |
| } |
| } |
| |
| if (name == null) |
| { |
| name = kindToName(item.kind); |
| } |
| if (item.kind==6) |
| name += "[" + format(item.data, 0) + "]"; |
| if (bsize) |
| name = name + " / " + format(item.size, 7); |
| return name; |
| } |
| } |