/*
 * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

package sun.jvm.hotspot.tools;

import java.io.*;
import java.util.*;

import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.memory.*;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.tools.*;
import sun.jvm.hotspot.utilities.*;

/**
  A command line tool to print perm. generation statistics.
*/

public class PermStat extends Tool {
   boolean verbose = true;

   public static void main(String[] args) {
      PermStat ps = new PermStat();
      ps.start(args);
      ps.stop();
   }

   private static class ClassData {
      Klass klass;
      long  size;

      ClassData(Klass klass, long size) {
         this.klass = klass; this.size = size;
      }
   }

   private static class LoaderData {
      long     numClasses;
      long     classSize;
      List     classDetail = new ArrayList(); // List<ClassData>
   }

   public void run() {
      printInternStringStatistics();
      printClassLoaderStatistics();
   }

   private void printInternStringStatistics() {
      class StringStat implements StringTable.StringVisitor {
         private int count;
         private long size;
         private OopField stringValueField;

         StringStat() {
            VM vm = VM.getVM();
            SystemDictionary sysDict = vm.getSystemDictionary();
            InstanceKlass strKlass = sysDict.getStringKlass();
            // String has a field named 'value' of type 'char[]'.
            stringValueField = (OopField) strKlass.findField("value", "[C");
         }

         private long stringSize(Instance instance) {
            // We include String content in size calculation.
            return instance.getObjectSize() +
                   stringValueField.getValue(instance).getObjectSize();
         }

         public void visit(Instance str) {
            count++;
            size += stringSize(str);
         }

         public void print() {
            System.out.println(count +
                  " intern Strings occupying " + size + " bytes.");
         }
      }

      StringStat stat = new StringStat();
      StringTable strTable = VM.getVM().getStringTable();
      strTable.stringsDo(stat);
      stat.print();
   }

