FELIX-2208 A few improvements:
   * Detailed tabular listing of Memory Pools
   * Add support for generation of Heap Dumps on IBM VMs
   * Add support for sorting of Memory Pools table

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@925188 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java
index 22e72bd..9a8a45d 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java
@@ -20,6 +20,7 @@
 
 import java.io.File;
 import java.io.PrintStream;
+import java.util.NoSuchElementException;
 import java.util.StringTokenizer;
 
 import org.apache.felix.shell.Command;
@@ -189,10 +190,9 @@
             File dumpFile = support.dumpHeap(dumpTarget, !all);
             out.println("Heap dumped to " + dumpFile + " (" + dumpFile.length() + " bytes)");
         }
-        catch (Exception e)
+        catch (NoSuchElementException e)
         {
-            err.println("Problem dumping heap");
-            e.printStackTrace(err);
+            err.println("Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
         }
     }
 
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
index a673473..d4f44ee 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
@@ -26,6 +26,7 @@
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.NoSuchElementException;
 import java.util.zip.Deflater;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -39,11 +40,16 @@
 import org.apache.felix.webconsole.ConfigurationPrinter;
 import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.WebConsoleUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @SuppressWarnings("serial")
 public class MemoryUsagePanel extends AbstractWebConsolePlugin implements ConfigurationPrinter, AttachmentProvider
 {
 
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     private final MemoryUsageSupport support;
 
     public MemoryUsagePanel(final MemoryUsageSupport support)
@@ -62,7 +68,7 @@
     @Override
     public String getTitle()
     {
-        return "Heap Dumps";
+        return "%dump.title";
     }
 
     @SuppressWarnings("unchecked")
@@ -114,6 +120,7 @@
         resolver.put("__status__", statusBuf.toString());
         resolver.put("__threshold__", String.valueOf(support.getThreshold()));
         resolver.put("__overall__", jph.getString());
+        resolver.put("__pools__", support.getMemoryPoolsJson());
 
         String template = readTemplateFile("/templates/memoryusage.html");
         pw.println(template);
@@ -183,15 +190,17 @@
             String command = req.getParameter("command");
             if ("dump".equals(command))
             {
+                resp.setContentType("text/plain; charset=UTF-8");
                 try
                 {
                     File file = support.dumpHeap(null, false);
-                    resp.setContentType("text/plain; charset=UTF-8");
                     resp.getWriter().print("Dumped heap to " + file.getName());
                 }
-                catch (Exception e)
+                catch (NoSuchElementException e)
                 {
-                    // TODO: handle
+                    resp.getWriter().print(
+                        "Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
+                    log.error("Heap Dump creation failed: JVM has no known Heap Dump API");
                 }
             }
             else if ("gc".equals(command))
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
index dd6f9be..78a92e2 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
@@ -23,9 +23,14 @@
 import java.lang.management.MemoryMXBean;
 import java.lang.management.MemoryNotificationInfo;
 import java.lang.management.MemoryPoolMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.NoSuchElementException;
 
 import javax.management.ListenerNotFoundException;
 import javax.management.MBeanServer;
@@ -44,6 +49,8 @@
     // This is the name of the HotSpot Diagnostic MBean
     private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     private final File dumpLocation;
 
     private int threshold;
@@ -163,6 +170,86 @@
         }
     }
 
