/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * * Patrick C. Beard * * Alternatively, the contents of this file may be used under the * terms of the GNU Public License (the "GPL"), in which case the * provisions of the GPL are applicable instead of those above. * If you wish to allow use of your version of this file only * under the terms of the GPL and not to allow others to use your * version of this file under the NPL, indicate your decision by * deleting the provisions above and replace them with the notice * and other provisions required by the GPL. If you do not delete * the provisions above, a recipient may use your version of this * file under either the NPL or the GPL. */ import java.io.*; import java.util.*; class Leak extends Reference { String mName; long mCrawlOffset; short mCrawlCount; short mRefCount; short mChildCount; Leak[] mParents; int mTotalSize; boolean mMarked; Leak(String addr, Type type, Object[] refs, long crawlOffset, short crawlCount) { super(addr, type, refs); mName = addr; mCrawlOffset = crawlOffset; mCrawlCount = crawlCount; mRefCount = 0; mChildCount = 0; mParents = null; mTotalSize = 0; mMarked = false; } void setParents(Vector parents) { mParents = new Leak[parents.size()]; parents.copyInto(mParents); } void computeTotalSize() { // first, mark this node as having been visited. // we only want to include nodes that haven't been // visited in our total size. mTotalSize = mType.mSize; // then, visit all nodes that haven't been visited, // and include their total size in ours. int count = mReferences.length; for (int i = 0; i < count; ++i) { Object ref = mReferences[i]; if (ref instanceof Leak) { Leak leak = (Leak) ref; if (leak.mTotalSize == 0) { leak.computeTotalSize(); mTotalSize += leak.mTotalSize; } } } } void clearTotalSize() { // first, clear our total size. mTotalSize = 0; // then, visit all nodes that haven't been visited, // and clear each one's total size. int count = mReferences.length; for (int i = 0; i < count; ++i) { Object ref = mReferences[i]; if (ref instanceof Leak) { Leak leak = (Leak) ref; if (leak.mTotalSize != 0) leak.clearTotalSize(); } } } void clearMarks() { // first, clear mark. mMarked = false; // then, visit all nodes that haven't been visited, // and clear each one's mark. int count = mReferences.length; for (int i = 0; i < count; ++i) { Object ref = mReferences[i]; if (ref instanceof Leak) { Leak leak = (Leak) ref; if (leak.mMarked) leak.clearMarks(); } } } static final char INDENT = '\t'; void printGraph(PrintWriter out) { printGraph(out, 0); clearMarks(); } private void printGraph(PrintWriter out, int indent) { // first, mark this node as having been visited. // we only want to include nodes that haven't been // visited in our total size. mMarked = true; for (int i = 0; i < indent; ++i) out.print(INDENT); out.println(toString()); // then, visit all nodes that haven't been visited, // and include their total size in ours. int count = mReferences.length; if (count > 0) { int subIndent = indent + 1; for (int i = 0; i < count; ++i) { Object ref = mReferences[i]; if (ref instanceof Leak) { Leak leak = (Leak) ref; if (!leak.mMarked) leak.printGraph(out, subIndent); } } } } void printCycle(PrintWriter out) { printCycle(out, 0); clearMarks(); } private void printCycle(PrintWriter out, int indent) { // first, mark this node as having been visited. // we only want to include nodes that haven't been // visited in our total size. mMarked = true; // then, visit all nodes that haven't been visited, // and include their total size in ours. if (mChildCount > 0) { // don't print leaf nodes in a cycle. they aren't interesting. for (int i = 0; i < indent; ++i) out.print(INDENT); out.println(toString()); int subIndent = indent + 1; int count = mReferences.length; for (int i = 0; i < count; ++i) { Object ref = mReferences[i]; if (ref instanceof Leak) { Leak leak = (Leak) ref; if (!leak.mMarked) leak.printCycle(out, subIndent); } } } } public String toString() { return ("" + mName + " [" + mRefCount + "] " + mType + "{" + mTotalSize + "}"); } /** * Sorts in order of increasing reference count. */ static class ByRefCount extends QuickSort.Comparator { public int compare(Object obj1, Object obj2) { Leak l1 = (Leak) obj1, l2 = (Leak) obj2; return (l1.mRefCount - l2.mRefCount); } } /** * Sorts in order of decreasing number of children. */ public static class ByChildCount extends QuickSort.Comparator { public int compare(Object obj1, Object obj2) { Leak l1 = (Leak) obj1, l2 = (Leak) obj2; return (l2.mChildCount - l1.mChildCount); } } /** * Sorts in order of decreasing total size. */ static class ByTotalSize extends QuickSort.Comparator { public int compare(Object obj1, Object obj2) { Leak l1 = (Leak) obj1, l2 = (Leak) obj2; return (l2.mTotalSize - l1.mTotalSize); } } } final class LineReader { BufferedReader reader; long offset; LineReader(BufferedReader reader) { this.reader = reader; this.offset = 0; } String readLine() throws IOException { String line = reader.readLine(); if (line != null) offset += 1 + line.length(); return line; } void close() throws IOException { reader.close(); } } public class leaksoup { private static boolean ROOTS_ONLY = false; public static void main(String[] args) { if (args.length == 0) { System.out.println("usage: leaksoup [-blame] [-lxr] [-assign] [-roots] leaks"); System.exit(1); } // assume user want's blame URLs. FileLocator.USE_BLAME = true; FileLocator.ASSIGN_BLAME = false; ROOTS_ONLY = false; for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.charAt(0) == '-') { if (arg.equals("-blame")) FileLocator.USE_BLAME = true; else if (arg.equals("-lxr")) FileLocator.USE_BLAME = false; else if (arg.equals("-assign")) FileLocator.ASSIGN_BLAME = true; else if (arg.equals("-roots")) ROOTS_ONLY = true; else System.out.println("unrecognized option: " + arg); } else { cook(arg); } } // quit the application. System.exit(0); } static void cook(String inputName) { try { Vector vec = new Vector(); Hashtable leakTable = new Hashtable(); Hashtable types = new Hashtable(); Histogram hist = new Histogram(); LineReader reader = new LineReader(new BufferedReader(new InputStreamReader(new FileInputStream(inputName)))); StringTable strings = new StringTable(); String line = reader.readLine(); while (line != null) { if (line.startsWith("0x")) { String addr = strings.intern(line.substring(0, 10)); String name = strings.intern(line.substring(line.indexOf('<') + 1, line.indexOf('>'))); int size; try { String str = line.substring(line.indexOf('(') + 1, line.indexOf(')')).trim(); size = Integer.parseInt(str); } catch (NumberFormatException nfe) { size = 0; } // generate a unique type for this object. String key = strings.intern(name + "_" + size); Type type = (Type) types.get(key); if (type == null) { type = new Type(name, size); types.put(key, type); } // read in fields. could compress these by converting to Integer objects. vec.setSize(0); for (line = reader.readLine(); line != null && line.charAt(0) == '\t'; line = reader.readLine()) vec.addElement(strings.intern(line.substring(1, 11))); Object[] refs = new Object[vec.size()]; vec.copyInto(refs); vec.setSize(0); // record the offset of the stack crawl, which will be read in and formatted at the end, to save memory. long crawlOffset = reader.offset; short crawlCount = 0; for (line = reader.readLine(); line != null && !line.startsWith("Leaked "); line = reader.readLine()) ++crawlCount; // record the leak. leakTable.put(addr, new Leak(addr, type, refs, crawlOffset, crawlCount)); // count the leak types in a histogram. hist.record(type); } else { line = reader.readLine(); } } reader.close(); // don't need the interned strings table anymore. strings = null; Leak[] leaks = new Leak[leakTable.size()]; int leakCount = 0; long totalSize = 0; Hashtable parentTable = new Hashtable(); // now, we have a table full of leaked objects, lets derive reference counts, and build the graph. Enumeration e = leakTable.elements(); while (e.hasMoreElements()) { Leak leak = (Leak) e.nextElement(); Object[] refs = leak.mReferences; int count = refs.length; for (int r = 0; r < count; ++r) { String addr = (String) refs[r]; Leak ref = (Leak) leakTable.get(addr); if (ref != null) { // increase the ref count. ref.mRefCount++; // change string to ref itself. refs[r] = ref; // add leak to ref's parents vector. Vector parents = (Vector) parentTable.get(ref); if (parents == null) { parents = new Vector(); parentTable.put(ref, parents); } parents.addElement(leak); } } leaks[leakCount++] = leak; totalSize += leak.mType.mSize; } // be nice to the GC. leakTable.clear(); leakTable = null; // sort the leaks by address, and find interior pointers. { QuickSort byAddress = new QuickSort(new Reference.ByAddress()); byAddress.sort(leaks); } for (int i = 0; i < leakCount; ++i) { Leak leak = leaks[i]; Object[] refs = leak.mReferences; int count = refs.length; short childCount = 0; for (int r = 0; r < count; ++r) { if (refs[r] instanceof String) { String addr = (String) refs[r]; if (addr.equals("0x00000000")) continue; int address = (int) Long.parseLong(addr.substring(2), 16); Leak ref = (Leak) Reference.findNearest(leaks, address); if (ref != null) { // increase the ref count. ref.mRefCount++; // change string to ref itself. refs[r] = ref; // add leak to ref's parents vector. Vector parents = (Vector) parentTable.get(ref); if (parents == null) { parents = new Vector(); parentTable.put(ref, parents); } parents.addElement(leak); ++childCount; } } else { ++childCount; } } leak.mChildCount = childCount; } // set the parents of each leak. e = parentTable.keys(); while (e.hasMoreElements()) { Leak leak = (Leak) e.nextElement(); Vector parents = (Vector) parentTable.get(leak); if (parents != null) leak.setParents(parents); } // be nice to the GC. parentTable.clear(); parentTable = null; // store the leak report in inputName + ".html" PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(inputName + ".html")))); Date now = new Date(); out.println("Leaks as of " + now + ""); // print leak summary. out.println("

Leak Summary

"); out.println("total objects leaked = " + leakCount + "
"); out.println("total memory leaked = " + totalSize + " bytes.
"); printLeakHistogram(out, hist); printLeakStructure(out, leaks); // open original file again, as a RandomAccessFile, to read in stack crawl information. // print the leak report. if (!ROOTS_ONLY) { RandomAccessFile in = new RandomAccessFile(inputName, "r"); printLeaks(in, out, leaks); in.close(); } out.close(); } catch (Exception e) { e.printStackTrace(System.err); } } /** * Sorts the bins of a histogram by (count * typeSize) to show the * most pressing leaks. */ static class ByTypeBinSize extends QuickSort.Comparator { Histogram hist; ByTypeBinSize(Histogram hist) { this.hist = hist; } public int compare(Object obj1, Object obj2) { Type t1 = (Type) obj1, t2 = (Type) obj2; return (hist.count(t1) * t1.mSize - hist.count(t2) * t2.mSize); } } static void printLeakHistogram(PrintWriter out, Histogram hist) throws IOException { // sort the types by histogram count. Object[] types = hist.objects(); QuickSort byTypeBinSize = new QuickSort(new ByTypeBinSize(hist)); byTypeBinSize.sort(types); out.println("

Leak Histogram

"); out.println("
");
		int index = types.length;
		while (index > 0) {
			Type type = (Type) types[--index];
			int count = hist.count(type);
			out.println(type.toString() + " : " + count + " {" + (count * type.mSize) + "}");
		}
		out.println("
"); } static void printLeakStructure(PrintWriter out, Leak[] leaks) { // print root leaks. consider only leaks with a reference // count of 0, which when fixed, will hopefully reclaim // all of the objects below them in the graph. { QuickSort byRefCount = new QuickSort(new Leak.ByRefCount()); byRefCount.sort(leaks); } int rootCount = 0; int leakCount = leaks.length; for (int i = 0; i < leakCount; ++i) { Leak leak = leaks[i]; if (leak.mRefCount > 0) break; ++rootCount; leak.computeTotalSize(); } { QuickSort byTotalSize = new QuickSort(new Leak.ByTotalSize()); byTotalSize.sort(leaks, rootCount); } out.println("

Leak Roots

"); out.println("
");
        for (int i = 0; i < rootCount; ++i) {
            Leak leak = leaks[i];
            leak.printGraph(out);
        }
        out.println("
"); // print leak cycles. traverse the leaks from objects with most number // of children to least, so that leaf objects will be printed after // their parents. { QuickSort byChildCount = new QuickSort(new Leak.ByChildCount()); byChildCount.sort(leaks); } out.println("

Leak Cycles

"); out.println("
");
        for (int i = 0; i < leakCount; ++i) {
            Leak leak = leaks[i];
            // if an object's total size isn't known yet, then it must
            // be a member of a cycle, since it wasn't reached when traversing roots.
            if (leak.mTotalSize == 0) {
                leak.computeTotalSize();
                leak.printCycle(out);
            }
        }
        out.println("
"); } static StringBuffer appendChar(StringBuffer buffer, int ch) { if (ch > 32 && ch < 0x7F) { switch (ch) { case '<': buffer.append("<"); break; case '>': buffer.append(">"); break; default: buffer.append((char)ch); break; } } else { buffer.append("·"); } return buffer; } static void printField(PrintWriter out, Object field) { String value = field.toString(); if (field instanceof String) { // this is just a plain HEX value, print its contents as ASCII as well. if (value.startsWith("0x")) { try { int hexValue = Integer.parseInt(value.substring(2), 16); // don't interpret some common values, to save some space. if (hexValue != 0 && hexValue != -1) { StringBuffer buffer = new StringBuffer(value); buffer.append('\t'); appendChar(buffer, ((hexValue >>> 24) & 0x00FF)); appendChar(buffer, ((hexValue >>> 16) & 0x00FF)); appendChar(buffer, ((hexValue >>> 8) & 0x00FF)); appendChar(buffer, (hexValue & 0x00FF)); value = buffer.toString(); } } catch (NumberFormatException nfe) { } } } out.println("\t" + value); } static void printLeaks(RandomAccessFile in, PrintWriter out, Leak[] leaks) throws IOException { // sort the leaks by total size. QuickSort bySize = new QuickSort(new Leak.ByTotalSize()); bySize.sort(leaks); // now, print the report, sorted by type size. out.println("
");
		Type anchorType = null;
        int leakCount = leaks.length;
		for (int i = 0; i < leakCount; ++i) {
			Leak leak = leaks[i];
			if (anchorType != leak.mType) {
				anchorType = leak.mType;
				out.println("\n
"); out.println(""); out.println("

" + anchorType + " Leaks

"); } out.println(""); if (leak.mParents != null) { out.print(leak); out.println(" parents"); } else { out.println(leak); } // print object's fields: Object[] refs = leak.mReferences; int count = refs.length; for (int j = 0; j < count; j++) printField(out, refs[j]); // print object's stack crawl: in.seek(leak.mCrawlOffset); short crawlCount = leak.mCrawlCount; while (crawlCount-- > 0) { String line = in.readLine(); String location = FileLocator.getFileLocation(line); out.println(location); } // print object's parents. if (leak.mParents != null) { out.println(""); out.println("\nLeak Parents:"); Leak[] parents = leak.mParents; count = parents.length; for (int j = 0; j < count; j++) out.println("\t" + parents[j]); } } out.println("
"); } }