   private void printClassLoaderStatistics() {
      final PrintStream out = System.out;
      final PrintStream err = System.err;
      final Map loaderMap = new HashMap();
      // loader data for bootstrap class loader
      final LoaderData bootstrapLoaderData = new LoaderData();
      if (verbose) {
         err.print("finding class loader instances ..");
      }

      VM vm = VM.getVM();
      ObjectHeap heap = vm.getObjectHeap();
      Klass classLoaderKlass = vm.getSystemDictionary().getClassLoaderKlass();
      try {
         heap.iterateObjectsOfKlass(new DefaultHeapVisitor() {
                         public boolean doObj(Oop oop) {
                            loaderMap.put(oop, new LoaderData());
                                                        return false;
                         }
                      }, classLoaderKlass);
      } catch (Exception se) {
         se.printStackTrace();
      }

      if (verbose) {
         err.println("done.");
         err.print("computing per loader stat ..");
      }

      SystemDictionary dict = VM.getVM().getSystemDictionary();
      dict.classesDo(new SystemDictionary.ClassAndLoaderVisitor() {
                        public void visit(Klass k, Oop loader) {
                           if (! (k instanceof InstanceKlass)) {
                              return;
                           }
                           LoaderData ld = (loader != null) ? (LoaderData)loaderMap.get(loader)
                                                            : bootstrapLoaderData;
                           if (ld != null) {
                              ld.numClasses++;
                              long size = computeSize((InstanceKlass)k);
                              ld.classDetail.add(new ClassData(k, size));
                              ld.classSize += size;
                           }
                        }
                     });

      if (verbose) {
         err.println("done.");
         err.print("please wait.. computing liveness");
      }

      // compute reverse pointer analysis (takes long time for larger app)
      ReversePtrsAnalysis analysis = new ReversePtrsAnalysis();

      if (verbose) {
         analysis.setHeapProgressThunk(new HeapProgressThunk() {
            public void heapIterationFractionUpdate(double fractionOfHeapVisited) {
               err.print('.');
            }
            // This will be called after the iteration is complete
            public void heapIterationComplete() {
               err.println("done.");
            }
         });
      }

      try {
         analysis.run();
      } catch (Exception e) {
         // e.printStackTrace();
         if (verbose)
           err.println("liveness analysis may be inaccurate ...");
      }
      ReversePtrs liveness = VM.getVM().getRevPtrs();

      out.println("class_loader\tclasses\tbytes\tparent_loader\talive?\ttype");
      out.println();

      long numClassLoaders = 1L;
      long totalNumClasses = bootstrapLoaderData.numClasses;
      long totalClassSize  = bootstrapLoaderData.classSize;
      long numAliveLoaders = 1L;
      long numDeadLoaders  = 0L;

      // print bootstrap loader details
      out.print("<bootstrap>");
      out.print('\t');
      out.print(bootstrapLoaderData.numClasses);
      out.print('\t');
      out.print(bootstrapLoaderData.classSize);
      out.print('\t');
      out.print("  null  ");
      out.print('\t');
      // bootstrap loader is always alive
      out.print("live");
      out.print('\t');
      out.println("<internal>");

      for (Iterator keyItr = loaderMap.keySet().iterator(); keyItr.hasNext();) {
         Oop loader = (Oop) keyItr.next();
         LoaderData data = (LoaderData) loaderMap.get(loader);
         numClassLoaders ++;
         totalNumClasses += data.numClasses;
         totalClassSize  += data.classSize;

         out.print(loader.getHandle());
         out.print('\t');
         out.print(data.numClasses);
         out.print('\t');
         out.print(data.classSize);
         out.print('\t');

         class ParentFinder extends DefaultOopVisitor {
            public void doOop(OopField field, boolean isVMField) {
               if (field.getID().getName().equals("parent")) {
                  parent = field.getValue(getObj());
               }
            }
            private Oop parent = null;
            public Oop getParent() { return parent; }
         }

         ParentFinder parentFinder = new ParentFinder();
         loader.iterate(parentFinder, false);
         Oop parent = parentFinder.getParent();
         out.print((parent != null)? parent.getHandle().toString() : "  null  ");
         out.print('\t');
         boolean alive = (liveness != null) ? (liveness.get(loader) != null) : true;
         out.print(alive? "live" : "dead");
         if (alive) numAliveLoaders++; else numDeadLoaders++;
         out.print('\t');
         Klass loaderKlass = loader.getKlass();
         if (loaderKlass != null) {
            out.print(loaderKlass.getName().asString());
            out.print('@');
            out.print(loader.getKlass().getHandle());
         } else {
            out.print("    null!    ");
         }
         out.println();
      }

      out.println();
      // summary line
      out.print("total = ");
      out.print(numClassLoaders);
      out.print('\t');
      out.print(totalNumClasses);
      out.print('\t');
      out.print(totalClassSize);
      out.print('\t');
      out.print("    N/A    ");
      out.print('\t');
      out.print("alive=");
      out.print(numAliveLoaders);
      out.print(", dead=");
      out.print(numDeadLoaders);
      out.print('\t');
      out.print("    N/A    ");
      out.println();
   }

   private static long objectSize(Oop oop) {
      return oop == null ? 0L : oop.getObjectSize();
   }

   // Don't count the shared empty arrays
   private static long arraySize(Array arr) {
     return arr.getLength() != 0L ? arr.getObjectSize() : 0L;
   }

   private long computeSize(InstanceKlass k) {
      long size = 0L;
      // the InstanceKlass object itself
      size += k.getObjectSize();

      // Constant pool
      ConstantPool cp = k.getConstants();
      size += cp.getObjectSize();
      size += objectSize(cp.getCache());
      size += objectSize(cp.getTags());

      // Interfaces
      size += arraySize(k.getLocalInterfaces());
      size += arraySize(k.getTransitiveInterfaces());

      // Inner classes
      size += objectSize(k.getInnerClasses());

      // Fields
      size += objectSize(k.getFields());

      // Methods
      ObjArray methods = k.getMethods();
      int nmethods = (int) methods.getLength();
      if (nmethods != 0L) {
         size += methods.getObjectSize();
         for (int i = 0; i < nmethods; ++i) {
            Method m = (Method) methods.getObjAt(i);
            size += m.getObjectSize();
            size += objectSize(m.getConstMethod());
         }
      }

      // MethodOrdering - an int array that records the original
      // ordering of methods in the class file
      size += arraySize(k.getMethodOrdering());

      return size;
   }
}
