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> </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"> </td></tr>
+ </tbody>
+ <tfoot class="ui-widget-header">
+ <tr><td colspan="4"> </td></tr>
+ </tfoot>
+</table>
+
+<p> </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>