Back to index

lightning-sunbird  0.9+nobinonly
leaksoup.java
Go to the documentation of this file.
00001 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * The contents of this file are subject to the Netscape Public
00004  * License Version 1.1 (the "License"); you may not use this file
00005  * except in compliance with the License. You may obtain a copy of
00006  * the License at http://www.mozilla.org/NPL/
00007  *
00008  * Software distributed under the License is distributed on an "AS
00009  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
00010  * implied. See the License for the specific language governing
00011  * rights and limitations under the License.
00012  *
00013  * The Original Code is Mozilla Communicator client code, released
00014  * March 31, 1998.
00015  *
00016  * The Initial Developer of the Original Code is Netscape
00017  * Communications Corporation.  Portions created by Netscape are
00018  * Copyright (C) 1998 Netscape Communications Corporation. All
00019  * Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *
00023  * Patrick C. Beard <beard@netscape.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the
00026  * terms of the GNU Public License (the "GPL"), in which case the
00027  * provisions of the GPL are applicable instead of those above.
00028  * If you wish to allow use of your version of this file only
00029  * under the terms of the GPL and not to allow others to use your
00030  * version of this file under the NPL, indicate your decision by
00031  * deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL.  If you do not delete
00033  * the provisions above, a recipient may use your version of this
00034  * file under either the NPL or the GPL.
00035  */
00036 
00037 import java.io.*;
00038 import java.util.*;
00039 
00040 class Leak extends Reference {
00041        String mName;
00042        long mCrawlOffset;
00043        short mCrawlCount;
00044        short mRefCount;
00045        short mChildCount;
00046        Leak[] mParents;
00047        int mTotalSize;
00048        boolean mMarked;
00049 
00050        Leak(String addr, Type type, Object[] refs, long crawlOffset, short crawlCount) {
00051            super(addr, type, refs);
00052            mName = addr;
00053               mCrawlOffset = crawlOffset;
00054               mCrawlCount = crawlCount;
00055               mRefCount = 0;
00056               mChildCount = 0;
00057               mParents = null;
00058               mTotalSize = 0;
00059               mMarked = false;
00060        }
00061        
00062        void setParents(Vector parents) {
00063               mParents = new Leak[parents.size()];
00064               parents.copyInto(mParents);
00065        }
00066        
00067        void computeTotalSize() {
00068               // first, mark this node as having been visited.
00069               // we only want to include nodes that haven't been
00070               // visited in our total size.
00071               mTotalSize = mType.mSize;
00072               
00073               // then, visit all nodes that haven't been visited,
00074               // and include their total size in ours.
00075               int count = mReferences.length;
00076               for (int i = 0; i < count; ++i) {
00077                      Object ref = mReferences[i];
00078                      if (ref instanceof Leak) {
00079                             Leak leak = (Leak) ref;
00080                             if (leak.mTotalSize == 0) {
00081                                    leak.computeTotalSize();
00082                                    mTotalSize += leak.mTotalSize;
00083                             }
00084                      }
00085               }
00086        }
00087 
00088        void clearTotalSize() {
00089               // first, clear our total size.
00090               mTotalSize = 0;
00091               
00092               // then, visit all nodes that haven't been visited,
00093               // and clear each one's total size.
00094               int count = mReferences.length;
00095               for (int i = 0; i < count; ++i) {
00096                      Object ref = mReferences[i];
00097                      if (ref instanceof Leak) {
00098                             Leak leak = (Leak) ref;
00099                             if (leak.mTotalSize != 0)
00100                                    leak.clearTotalSize();
00101                      }
00102               }
00103        }
00104 
00105        void clearMarks() {
00106               // first, clear mark.
00107               mMarked = false;
00108               
00109               // then, visit all nodes that haven't been visited,
00110               // and clear each one's mark.
00111               int count = mReferences.length;
00112               for (int i = 0; i < count; ++i) {
00113                      Object ref = mReferences[i];
00114                      if (ref instanceof Leak) {
00115                             Leak leak = (Leak) ref;
00116                             if (leak.mMarked)
00117                                    leak.clearMarks();
00118                      }
00119               }
00120        }
00121        
00122        static final char INDENT = '\t';
00123        
00124     void printGraph(PrintWriter out) {
00125            printGraph(out, 0);
00126            clearMarks();
00127     }
00128 
00129        private void printGraph(PrintWriter out, int indent) {
00130               // first, mark this node as having been visited.
00131               // we only want to include nodes that haven't been
00132               // visited in our total size.
00133               mMarked = true;
00134               for (int i = 0; i < indent; ++i)
00135                   out.print(INDENT);
00136               out.println(toString());
00137               
00138               // then, visit all nodes that haven't been visited,
00139               // and include their total size in ours.
00140               int count = mReferences.length;
00141               if (count > 0) {
00142                   int subIndent = indent + 1;
00143               for (int i = 0; i < count; ++i) {
00144                      Object ref = mReferences[i];
00145                      if (ref instanceof Leak) {
00146                             Leak leak = (Leak) ref;
00147                             if (!leak.mMarked)
00148                                    leak.printGraph(out, subIndent);
00149                      }
00150               }
00151        }
00152        }
00153        
00154        void printCycle(PrintWriter out) {
00155            printCycle(out, 0);
00156            clearMarks();
00157        }
00158        
00159        private void printCycle(PrintWriter out, int indent) {
00160               // first, mark this node as having been visited.
00161               // we only want to include nodes that haven't been
00162               // visited in our total size.
00163               mMarked = true;
00164               
00165               // then, visit all nodes that haven't been visited,
00166               // and include their total size in ours.
00167               if (mChildCount > 0) {
00168                   // don't print leaf nodes in a cycle. they aren't interesting.
00169               for (int i = 0; i < indent; ++i)
00170                   out.print(INDENT);
00171               out.println(toString());
00172                   int subIndent = indent + 1;
00173               int count = mReferences.length;
00174               for (int i = 0; i < count; ++i) {
00175                      Object ref = mReferences[i];
00176                      if (ref instanceof Leak) {
00177                             Leak leak = (Leak) ref;
00178                             if (!leak.mMarked)
00179                                    leak.printCycle(out, subIndent);
00180                      }
00181               }
00182        }
00183        }
00184 
00185        public String toString() {
00186               return ("<A HREF=\"#" + mName + "\">" + mName + "</A> [" + mRefCount + "] " + mType + "{" + mTotalSize + "}");
00187        }
00188        
00192        static class ByRefCount extends QuickSort.Comparator {
00193               public int compare(Object obj1, Object obj2) {
00194                      Leak l1 = (Leak) obj1, l2 = (Leak) obj2;
00195                      return (l1.mRefCount - l2.mRefCount);
00196               }
00197        }
00198 
00202        public static class ByChildCount extends QuickSort.Comparator {
00203               public int compare(Object obj1, Object obj2) {
00204                      Leak l1 = (Leak) obj1, l2 = (Leak) obj2;
00205                      return (l2.mChildCount - l1.mChildCount);
00206               }
00207        }
00208 
00212        static class ByTotalSize extends QuickSort.Comparator {
00213               public int compare(Object obj1, Object obj2) {
00214                      Leak l1 = (Leak) obj1, l2 = (Leak) obj2;
00215                      return (l2.mTotalSize - l1.mTotalSize);
00216               }
00217        }
00218 }
00219 
00220 final class LineReader {
00221     BufferedReader reader;
00222     long offset;
00223     
00224     LineReader(BufferedReader reader) {
00225         this.reader = reader;
00226         this.offset = 0;
00227     }
00228     
00229     String readLine() throws IOException {
00230         String line = reader.readLine();
00231         if (line != null)
00232             offset += 1 + line.length();
00233         return line;
00234     }
00235     
00236     void close() throws IOException {
00237         reader.close();
00238     }
00239 }
00240 
00241 public class leaksoup {
00242        private static boolean ROOTS_ONLY = false;
00243 
00244        public static void main(String[] args) {
00245               if (args.length == 0) {
00246                      System.out.println("usage:  leaksoup [-blame] [-lxr] [-assign] [-roots] leaks");
00247                      System.exit(1);
00248               }
00249               
00250               // assume user want's blame URLs.
00251               FileLocator.USE_BLAME = true;
00252               FileLocator.ASSIGN_BLAME = false;
00253               ROOTS_ONLY = false;
00254               
00255               for (int i = 0; i < args.length; i++) {
00256                      String arg = args[i];
00257                      if (arg.charAt(0) == '-') {
00258                             if (arg.equals("-blame"))
00259                                    FileLocator.USE_BLAME = true;
00260                             else if (arg.equals("-lxr"))
00261                                    FileLocator.USE_BLAME = false;
00262                             else if (arg.equals("-assign"))
00263                                    FileLocator.ASSIGN_BLAME = true;
00264                             else if (arg.equals("-roots"))
00265                                    ROOTS_ONLY = true;
00266                             else
00267                                    System.out.println("unrecognized option: " + arg);
00268                      } else {
00269                             cook(arg);
00270                      }
00271               }
00272               
00273               // quit the application.
00274               System.exit(0);
00275        }
00276        
00277        static void cook(String inputName) {
00278               try {
00279                      Vector vec = new Vector();
00280                      Hashtable leakTable = new Hashtable();
00281                      Hashtable types = new Hashtable();
00282                      Histogram hist = new Histogram();
00283                      LineReader reader = new LineReader(new BufferedReader(new InputStreamReader(new FileInputStream(inputName))));
00284                      StringTable strings = new StringTable();
00285                      String line = reader.readLine();
00286                      while (line != null) {
00287                             if (line.startsWith("0x")) {
00288                                    String addr = strings.intern(line.substring(0, 10));
00289                                    String name = strings.intern(line.substring(line.indexOf('<') + 1, line.indexOf('>')));
00290                                    int size;
00291                                    try {
00292                                           String str = line.substring(line.indexOf('(') + 1, line.indexOf(')')).trim();
00293                                           size = Integer.parseInt(str);
00294                                    } catch (NumberFormatException nfe) {
00295                                           size = 0;
00296                                    }
00297 
00298                                    // generate a unique type for this object.
00299                                    String key = strings.intern(name + "_" + size);
00300                                    Type type = (Type) types.get(key);
00301                                    if (type == null) {
00302                                           type = new Type(name, size);
00303                                           types.put(key, type);
00304                                    }
00305                                    
00306                                    // read in fields. could compress these by converting to Integer objects.
00307                                    vec.setSize(0);
00308                                    for (line = reader.readLine(); line != null && line.charAt(0) == '\t'; line = reader.readLine())
00309                                           vec.addElement(strings.intern(line.substring(1, 11)));
00310                                    Object[] refs = new Object[vec.size()];
00311                                    vec.copyInto(refs);
00312                                    vec.setSize(0);
00313                                    
00314                                 // record the offset of the stack crawl, which will be read in and formatted at the end, to save memory.
00315                                    long crawlOffset = reader.offset;
00316                                    short crawlCount = 0;
00317                                    for (line = reader.readLine(); line != null && !line.startsWith("Leaked "); line = reader.readLine())
00318                                           ++crawlCount;
00319 
00320                                    // record the leak.
00321                                    leakTable.put(addr, new Leak(addr, type, refs, crawlOffset, crawlCount));
00322 
00323                                    // count the leak types in a histogram.
00324                                    hist.record(type);
00325                             } else {
00326                                    line = reader.readLine();
00327                             }
00328                      }
00329                      reader.close();
00330                      
00331                      // don't need the interned strings table anymore.
00332                      strings = null;
00333                      
00334                      Leak[] leaks = new Leak[leakTable.size()];
00335                      int leakCount = 0;
00336                      long totalSize = 0;
00337 
00338                      Hashtable parentTable = new Hashtable();
00339 
00340                      // now, we have a table full of leaked objects, lets derive reference counts, and build the graph.
00341                      Enumeration e = leakTable.elements();
00342                      while (e.hasMoreElements()) {
00343                             Leak leak = (Leak) e.nextElement();
00344                             Object[] refs = leak.mReferences;
00345                             int count = refs.length;
00346                             for (int r = 0; r < count; ++r) {
00347                                    String addr = (String) refs[r];
00348                                    Leak ref = (Leak) leakTable.get(addr);
00349                                    if (ref != null) {
00350                                           // increase the ref count.
00351                                           ref.mRefCount++;
00352                                           // change string to ref itself.
00353                                           refs[r] = ref;
00354                                           // add leak to ref's parents vector.
00355                                           Vector parents = (Vector) parentTable.get(ref);
00356                                           if (parents == null) {
00357                                                  parents = new Vector();
00358                                                  parentTable.put(ref, parents);
00359                                           }
00360                                           parents.addElement(leak);
00361                                    }
00362                             }
00363                             leaks[leakCount++] = leak;
00364                             totalSize += leak.mType.mSize;
00365                      }
00366 
00367                      // be nice to the GC.
00368                      leakTable.clear();
00369                      leakTable = null;
00370 
00371             // sort the leaks by address, and find interior pointers.
00372             {
00373                 QuickSort byAddress = new QuickSort(new Reference.ByAddress());
00374                 byAddress.sort(leaks);
00375             }
00376             
00377             for (int i = 0; i < leakCount; ++i) {
00378                 Leak leak = leaks[i];
00379                 Object[] refs = leak.mReferences;
00380                 int count = refs.length;
00381                 short childCount = 0;
00382                 for (int r = 0; r < count; ++r) {
00383                     if (refs[r] instanceof String) {
00384                         String addr = (String) refs[r];
00385                         if (addr.equals("0x00000000")) continue;
00386                         int address = (int) Long.parseLong(addr.substring(2), 16);
00387                         Leak ref = (Leak) Reference.findNearest(leaks, address);
00388                         if (ref != null) {
00389                                           // increase the ref count.
00390                                           ref.mRefCount++;
00391                                           // change string to ref itself.
00392                                           refs[r] = ref;
00393                                               // add leak to ref's parents vector.
00394                                           Vector parents = (Vector) parentTable.get(ref);
00395                                           if (parents == null) {
00396                                                  parents = new Vector();
00397                                                  parentTable.put(ref, parents);
00398                                           }
00399                                           parents.addElement(leak);
00400                                           ++childCount;
00401                         }
00402                     } else {
00403                         ++childCount;
00404                     }
00405                 }
00406                 leak.mChildCount = childCount;
00407             }
00408                      
00409                      // set the parents of each leak.
00410                      e = parentTable.keys();
00411                      while (e.hasMoreElements()) {
00412                             Leak leak = (Leak) e.nextElement();
00413                             Vector parents = (Vector) parentTable.get(leak);
00414                             if (parents != null)
00415                                    leak.setParents(parents);
00416                      }
00417                      
00418                      // be nice to the GC.
00419                      parentTable.clear();
00420                      parentTable = null;
00421                      
00422                      // store the leak report in inputName + ".html"
00423                      PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(inputName + ".html"))));
00424 
00425                      Date now = new Date();
00426                      out.println("<TITLE>Leaks as of " + now + "</TITLE>");
00427 
00428             // print leak summary.
00429             out.println("<H2>Leak Summary</H2>");
00430             out.println("total objects leaked = " + leakCount + "<BR>");
00431             out.println("total memory leaked  = " + totalSize + " bytes.<BR>");
00432 
00433               printLeakHistogram(out, hist);
00434                printLeakStructure(out, leaks);
00435 
00436                      // open original file again, as a RandomAccessFile, to read in stack crawl information.
00437                      
00438                      // print the leak report.
00439                      if (!ROOTS_ONLY) {
00440                      RandomAccessFile in = new RandomAccessFile(inputName, "r");
00441                             printLeaks(in, out, leaks);
00442                             in.close();
00443                      }
00444                      
00445                      out.close();
00446               } catch (Exception e) {
00447                      e.printStackTrace(System.err);
00448               }
00449        }
00450        
00455        static class ByTypeBinSize extends QuickSort.Comparator {
00456               Histogram hist;
00457               
00458               ByTypeBinSize(Histogram hist) {
00459                      this.hist = hist;
00460               }
00461        
00462               public int compare(Object obj1, Object obj2) {
00463                      Type t1 = (Type) obj1, t2 = (Type) obj2;
00464                      return (hist.count(t1) * t1.mSize - hist.count(t2) * t2.mSize);
00465               }
00466        }
00467 
00468        static void printLeakHistogram(PrintWriter out, Histogram hist) throws IOException {
00469               // sort the types by histogram count.
00470               Object[] types = hist.objects();
00471               QuickSort byTypeBinSize = new QuickSort(new ByTypeBinSize(hist));
00472               byTypeBinSize.sort(types);
00473               
00474               out.println("<H2>Leak Histogram</H2>");
00475               out.println("<PRE>");
00476               int index = types.length;
00477               while (index > 0) {
00478                      Type type = (Type) types[--index];
00479                      int count = hist.count(type);
00480                      out.println(type.toString() + " : " + count + " {" + (count * type.mSize) + "}");
00481               }
00482               out.println("</PRE>");
00483        }
00484        
00485        static void printLeakStructure(PrintWriter out, Leak[] leaks) {
00486         // print root leaks. consider only leaks with a reference
00487         // count of 0, which when fixed, will hopefully reclaim
00488         // all of the objects below them in the graph.
00489         {
00490             QuickSort byRefCount = new QuickSort(new Leak.ByRefCount());
00491             byRefCount.sort(leaks);
00492         }
00493         
00494         int rootCount = 0;
00495         int leakCount = leaks.length;
00496         for (int i = 0; i < leakCount; ++i) {
00497             Leak leak = leaks[i];
00498             if (leak.mRefCount > 0)
00499                 break;
00500             ++rootCount;
00501             leak.computeTotalSize();
00502         }
00503 
00504         {
00505             QuickSort byTotalSize = new QuickSort(new Leak.ByTotalSize());
00506             byTotalSize.sort(leaks, rootCount);
00507         }
00508 
00509         out.println("<H2>Leak Roots</H2>");
00510         out.println("<PRE>");
00511         for (int i = 0; i < rootCount; ++i) {
00512             Leak leak = leaks[i];
00513             leak.printGraph(out);
00514         }
00515         out.println("</PRE>");
00516 
00517         // print leak cycles. traverse the leaks from objects with most number
00518         // of children to least, so that leaf objects will be printed after
00519         // their parents.
00520         {
00521             QuickSort byChildCount = new QuickSort(new Leak.ByChildCount());
00522             byChildCount.sort(leaks);
00523         }
00524         
00525         out.println("<H2>Leak Cycles</H2>");
00526         out.println("<PRE>");
00527         for (int i = 0; i < leakCount; ++i) {
00528             Leak leak = leaks[i];
00529             // if an object's total size isn't known yet, then it must
00530             // be a member of a cycle, since it wasn't reached when traversing roots.
00531             if (leak.mTotalSize == 0) {
00532                 leak.computeTotalSize();
00533                 leak.printCycle(out);
00534             }
00535         }
00536         out.println("</PRE>");
00537        }
00538        
00539        static StringBuffer appendChar(StringBuffer buffer, int ch) {
00540               if (ch > 32 && ch < 0x7F) {
00541                      switch (ch) {
00542                      case '<':     buffer.append("&LT;"); break;
00543                      case '>':     buffer.append("&GT;"); break;
00544                      default:      buffer.append((char)ch); break;
00545                      }
00546               } else {
00547                      buffer.append("&#183;");
00548               }
00549               return buffer;
00550        }
00551        
00552        static void printField(PrintWriter out, Object field) {
00553               String value = field.toString();
00554               if (field instanceof String) {
00555                      // this is just a plain HEX value, print its contents as ASCII as well.
00556                      if (value.startsWith("0x")) {
00557                             try {
00558                                    int hexValue = Integer.parseInt(value.substring(2), 16);
00559                                    // don't interpret some common values, to save some space.
00560                                    if (hexValue != 0 && hexValue != -1) {
00561                                           StringBuffer buffer = new StringBuffer(value);
00562                                           buffer.append('\t');
00563                                           appendChar(buffer, ((hexValue >>> 24) & 0x00FF));
00564                                           appendChar(buffer, ((hexValue >>> 16) & 0x00FF));
00565                                           appendChar(buffer, ((hexValue >>>  8) & 0x00FF));
00566                                           appendChar(buffer, (hexValue & 0x00FF));
00567                                           value = buffer.toString();
00568                                    }
00569                             } catch (NumberFormatException nfe) {
00570                             }
00571                      }
00572               }
00573               out.println("\t" + value);
00574        }
00575 
00576        static void printLeaks(RandomAccessFile in, PrintWriter out, Leak[] leaks) throws IOException {
00577               // sort the leaks by total size.
00578               QuickSort bySize = new QuickSort(new Leak.ByTotalSize());
00579               bySize.sort(leaks);
00580 
00581               // now, print the report, sorted by type size.
00582               out.println("<PRE>");
00583               Type anchorType = null;
00584         int leakCount = leaks.length;
00585               for (int i = 0; i < leakCount; ++i) {
00586                      Leak leak = leaks[i];
00587                      if (anchorType != leak.mType) {
00588                             anchorType = leak.mType;
00589                             out.println("\n<HR>");
00590                             out.println("<A NAME=\"" + anchorType.mName + "_" + anchorType.mSize + "\"></A>");
00591                             out.println("<H3>" + anchorType + " Leaks</H3>");
00592                      }
00593                      out.println("<A NAME=\"" + leak.mName + "\"></A>");
00594                      if (leak.mParents != null) {
00595                             out.print(leak);
00596                             out.println(" <A HREF=\"#" + leak.mName + "_parents\">parents</A>");
00597                      } else {
00598                             out.println(leak);
00599                      }
00600                      // print object's fields:
00601                      Object[] refs = leak.mReferences;
00602                      int count = refs.length;
00603                      for (int j = 0; j < count; j++)
00604                             printField(out, refs[j]);
00605                      // print object's stack crawl:
00606                      in.seek(leak.mCrawlOffset);
00607                      short crawlCount = leak.mCrawlCount;
00608                      while (crawlCount-- > 0) {
00609                          String line = in.readLine();
00610                             String location = FileLocator.getFileLocation(line);
00611                             out.println(location);
00612                      }
00613                      // print object's parents.
00614                      if (leak.mParents != null) {
00615                             out.println("<A NAME=\"" + leak.mName + "_parents\"></A>");
00616                             out.println("\nLeak Parents:");
00617                             Leak[] parents = leak.mParents;
00618                             count = parents.length;
00619                             for (int j = 0; j < count; j++)
00620                                    out.println("\t" + parents[j]);
00621                      }
00622               }
00623               out.println("</PRE>");
00624        }
00625 }