+    final String getMemoryPoolsJson()
+    {
+        final StringBuilder buf = new StringBuilder();
+        buf.append("[");
+
+        long usedTotal = 0;
+        long initTotal = 0;
+        long committedTotal = 0;
+        long maxTotal = 0;
+
+        final List<MemoryPoolMXBean> pools = getMemoryPools();
+        for (MemoryPoolMXBean pool : pools)
+        {
+            buf.append("{");
+
+            buf.append("'name':'").append(pool.getName()).append('\'');
+            buf.append(",'type':'").append(pool.getType()).append('\'');
+
+            MemoryUsage usage = pool.getUsage();
+            usedTotal += doNumber(buf, "used", usage.getUsed());
+            initTotal += doNumber(buf, "init", usage.getInit());
+            committedTotal += doNumber(buf, "committed", usage.getCommitted());
+            maxTotal += doNumber(buf, "max", usage.getMax());
+
+            final long score = 100L * usage.getUsed() / usage.getMax();
+            buf.append(",'score':'").append(score).append("%'");
+
+            buf.append("},");
+        }
+
+        // totalisation
+        buf.append("{");
+        buf.append("'name':'Total','type':'TOTAL'");
+        doNumber(buf, "used", usedTotal);
+        doNumber(buf, "init", initTotal);
+        doNumber(buf, "committed", committedTotal);
+        doNumber(buf, "max", maxTotal);
+
+        final long score = 100L * usedTotal / maxTotal;
+        buf.append(",'score':'").append(score).append("%'");
+
+        buf.append("}");
+
+        buf.append("]");
+        return buf.toString();
+    }
+
+    private long doNumber(final StringBuilder buf, final String title, final long value)
+    {
+
+        final BigDecimal KB = new BigDecimal(1000L);
+        final BigDecimal MB = new BigDecimal(1000L * 1000);
+        final BigDecimal GB = new BigDecimal(1000L * 1000 * 1000);
+
+        BigDecimal bd = new BigDecimal(value);
+        final String suffix;
+        if (bd.compareTo(GB) > 0)
+        {
+            bd = bd.divide(GB);
+            suffix = "GB";
+        }
+        else if (bd.compareTo(MB) > 0)
+        {
+            bd = bd.divide(MB);
+            suffix = "MB";
+        }
+        else if (bd.compareTo(KB) > 0)
+        {
+            bd = bd.divide(KB);
+            suffix = "kB";
+        }
+        else
+        {
+            suffix = "B";
+        }
+        bd = bd.setScale(2, RoundingMode.UP);
+        buf.append(",'").append(title).append("':'").append(bd).append(suffix).append('\'');
+        return value;
+    }
+
     final void listDumpFiles(final PrintHelper pw)
     {
         pw.title(dumpLocation.getAbsolutePath(), 1);
@@ -230,34 +317,22 @@
      *
      * @param live <code>true</code> if only live objects are to be returned
      * @return
-     * @see http://blogs.sun.com/sundararajan/entry/
-     *      programmatically_dumping_heap_from_java
+     * @throws NoSuchElementException If no provided mechanism is successfully used to create a heap dump
      */
-    final File dumpHeap(String name, final boolean live) throws Exception
+    final File dumpHeap(String name, final boolean live)
     {
-        try
+        File dump = dumpSunMBean(name, live);
+        if (dump == null)
         {
-            if (name == null)
-            {
-                name = "heap." + System.currentTimeMillis() + ".bin";
-            }
-            dumpLocation.mkdirs();
-            File tmpFile = new File(dumpLocation, name);
-            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
-            server.invoke(new ObjectName(HOTSPOT_BEAN_NAME), "dumpHeap", new Object[]
-                { tmpFile.getAbsolutePath(), live }, new String[]
-                { String.class.getName(), boolean.class.getName() });
-            return tmpFile;
-            // } catch (JMException je) {
-            // } catch (IOException ioe) {
-        }
-        finally
-        {
-
+            dump = dumpIbmDump(name);
         }
 
-        // failure
-        // return null;
+        if (dump == null)
+        {
+            throw new NoSuchElementException();
+        }
+
+        return dump;
     }
 
     final MemoryMXBean getMemory()
@@ -272,18 +347,18 @@
 
     public void handleNotification(Notification notification, Object handback)
     {
-        Logger log = LoggerFactory.getLogger(getClass());
         String notifType = notification.getType();
         if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED))
         {
+            log.warn("Received Memory Threshold Exceed Notification, dumping Heap");
             try
             {
                 File file = dumpHeap(null, true);
                 log.warn("Heap dumped to " + file);
             }
-            catch (Exception e)
+            catch (NoSuchElementException e)
             {
-                log.error("Failed dumping heap", e);
+                log.error("Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
             }
         }
     }
