FELIX-2224 Add Configuration Admin support for the Memory Usage plugin
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@926544 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/memoryusage/pom.xml b/webconsole-plugins/memoryusage/pom.xml
index d7b5e5f..1aeafcf 100644
--- a/webconsole-plugins/memoryusage/pom.xml
+++ b/webconsole-plugins/memoryusage/pom.xml
@@ -69,9 +69,17 @@
javax.management.*,
org.osgi.framework,
org.slf4j.*,
- javax.servlet.*;
+
+ <!-- plug into the traditional Felix shell -->
org.apache.felix.shell;
- org.apache.felix.webconsole;resolution:=optional
+
+ <!-- plug into the web console -->
+ javax.servlet.*;
+ org.apache.felix.webconsole;
+
+ <!-- support configuration -->
+ org.osgi.service.cm;
+ org.osgi.service.metatype;resolution:=optional
</Import-Package>
<Private-Package>
org.apache.felix.webconsole.plugins.memoryusage.*
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
index e964971..18d0fc1 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
@@ -62,6 +62,20 @@
{
// web console might not be available, don't care
}
+
+ // register for configuration
+ try
+ {
+ MemoryUsageConfigurator tdp = new MemoryUsageConfigurator(support);
+ Dictionary<String, Object> properties = new Hashtable<String, Object>();
+ properties.put(Constants.SERVICE_PID, MemoryUsageConfigurator.NAME);
+ register(bundleContext, new String[]
+ { "org.osgi.service.cm.ManagedService" }, tdp, properties);
+ }
+ catch (Throwable t)
+ {
+ // Configuration Admin and Metatype Service API might not be available, don't care
+ }
}
public void stop(BundleContext bundleContext)
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java
new file mode 100644
index 0000000..8aad994
--- /dev/null
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.webconsole.plugins.memoryusage.internal;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+public class MemoryUsageConfigurator implements ManagedService, MetaTypeProvider
+{
+
+ static final String NAME = "org.apache.felix.webconsole.plugins.memoryusage.internal.MemoryUsageConfigurator";
+
+ private static final String PROP_DUMP_THRESHOLD = "dump.threshold";
+
+ private static final String PROP_DUMP_LOCATION = "dump.location";
+
+ private final MemoryUsageSupport support;
+
+ private ObjectClassDefinition ocd;
+
+ MemoryUsageConfigurator(final MemoryUsageSupport support)
+ {
+ this.support = support;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void updated(Dictionary properties) throws ConfigurationException
+ {
+ final Object thresholdValue = properties.get(PROP_DUMP_THRESHOLD);
+ if (thresholdValue != null)
+ {
+ final int threshold;
+ if (thresholdValue instanceof Number)
+ {
+ threshold = ((Number) thresholdValue).intValue();
+ }
+ else
+ {
+ // try to convert
+ try
+ {
+ threshold = Integer.parseInt(thresholdValue.toString());
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw failure(thresholdValue);
+ }
+ }
+
+ try
+ {
+ support.setThreshold(threshold);
+ }
+ catch (IllegalArgumentException iae)
+ {
+ throw failure(iae.getMessage());
+ }
+ }
+
+ final Object locationValue = properties.get(PROP_DUMP_LOCATION);
+ if (locationValue instanceof String)
+ {
+ support.setDumpLocation((String) locationValue);
+ }
+ }
+
+ public String[] getLocales()
+ {
+ return null;
+ }
+
+ public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
+ {
+ if (!NAME.equals(id))
+ {
+ return null;
+ }
+
+ if (ocd == null)
+ {
+
+ final ArrayList<AttributeDefinition> adList = new ArrayList<AttributeDefinition>();
+ adList.add(new AttributeDefinitionImpl(PROP_DUMP_THRESHOLD, "Dump Threshold",
+ "Threshold at which to automatically create a memory dump as a percentage in the range "
+ + MemoryUsageSupport.MIN_DUMP_THRESHOLD + " to " + MemoryUsageSupport.MAX_DUMP_THRESHOLD
+ + " or zero to disable automatic dump creation.", AttributeDefinition.INTEGER, new String[]
+ { String.valueOf(MemoryUsageSupport.DEFAULT_DUMP_THRESHOLD) }, 0, null, null)
+ {
+ @Override
+ public String validate(String value)
+ {
+ try
+ {
+ int threshold = Integer.parseInt(value);
+ if (threshold != 0
+ && (threshold < MemoryUsageSupport.MIN_DUMP_THRESHOLD || threshold > MemoryUsageSupport.MAX_DUMP_THRESHOLD))
+ {
+ return "Threshold must in the range " + MemoryUsageSupport.MIN_DUMP_THRESHOLD + " to "
+ + MemoryUsageSupport.MAX_DUMP_THRESHOLD + " or zero";
+ }
+ return ""; // everything ok
+ }
+ catch (NumberFormatException nfe)
+ {
+ return "Threshhold must be numeric";
+ }
+ }
+ });
+ adList.add(new AttributeDefinitionImpl(PROP_DUMP_LOCATION, "Dumpe Location",
+ "The filesystem location where heap dumps are stored. If this is null or empty (the default) the dumps are stored in "
+ + support.getDefaultDumpLocation(), ""));
+
+ ocd = new ObjectClassDefinition()
+ {
+
+ private final AttributeDefinition[] attrs = adList.toArray(new AttributeDefinition[adList.size()]);
+
+ public String getName()
+ {
+ return "Apache Felix Web Console Memory Usage Plugin";
+ }
+
+ public InputStream getIcon(int arg0)
+ {
+ return null;
+ }
+
+ public String getID()
+ {
+ return NAME;
+ }
+
+ public String getDescription()
+ {
+ return "Configuration of the Apache Felix Web Console Memory Usage Plugin.";
+ }
+
+ public AttributeDefinition[] getAttributeDefinitions(int filter)
+ {
+ return (filter == OPTIONAL) ? null : attrs;
+ }
+ };
+ }
+
+ return ocd;
+ }
+
+ private ConfigurationException failure(final Object invalidValue)
+ {
+ return new ConfigurationException(PROP_DUMP_THRESHOLD, "Invalid Dump Threshold value '" + invalidValue
+ + "': Must be an integer number in the range " + MemoryUsageSupport.MIN_DUMP_THRESHOLD + " to "
+ + MemoryUsageSupport.MAX_DUMP_THRESHOLD + " or zero to disable");
+ }
+
+ private static class AttributeDefinitionImpl implements AttributeDefinition
+ {
+
+ private final String id;
+ private final String name;
+ private final String description;
+ private final int type;
+ private final String[] defaultValues;
+ private final int cardinality;
+ private final String[] optionLabels;
+ private final String[] optionValues;
+
+ AttributeDefinitionImpl(final String id, final String name, final String description, final String defaultValue)
+ {
+ this(id, name, description, STRING, new String[]
+ { defaultValue }, 0, null, null);
+ }
+
+ AttributeDefinitionImpl(final String id, final String name, final String description, final int type,
+ final String[] defaultValues, final int cardinality, final String[] optionLabels,
+ final String[] optionValues)
+ {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.type = type;
+ this.defaultValues = defaultValues;
+ this.cardinality = cardinality;
+ this.optionLabels = optionLabels;
+ this.optionValues = optionValues;
+ }
+
+ public int getCardinality()
+ {
+ return cardinality;
+ }
+
+ public String[] getDefaultValue()
+ {
+ return defaultValues;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public String getID()
+ {
+ return id;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String[] getOptionLabels()
+ {
+ return optionLabels;
+ }
+
+ public String[] getOptionValues()
+ {
+ return optionValues;
+ }
+
+ public int getType()
+ {
+ return type;
+ }
+
+ public String validate(String arg0)
+ {
+ return null;
+ }
+ }
+}
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 78a92e2..be79755 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
@@ -1,18 +1,18 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
+ * or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
+ * regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
+ * with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
+ * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@@ -46,28 +46,56 @@
final class MemoryUsageSupport implements NotificationListener
{
+ /**
+ * The minimum allowed automatic heap dump threshold (value is 50).
+ */
+ static final int MIN_DUMP_THRESHOLD = 50;
+
+ /**
+ * The maximum allowed automatic heap dump threshold (value is 99).
+ */
+ static final int MAX_DUMP_THRESHOLD = 99;
+
+ /**
+ * The default automatic heap dump threshold if none has been configured
+ * (or no configuration has yet been provided) (value is 95).
+ */
+ static final int DEFAULT_DUMP_THRESHOLD = 95;
+
// 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;
+ // the default dump location: the dumps folder in the bundle private data
+ // or the current working directory
+ private final File defaultDumpLocation;
+ // the configured dump location
+ private File dumpLocation;
+
+ // the actual threshold (configured or dynamically set in the console UI)
private int threshold;
MemoryUsageSupport(final BundleContext context)
{
+ // get the dump location
File dumps = context.getDataFile("dumps");
if (dumps == null)
{
dumps = new File("dumps");
}
+ defaultDumpLocation = dumps.getAbsoluteFile();
- dumpLocation = dumps;
- dumpLocation.mkdirs();
+ // prepare the dump location
+ setDumpLocation(null);
+ // register for memory threshold notifications
NotificationEmitter memEmitter = (NotificationEmitter) getMemory();
memEmitter.addNotificationListener(this, null, null);
+
+ // set the initial automatic dump threshold
+ setThreshold(DEFAULT_DUMP_THRESHOLD);
}
void dispose()
@@ -77,19 +105,26 @@
{
memEmitter.removeNotificationListener(this);
}
- catch (ListenerNotFoundException lnfes)
+ catch (ListenerNotFoundException e)
{
- // TODO: not expected really ?
+ // don't care
}
}
+ /**
+ * Sets the threshold percentage.
+ *
+ * @param percentage The threshold as a percentage of memory consumption.
+ * This value may be 0 (zero) to switch off automatic heap dumps
+ * or in the range {@link #MIN_DUMP_THRESHOLD} to
+ * {@link #MAX_DUMP_THRESHOLD}.
+ * @throws IllegalArgumentException if the percentage value is outside of
+ * the valid range of thresholds. The message is the percentage
+ * value which is not accepted.
+ */
final void setThreshold(final int percentage)
{
- if (percentage < 50 || percentage > 100)
- {
- // wrong value
- }
- else
+ if (threshold == 0 || (threshold >= MIN_DUMP_THRESHOLD && threshold <= MAX_DUMP_THRESHOLD))
{
List<MemoryPoolMXBean> pools = getMemoryPools();
for (MemoryPoolMXBean pool : pools)
@@ -102,6 +137,10 @@
}
this.threshold = percentage;
}
+ else
+ {
+ throw new IllegalArgumentException(String.valueOf(threshold));
+ }
}
final int getThreshold()
@@ -189,10 +228,10 @@
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());
+ usedTotal += formatNumber(buf, "used", usage.getUsed());
+ initTotal += formatNumber(buf, "init", usage.getInit());
+ committedTotal += formatNumber(buf, "committed", usage.getCommitted());
+ maxTotal += formatNumber(buf, "max", usage.getMax());
final long score = 100L * usage.getUsed() / usage.getMax();
buf.append(",'score':'").append(score).append("%'");
@@ -203,10 +242,10 @@
// 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);
+ formatNumber(buf, "used", usedTotal);
+ formatNumber(buf, "init", initTotal);
+ formatNumber(buf, "committed", committedTotal);
+ formatNumber(buf, "max", maxTotal);
final long score = 100L * usedTotal / maxTotal;
buf.append(",'score':'").append(score).append("%'");
@@ -217,7 +256,7 @@
return buf.toString();
}
- private long doNumber(final StringBuilder buf, final String title, final long value)
+ long formatNumber(final StringBuilder buf, final String title, final long value)
{
final BigDecimal KB = new BigDecimal(1000L);
@@ -250,6 +289,28 @@
return value;
}
+ final String getDefaultDumpLocation()
+ {
+ return defaultDumpLocation.getAbsolutePath();
+ }
+
+ final void setDumpLocation(final String dumpLocation)
+ {
+ if (dumpLocation == null || dumpLocation.length() == 0)
+ {
+ this.dumpLocation = defaultDumpLocation;
+ }
+ else
+ {
+ this.dumpLocation = new File(dumpLocation).getAbsoluteFile();
+ }
+ }
+
+ final File getDumpLocation()
+ {
+ return dumpLocation;
+ }
+
final void listDumpFiles(final PrintHelper pw)
{
pw.title(dumpLocation.getAbsolutePath(), 1);
@@ -317,10 +378,14 @@
*
* @param live <code>true</code> if only live objects are to be returned
* @return
- * @throws NoSuchElementException If no provided mechanism is successfully used to create a heap dump
+ * @throws NoSuchElementException If no provided mechanism is successfully
+ * used to create a heap dump
*/
final File dumpHeap(String name, final boolean live)
{
+ // ensure dumplocation exists
+ dumpLocation.mkdirs();
+
File dump = dumpSunMBean(name, live);
if (dump == null)
{
@@ -372,10 +437,11 @@
void keyVal(final String key, final Object value);
}
- //---------- Various System Specific Heap Dump mechanisms
+ // ---------- Various System Specific Heap Dump mechanisms
/**
- * @see http://blogs.sun.com/sundararajan/entry/programmatically_dumping_heap_from_java
+ * @see http://blogs.sun.com/sundararajan/entry/
+ * programmatically_dumping_heap_from_java
*/
private File dumpSunMBean(String name, boolean live)
{
@@ -384,7 +450,6 @@
name = "heap." + System.currentTimeMillis() + ".hprof";
}
- dumpLocation.mkdirs();
File tmpFile = new File(dumpLocation, name);
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
@@ -409,7 +474,9 @@
/**
* @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
+ * @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)
{