1
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
* The contents of this file are subject to the Netscape Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/NPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is Mozilla Communicator client code, released
16
* The Initial Developer of the Original Code is Netscape
17
* Communications Corporation. Portions created by Netscape are
18
* Copyright (C) 1998 Netscape Communications Corporation. All
23
* Patrick C. Beard <beard@netscape.com>
25
* Alternatively, the contents of this file may be used under the
26
* terms of the GNU Public License (the "GPL"), in which case the
27
* provisions of the GPL are applicable instead of those above.
28
* If you wish to allow use of your version of this file only
29
* under the terms of the GPL and not to allow others to use your
30
* version of this file under the NPL, indicate your decision by
31
* deleting the provisions above and replace them with the notice
32
* and other provisions required by the GPL. If you do not delete
33
* the provisions above, a recipient may use your version of this
34
* file under either the NPL or the GPL.
40
class Leak extends Reference {
50
Leak(String addr, Type type, Object[] refs, long crawlOffset, short crawlCount) {
51
super(addr, type, refs);
53
mCrawlOffset = crawlOffset;
54
mCrawlCount = crawlCount;
62
void setParents(Vector parents) {
63
mParents = new Leak[parents.size()];
64
parents.copyInto(mParents);
67
void computeTotalSize() {
68
// first, mark this node as having been visited.
69
// we only want to include nodes that haven't been
70
// visited in our total size.
71
mTotalSize = mType.mSize;
73
// then, visit all nodes that haven't been visited,
74
// and include their total size in ours.
75
int count = mReferences.length;
76
for (int i = 0; i < count; ++i) {
77
Object ref = mReferences[i];
78
if (ref instanceof Leak) {
79
Leak leak = (Leak) ref;
80
if (leak.mTotalSize == 0) {
81
leak.computeTotalSize();
82
mTotalSize += leak.mTotalSize;
88
void clearTotalSize() {
89
// first, clear our total size.
92
// then, visit all nodes that haven't been visited,
93
// and clear each one's total size.
94
int count = mReferences.length;
95
for (int i = 0; i < count; ++i) {
96
Object ref = mReferences[i];
97
if (ref instanceof Leak) {
98
Leak leak = (Leak) ref;
99
if (leak.mTotalSize != 0)
100
leak.clearTotalSize();
106
// first, clear mark.
109
// then, visit all nodes that haven't been visited,
110
// and clear each one's mark.
111
int count = mReferences.length;
112
for (int i = 0; i < count; ++i) {
113
Object ref = mReferences[i];
114
if (ref instanceof Leak) {
115
Leak leak = (Leak) ref;
122
static final char INDENT = '\t';
124
void printGraph(PrintWriter out) {
129
private void printGraph(PrintWriter out, int indent) {
130
// first, mark this node as having been visited.
131
// we only want to include nodes that haven't been
132
// visited in our total size.
134
for (int i = 0; i < indent; ++i)
136
out.println(toString());
138
// then, visit all nodes that haven't been visited,
139
// and include their total size in ours.
140
int count = mReferences.length;
142
int subIndent = indent + 1;
143
for (int i = 0; i < count; ++i) {
144
Object ref = mReferences[i];
145
if (ref instanceof Leak) {
146
Leak leak = (Leak) ref;
148
leak.printGraph(out, subIndent);
154
void printCycle(PrintWriter out) {
159
private void printCycle(PrintWriter out, int indent) {
160
// first, mark this node as having been visited.
161
// we only want to include nodes that haven't been
162
// visited in our total size.
165
// then, visit all nodes that haven't been visited,
166
// and include their total size in ours.
167
if (mChildCount > 0) {
168
// don't print leaf nodes in a cycle. they aren't interesting.
169
for (int i = 0; i < indent; ++i)
171
out.println(toString());
172
int subIndent = indent + 1;
173
int count = mReferences.length;
174
for (int i = 0; i < count; ++i) {
175
Object ref = mReferences[i];
176
if (ref instanceof Leak) {
177
Leak leak = (Leak) ref;
179
leak.printCycle(out, subIndent);
185
public String toString() {
186
return ("<A HREF=\"#" + mName + "\">" + mName + "</A> [" + mRefCount + "] " + mType + "{" + mTotalSize + "}");
190
* Sorts in order of increasing reference count.
192
static class ByRefCount extends QuickSort.Comparator {
193
public int compare(Object obj1, Object obj2) {
194
Leak l1 = (Leak) obj1, l2 = (Leak) obj2;
195
return (l1.mRefCount - l2.mRefCount);
200
* Sorts in order of decreasing number of children.
202
public static class ByChildCount extends QuickSort.Comparator {
203
public int compare(Object obj1, Object obj2) {
204
Leak l1 = (Leak) obj1, l2 = (Leak) obj2;
205
return (l2.mChildCount - l1.mChildCount);
210
* Sorts in order of decreasing total size.
212
static class ByTotalSize extends QuickSort.Comparator {
213
public int compare(Object obj1, Object obj2) {
214
Leak l1 = (Leak) obj1, l2 = (Leak) obj2;
215
return (l2.mTotalSize - l1.mTotalSize);
220
final class LineReader {
221
BufferedReader reader;
224
LineReader(BufferedReader reader) {
225
this.reader = reader;
229
String readLine() throws IOException {
230
String line = reader.readLine();
232
offset += 1 + line.length();
236
void close() throws IOException {
241
public class leaksoup {
242
private static boolean ROOTS_ONLY = false;
244
public static void main(String[] args) {
245
if (args.length == 0) {
246
System.out.println("usage: leaksoup [-blame] [-lxr] [-assign] [-roots] leaks");
250
// assume user want's blame URLs.
251
FileLocator.USE_BLAME = true;
252
FileLocator.ASSIGN_BLAME = false;
255
for (int i = 0; i < args.length; i++) {
256
String arg = args[i];
257
if (arg.charAt(0) == '-') {
258
if (arg.equals("-blame"))
259
FileLocator.USE_BLAME = true;
260
else if (arg.equals("-lxr"))
261
FileLocator.USE_BLAME = false;
262
else if (arg.equals("-assign"))
263
FileLocator.ASSIGN_BLAME = true;
264
else if (arg.equals("-roots"))
267
System.out.println("unrecognized option: " + arg);
273
// quit the application.
277
static void cook(String inputName) {
279
Vector vec = new Vector();
280
Hashtable leakTable = new Hashtable();
281
Hashtable types = new Hashtable();
282
Histogram hist = new Histogram();
283
LineReader reader = new LineReader(new BufferedReader(new InputStreamReader(new FileInputStream(inputName))));
284
StringTable strings = new StringTable();
285
String line = reader.readLine();
286
while (line != null) {
287
if (line.startsWith("0x")) {
288
String addr = strings.intern(line.substring(0, 10));
289
String name = strings.intern(line.substring(line.indexOf('<') + 1, line.indexOf('>')));
292
String str = line.substring(line.indexOf('(') + 1, line.indexOf(')')).trim();
293
size = Integer.parseInt(str);
294
} catch (NumberFormatException nfe) {
298
// generate a unique type for this object.
299
String key = strings.intern(name + "_" + size);
300
Type type = (Type) types.get(key);
302
type = new Type(name, size);
303
types.put(key, type);
306
// read in fields. could compress these by converting to Integer objects.
308
for (line = reader.readLine(); line != null && line.charAt(0) == '\t'; line = reader.readLine())
309
vec.addElement(strings.intern(line.substring(1, 11)));
310
Object[] refs = new Object[vec.size()];
314
// record the offset of the stack crawl, which will be read in and formatted at the end, to save memory.
315
long crawlOffset = reader.offset;
316
short crawlCount = 0;
317
for (line = reader.readLine(); line != null && !line.startsWith("Leaked "); line = reader.readLine())
321
leakTable.put(addr, new Leak(addr, type, refs, crawlOffset, crawlCount));
323
// count the leak types in a histogram.
326
line = reader.readLine();
331
// don't need the interned strings table anymore.
334
Leak[] leaks = new Leak[leakTable.size()];
338
Hashtable parentTable = new Hashtable();
340
// now, we have a table full of leaked objects, lets derive reference counts, and build the graph.
341
Enumeration e = leakTable.elements();
342
while (e.hasMoreElements()) {
343
Leak leak = (Leak) e.nextElement();
344
Object[] refs = leak.mReferences;
345
int count = refs.length;
346
for (int r = 0; r < count; ++r) {
347
String addr = (String) refs[r];
348
Leak ref = (Leak) leakTable.get(addr);
350
// increase the ref count.
352
// change string to ref itself.
354
// add leak to ref's parents vector.
355
Vector parents = (Vector) parentTable.get(ref);
356
if (parents == null) {
357
parents = new Vector();
358
parentTable.put(ref, parents);
360
parents.addElement(leak);
363
leaks[leakCount++] = leak;
364
totalSize += leak.mType.mSize;
367
// be nice to the GC.
371
// sort the leaks by address, and find interior pointers.
373
QuickSort byAddress = new QuickSort(new Reference.ByAddress());
374
byAddress.sort(leaks);
377
for (int i = 0; i < leakCount; ++i) {
378
Leak leak = leaks[i];
379
Object[] refs = leak.mReferences;
380
int count = refs.length;
381
short childCount = 0;
382
for (int r = 0; r < count; ++r) {
383
if (refs[r] instanceof String) {
384
String addr = (String) refs[r];
385
if (addr.equals("0x00000000")) continue;
386
int address = (int) Long.parseLong(addr.substring(2), 16);
387
Leak ref = (Leak) Reference.findNearest(leaks, address);
389
// increase the ref count.
391
// change string to ref itself.
393
// add leak to ref's parents vector.
394
Vector parents = (Vector) parentTable.get(ref);
395
if (parents == null) {
396
parents = new Vector();
397
parentTable.put(ref, parents);
399
parents.addElement(leak);
406
leak.mChildCount = childCount;
409
// set the parents of each leak.
410
e = parentTable.keys();
411
while (e.hasMoreElements()) {
412
Leak leak = (Leak) e.nextElement();
413
Vector parents = (Vector) parentTable.get(leak);
415
leak.setParents(parents);
418
// be nice to the GC.
422
// store the leak report in inputName + ".html"
423
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(inputName + ".html"))));
425
Date now = new Date();
426
out.println("<TITLE>Leaks as of " + now + "</TITLE>");
428
// print leak summary.
429
out.println("<H2>Leak Summary</H2>");
430
out.println("total objects leaked = " + leakCount + "<BR>");
431
out.println("total memory leaked = " + totalSize + " bytes.<BR>");
433
printLeakHistogram(out, hist);
434
printLeakStructure(out, leaks);
436
// open original file again, as a RandomAccessFile, to read in stack crawl information.
438
// print the leak report.
440
RandomAccessFile in = new RandomAccessFile(inputName, "r");
441
printLeaks(in, out, leaks);
446
} catch (Exception e) {
447
e.printStackTrace(System.err);
452
* Sorts the bins of a histogram by (count * typeSize) to show the
453
* most pressing leaks.
455
static class ByTypeBinSize extends QuickSort.Comparator {
458
ByTypeBinSize(Histogram hist) {
462
public int compare(Object obj1, Object obj2) {
463
Type t1 = (Type) obj1, t2 = (Type) obj2;
464
return (hist.count(t1) * t1.mSize - hist.count(t2) * t2.mSize);
468
static void printLeakHistogram(PrintWriter out, Histogram hist) throws IOException {
469
// sort the types by histogram count.
470
Object[] types = hist.objects();
471
QuickSort byTypeBinSize = new QuickSort(new ByTypeBinSize(hist));
472
byTypeBinSize.sort(types);
474
out.println("<H2>Leak Histogram</H2>");
475
out.println("<PRE>");
476
int index = types.length;
478
Type type = (Type) types[--index];
479
int count = hist.count(type);
480
out.println(type.toString() + " : " + count + " {" + (count * type.mSize) + "}");
482
out.println("</PRE>");
485
static void printLeakStructure(PrintWriter out, Leak[] leaks) {
486
// print root leaks. consider only leaks with a reference
487
// count of 0, which when fixed, will hopefully reclaim
488
// all of the objects below them in the graph.
490
QuickSort byRefCount = new QuickSort(new Leak.ByRefCount());
491
byRefCount.sort(leaks);
495
int leakCount = leaks.length;
496
for (int i = 0; i < leakCount; ++i) {
497
Leak leak = leaks[i];
498
if (leak.mRefCount > 0)
501
leak.computeTotalSize();
505
QuickSort byTotalSize = new QuickSort(new Leak.ByTotalSize());
506
byTotalSize.sort(leaks, rootCount);
509
out.println("<H2>Leak Roots</H2>");
510
out.println("<PRE>");
511
for (int i = 0; i < rootCount; ++i) {
512
Leak leak = leaks[i];
513
leak.printGraph(out);
515
out.println("</PRE>");
517
// print leak cycles. traverse the leaks from objects with most number
518
// of children to least, so that leaf objects will be printed after
521
QuickSort byChildCount = new QuickSort(new Leak.ByChildCount());
522
byChildCount.sort(leaks);
525
out.println("<H2>Leak Cycles</H2>");
526
out.println("<PRE>");
527
for (int i = 0; i < leakCount; ++i) {
528
Leak leak = leaks[i];
529
// if an object's total size isn't known yet, then it must
530
// be a member of a cycle, since it wasn't reached when traversing roots.
531
if (leak.mTotalSize == 0) {
532
leak.computeTotalSize();
533
leak.printCycle(out);
536
out.println("</PRE>");
539
static StringBuffer appendChar(StringBuffer buffer, int ch) {
540
if (ch > 32 && ch < 0x7F) {
542
case '<': buffer.append("<"); break;
543
case '>': buffer.append(">"); break;
544
default: buffer.append((char)ch); break;
547
buffer.append("·");
552
static void printField(PrintWriter out, Object field) {
553
String value = field.toString();
554
if (field instanceof String) {
555
// this is just a plain HEX value, print its contents as ASCII as well.
556
if (value.startsWith("0x")) {
558
int hexValue = Integer.parseInt(value.substring(2), 16);
559
// don't interpret some common values, to save some space.
560
if (hexValue != 0 && hexValue != -1) {
561
StringBuffer buffer = new StringBuffer(value);
563
appendChar(buffer, ((hexValue >>> 24) & 0x00FF));
564
appendChar(buffer, ((hexValue >>> 16) & 0x00FF));
565
appendChar(buffer, ((hexValue >>> 8) & 0x00FF));
566
appendChar(buffer, (hexValue & 0x00FF));
567
value = buffer.toString();
569
} catch (NumberFormatException nfe) {
573
out.println("\t" + value);
576
static void printLeaks(RandomAccessFile in, PrintWriter out, Leak[] leaks) throws IOException {
577
// sort the leaks by total size.
578
QuickSort bySize = new QuickSort(new Leak.ByTotalSize());
581
// now, print the report, sorted by type size.
582
out.println("<PRE>");
583
Type anchorType = null;
584
int leakCount = leaks.length;
585
for (int i = 0; i < leakCount; ++i) {
586
Leak leak = leaks[i];
587
if (anchorType != leak.mType) {
588
anchorType = leak.mType;
589
out.println("\n<HR>");
590
out.println("<A NAME=\"" + anchorType.mName + "_" + anchorType.mSize + "\"></A>");
591
out.println("<H3>" + anchorType + " Leaks</H3>");
593
out.println("<A NAME=\"" + leak.mName + "\"></A>");
594
if (leak.mParents != null) {
596
out.println(" <A HREF=\"#" + leak.mName + "_parents\">parents</A>");
600
// print object's fields:
601
Object[] refs = leak.mReferences;
602
int count = refs.length;
603
for (int j = 0; j < count; j++)
604
printField(out, refs[j]);
605
// print object's stack crawl:
606
in.seek(leak.mCrawlOffset);
607
short crawlCount = leak.mCrawlCount;
608
while (crawlCount-- > 0) {
609
String line = in.readLine();
610
String location = FileLocator.getFileLocation(line);
611
out.println(location);
613
// print object's parents.
614
if (leak.mParents != null) {
615
out.println("<A NAME=\"" + leak.mName + "_parents\"></A>");
616
out.println("\nLeak Parents:");
617
Leak[] parents = leak.mParents;
618
count = parents.length;
619
for (int j = 0; j < count; j++)
620
out.println("\t" + parents[j]);
623
out.println("</PRE>");