@@ -296,4 +371,95 @@
 
         void keyVal(final String key, final Object value);
     }
+
+    //---------- Various System Specific Heap Dump mechanisms
+
+    /**
+     * @see http://blogs.sun.com/sundararajan/entry/programmatically_dumping_heap_from_java
+     */
+    private File dumpSunMBean(String name, boolean live)
+    {
+        if (name == null)
+        {
+            name = "heap." + System.currentTimeMillis() + ".hprof";
+        }
+
+        dumpLocation.mkdirs();
+        File tmpFile = new File(dumpLocation, name);
+        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+
+        try
+        {
+            server.invoke(new ObjectName(HOTSPOT_BEAN_NAME), "dumpHeap", new Object[]
+                { tmpFile.getAbsolutePath(), live }, new String[]
+                { String.class.getName(), boolean.class.getName() });
+
+            log.debug("dumpSunMBean: Dumped Heap to {} using Sun HotSpot MBean", tmpFile);
+            return tmpFile;
+        }
+        catch (Throwable t)
+        {
+            log.debug("dumpSunMBean: Dump by Sun HotSpot MBean not working", t);
+            tmpFile.delete();
+        }
+
+        return null;
+    }
+
+    /**
+     * @param name
+     * @return
+     * @see http://publib.boulder.ibm.com/infocenter/javasdk/v5r0/index.jsp?topic=/com.ibm.java.doc.diagnostics.50/diag/tools/heapdump_enable.html
+     */
+    private File dumpIbmDump(String name)
+    {
+        try
+        {
+            // to try to indicate which file will contain the heap dump
+            long minFileTime = System.currentTimeMillis();
+
+            // call the com.ibm.jvm.Dump.HeapDump() method
+            Class<?> c = ClassLoader.getSystemClassLoader().loadClass("com.ibm.jvm.Dump");
+            Method m = c.getDeclaredMethod("HeapDump", (Class<?>[]) null);
+            m.invoke(null, (Object[]) null);
+
+            // find the file in the current working directory
+            File dir = new File("").getAbsoluteFile();
+            File[] files = dir.listFiles();
+            if (files != null)
+            {
+                for (File file : files)
+                {
+                    if (file.isFile() && file.lastModified() > minFileTime)
+                    {
+                        if (name == null)
+                        {
+                            name = file.getName();
+                        }
+                        File target = new File(dumpLocation, name);
+                        file.renameTo(target);
+
+                        log.debug("dumpSunMBean: Dumped Heap to {} using IBM Dump.HeapDump()", target);
+                        return target;
+                    }
+                }
+
+                log.debug("dumpIbmDump: None of {} files '{}' is younger than {}", new Object[]
+                    { files.length, dir, minFileTime });
+            }
+            else
+            {
+                log.debug("dumpIbmDump: Hmm '{}' does not seem to be a directory; isdir={} ??", dir, dir.isDirectory());
+            }
+
+            log.warn("dumpIbmDump: Heap Dump has been created but cannot be located");
+            return dumpLocation;
+        }
+        catch (Throwable t)
+        {
+            log.debug("dumpIbmDump: Dump by IBM Dump class not working", t);
+        }
+
+        return null;
+    }
 }
diff --git a/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle.properties
index 7b8c3a9..fdf0132 100644
--- a/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -29,6 +29,7 @@
 
 
 # Memory Usage Plugin
+dump.title = Memory Usage
 dump.dump = Dump Heap
 dump.gc = Run Garbage Collector
 
@@ -36,6 +37,14 @@
 dump.threshold = Automatic Heap Dump Threshold (%)
 dump.threshold.set = Set Threshold
 
+dump.pools.name = Name
+dump.pools.score = Usage Score
+dump.pools.used = Used
+dump.pools.committed = Committed
+dump.pools.max = Maximum
+dump.pools.init = Initial
+dump.pools.type = Type
+
 dump.statline = Dump Files: {0} file(s), {1} bytes
 dump.name = File Name
 dump.date = Creation Date
diff --git a/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html b/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html
index 55131d9..3b2106a 100644
--- a/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html
+++ b/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html
@@ -63,17 +63,60 @@
     }
 }
 
+function renderPools(/* Object[] */ pools) {
+    var tbody = $("#pools > tbody").empty();
+    for ( var idx = 0; idx < pools.length; idx++ ) {
+        var pool = pools[idx];
+        var trElement = tr( null, null, [
+            td( null, null, [ text( pool.name ) ] ),
+            td( null, null, [ text( pool.score ) ] ),
+            td( null, null, [ text( pool.used ) ] ),
+            td( null, null, [ text( pool.committed ) ] ),
+            td( null, null, [ text( pool.max ) ] ),
+            td( null, null, [ text( pool.init ) ] ),
+            td( null, null, [ text( pool.type ) ] ),
+        ]);
+
+        // append to body (unless last row append to tfoot)        
+        if (idx < pools.length - 1) {
+            tbody.append(trElement);
+        } else {
+            $("#pools > tfoot").empty().append(trElement);
+        }   
+    }
+}
+
 var __files__ = ${__files__};
 var __status__ = ${__status__};
 var __overall__ = ${__overall__};
+var __pools__ = ${__pools__};
 
 $(document).ready(function() {
 
     $(".statline").html("${dump.statline}".msgFormat(__status__[0].toLocaleString(), __status__[1].toLocaleString()));
 
-    renderData(__files__);
     renderOverall(__overall__);
-    
+    renderPools(__pools__);
+    renderData(__files__);
+
+    // check for cookie
+    var cv = $.cookies.get("webconsole.memusage");
+    var lo = (cv ? cv.split(",") : [1,0]);
+    var poolsTable = $("#pools").tablesorter({
+        headers: {
+            1: { sorter:"digit" },
+            2: { sorter:"digit" },
+            3: { sorter:"digit" },
+            4: { sorter:"digit" },
+            5: { sorter:"digit" },
+        },
+        // textExtraction:mixedLinksExtraction,
+        sortList: cv ? [lo] : false
+    }).bind("sortEnd", function() {
+        poolsTable.eq(0).attr("config");
+        $.cookies.set("webconsole.memusage", poolsTable.sortList.toString());
+    });
+        
     $("#dumpButton")
         .click(function() {
             $.post(window.location.pathname,
@@ -95,8 +138,6 @@
                 }
             );
         });
-
-    
 });
 
 
@@ -114,7 +155,7 @@
 </div>
 
 
-<table id="overview" class="tablesorter nicetable noauto">
+<table id="overview" class="nicetable">
     <thead>
         <tr>
             <th colspan="2">${dump.overview}</th>
@@ -136,13 +177,35 @@
 
 <p>&nbsp;</p>
 
-<table id="plugin_table" class="tablesorter nicetable noauto">
+<table id="pools" class="tablesorter nicetable noauto">
     <thead>
         <tr>
-            <th class="col_Name">${dump.name}</th>
-            <th class="col_Date">${dump.date}</th>
-            <th class="col_Size">${dump.size}</th>
-            <th class="col_Size">${dump.action}</th>
+            <th class="col_pool_name">${dump.pools.name}</th>
+            <th class="col_pool_score">${dump.pools.score}</th>
+            <th class="col_pool_used">${dump.pools.used}</th>
+            <th class="col_pool_committed">${dump.pools.committed}</th>
+            <th class="col_pool_max">${dump.pools.max}</th>
+            <th class="col_pool_init">${dump.pools.init}</th>
+            <th class="col_pool_type">${dump.pools.type}</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr><td colspan="7">&nbsp;</td></tr>
+    </tbody>
+    <tfoot class="ui-widget-header">
+        <tr><td colspan="4">&nbsp;</td></tr>
+    </tfoot>
+</table>
+
+<p>&nbsp;</p>
+
+<table id="plugin_table" class="nicetable">
+    <thead>
+        <tr>
+            <th>${dump.name}</th>
+            <th>${dump.date}</th>
+            <th>${dump.size}</th>
+            <th>${dump.action}</th>
         </tr>
     </thead>
     <tbody>