FELIX-2498 Add support to limit memory dumps written by setting a minimum dump interval
FELIX-2498 Set default threshold for automatic memory dumps to zero

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@981193 13f79535-47bb-0310-9956-ffa450edef68
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
index 7fbe40a..42bdc1c 100644
--- 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
@@ -67,7 +67,7 @@
                 }
                 catch (NumberFormatException nfe)
                 {
-                    throw failure(thresholdValue);
+                    throw thresholdFailure(thresholdValue);
                 }
             }
 
@@ -77,7 +77,7 @@
             }
             catch (IllegalArgumentException iae)
             {
-                throw failure(iae.getMessage());
+                throw thresholdFailure(iae.getMessage());
             }
         }
         else
@@ -85,6 +85,41 @@
             support.setThreshold(-1);
         }
 
+        final Object intervalValue = properties.get(MemoryUsageConstants.PROP_DUMP_INTERVAL);
+        if (intervalValue != null)
+        {
+            final int interval;
+            if (intervalValue instanceof Number)
+            {
+                interval = ((Number) intervalValue).intValue();
+            }
+            else
+            {
+                // try to convert
+                try
+                {
+                    interval = Integer.parseInt(intervalValue.toString());
+                }
+                catch (NumberFormatException nfe)
+                {
+                    throw intervalFailure(intervalValue);
+                }
+            }
+
+            try
+            {
+                support.setInterval(interval);
+            }
+            catch (IllegalArgumentException iae)
+            {
+                throw intervalFailure(iae.getMessage());
+            }
+        }
+        else
+        {
+            support.setInterval(-1);
+        }
+
         final Object locationValue = properties.get(MemoryUsageConstants.PROP_DUMP_LOCATION);
         if (locationValue instanceof String)
         {
@@ -112,11 +147,12 @@
         {
 
             final ArrayList<AttributeDefinition> adList = new ArrayList<AttributeDefinition>();
+
             adList.add(new AttributeDefinitionImpl(MemoryUsageConstants.PROP_DUMP_THRESHOLD, "Dump Threshold",
                 "Threshold at which to automatically create a memory dump as a percentage in the range "
-                    + MemoryUsageConstants.MIN_DUMP_THRESHOLD + " to " + MemoryUsageConstants.MAX_DUMP_THRESHOLD
-                    + " or zero to disable automatic dump creation.", AttributeDefinition.INTEGER, new String[]
-                    { String.valueOf(MemoryUsageConstants.DEFAULT_DUMP_THRESHOLD) }, 0, null, null)
+                + MemoryUsageConstants.MIN_DUMP_THRESHOLD + " to " + MemoryUsageConstants.MAX_DUMP_THRESHOLD
+                + " or zero to disable automatic dump creation.", AttributeDefinition.INTEGER, new String[]
+                                                                                                          { String.valueOf(MemoryUsageConstants.DEFAULT_DUMP_THRESHOLD) }, 0, null, null)
             {
                 @Override
                 public String validate(String value)
@@ -126,17 +162,49 @@
                         int threshold = Integer.parseInt(value);
                         if (!MemoryUsageConstants.isThresholdValid(threshold))
                         {
-                            return "Threshold must in the range " + MemoryUsageConstants.MIN_DUMP_THRESHOLD + " to "
-                                + MemoryUsageConstants.MAX_DUMP_THRESHOLD + " or zero";
+                            return "Dump Threshold must in the range " + MemoryUsageConstants.MIN_DUMP_THRESHOLD
+                                + " to " + MemoryUsageConstants.MAX_DUMP_THRESHOLD + " or zero";
                         }
                         return ""; // everything ok
                     }
                     catch (NumberFormatException nfe)
                     {
-                        return "Threshhold must be numeric";
+                        return "Dump Threshhold must be numeric";
                     }
                 }
             });
+
+            adList.add(new AttributeDefinitionImpl(MemoryUsageConstants.PROP_DUMP_INTERVAL, "Dump Interval",
+                "The minimum interval between two consecutive memory dumps being taken in seconds. "
+                    + "This property allows the limitation of the number of memory dumps being taken. "
+                    + "The default value for the interval is 6 hours. This means that a memory threshold "
+                    + "event is ignored unless the last memory dump has been taken at least 6 hours earlier. "
+                    + "This property allows limiting the number of memory dumps in case memory consumption is "
+                    + "oscillating around the threshold point. The property must be an integer value or be "
+                    + "parseable to an integer value. This should be a positive value or zero to force each "
+                    + "memory threshold event to cause a memory dump (discouraged).", AttributeDefinition.INTEGER,
+                new String[]
+                    { String.valueOf(MemoryUsageConstants.DEFAULT_DUMP_INTERVAL) }, 0, null, null)
+            {
+                @Override
+                public String validate(String value)
+                {
+                    try
+                    {
+                        int interval = Integer.parseInt(value);
+                        if (interval < 0)
+                        {
+                            return "Dump Interval must be zero or a positive number";
+                        }
+                        return ""; // everything ok
+                    }
+                    catch (NumberFormatException nfe)
+                    {
+                        return "Dump Interval must be numeric";
+                    }
+                }
+            });
+
             adList.add(new AttributeDefinitionImpl(MemoryUsageConstants.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(), ""));
@@ -176,13 +244,19 @@
         return ocd;
     }
 
-    private ConfigurationException failure(final Object invalidValue)
+    private ConfigurationException thresholdFailure(final Object invalidValue)
     {
         return new ConfigurationException(MemoryUsageConstants.PROP_DUMP_THRESHOLD, "Invalid Dump Threshold value '"
             + invalidValue + "': Must be an integer number in the range " + MemoryUsageConstants.MIN_DUMP_THRESHOLD
             + " to " + MemoryUsageConstants.MAX_DUMP_THRESHOLD + " or zero to disable");
     }
 
+    private ConfigurationException intervalFailure(final Object invalidValue)
+    {
+        return new ConfigurationException(MemoryUsageConstants.PROP_DUMP_INTERVAL, "Invalid Dump Interval value '"
+            + invalidValue + "': Must be a positive integer number or zero to disable");
+    }
+
     private static class AttributeDefinitionImpl implements AttributeDefinition
     {
 
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java
index a89ff98..87964e1 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java
@@ -64,20 +64,47 @@
     static final String PROP_DUMP_THRESHOLD = "felix.memoryusage.dump.threshold";
 
     /**
+     * The minimum interval between two consecutive memory dumps being taken
+     * in seconds. This property allows the limitation of the number of memory
+     * dumps being taken. The default value for the interval is 6 hours. This
+     * means that
+     * a memory threshold event is ignored unless the last memory dump has been
+     * taken
+     * at least 6 hours earlier.
+     * <p>
+     * This property allows limiting the number of memory dumps in case memory
+     * consumption is oscillating around the threshold point.
+     * <p>
+     * The property must be an integer value or be parseable to an integer
+     * value. This should be a positive value or zero to force each memory
+     * threshold event to cause a memory dump (discouraged). If the value is
+     * negative or not parseable to an int, the default value of
+     * {@link #DEFAULT_DUMP_INTERVAL 6 hours} applies.
+     */
+    static final String PROP_DUMP_INTERVAL = "felix.memoryusage.dump.interval";
+
+    /**
      * The default automatic heap dump threshold if none has been configured
      * (or no configuration has yet been provided).
      */
-    static final int DEFAULT_DUMP_THRESHOLD = 95;
+    static final int DEFAULT_DUMP_THRESHOLD = 0;
+
     /**
      * The maximum allowed automatic heap dump threshold.
      */
     static final int MAX_DUMP_THRESHOLD = 99;
+
     /**
      * The minimum allowed automatic heap dump threshold.
      */
     static final int MIN_DUMP_THRESHOLD = 50;
 
     /**
+     * The default interval betwen consecutive dumps (6 hours).
+     */
+    static final long DEFAULT_DUMP_INTERVAL = 6 * 3600 * 1000;
+
+    /**
      * Returns <code>true</code> if the given <code>threshold</code>value is
      * valid; that is if the vaue is either zero or in the range [
      * {@link #MIN_DUMP_THRESHOLD}..{@link #MAX_DUMP_THRESHOLD}].
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 8af03ca..4899b4b 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
@@ -40,7 +40,6 @@
 import org.apache.felix.webconsole.ConfigurationPrinter;
 import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.WebConsoleUtil;
-import org.osgi.framework.BundleContext;
 import org.osgi.service.log.LogService;
 
 @SuppressWarnings("serial")
@@ -113,6 +112,7 @@
         resolver.put("__files__", filesBuf.toString());
         resolver.put("__status__", statusBuf.toString());
         resolver.put("__threshold__", String.valueOf(support.getThreshold()));
+        resolver.put("__interval__", String.valueOf(support.getInterval()));
         resolver.put("__overall__", jph.getString());
         resolver.put("__pools__", support.getMemoryPoolsJson());
 
@@ -214,6 +214,19 @@
                 }
                 resp.sendRedirect(req.getRequestURI());
             }
+            else if ("interval".equals(command))
+            {
+                try
+                {
+                    int interval = Integer.parseInt(req.getParameter("interval"));
+                    support.setInterval(interval);
+                }
+                catch (Exception e)
+                {
+                    // ignore
+                }
+                resp.sendRedirect(req.getRequestURI());
+            }
             else
             {
                 super.doPost(req, resp);
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 5e65893..484c874 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
@@ -64,12 +64,22 @@
     // the default threshold value
     private final int defaultThreshold;
 
+    // the minimum number of milliseconds between two consecutive memory
+    // dumps written. this setting allows limitting the generation of memory
+    // dumps if memory consumption is oscillating around the memory
+    // threshold value
+    private long minDumpInterval;
+
     // the configured dump location
     private File dumpLocation;
 
     // the actual threshold (configured or dynamically set in the console UI)
     private int threshold;
 
+    // the system time of the last memory snapshot written; initialized so as
+    // at least write one dump
+    private long nextDumpTime = -1;
+
     // log service
     private ServiceReference logServiceReference;
     private Object logService;
@@ -151,6 +161,29 @@
         }
 
         this.defaultThreshold = defaultThreshold;
+
+        // set the initial automatic dump threshold
+        int interval;
+        String propInterval = context.getProperty(MemoryUsageConstants.PROP_DUMP_INTERVAL);
+        if (propInterval != null)
+        {
+            try
+            {
+                interval = Integer.parseInt(propInterval);
+            }
+            catch (Exception e)
+            {
+                // NumberFormatException - if propTreshold cannot be parsed to
+                // int
+                // IllegalArgumentException - if threshold is invalid
+                interval = -1;
+            }
+        }
+        else
+        {
+            interval = -1;
+        }
+        setInterval(interval);
     }
 
     void dispose()
@@ -233,10 +266,30 @@
         return threshold;
     }
 
+    final void setInterval(long interval)
+    {
+        if (interval < 0)
+        {
+            interval = MemoryUsageConstants.DEFAULT_DUMP_INTERVAL;
+        }
+        else
+        {
+            interval = 1000L * interval;
+        }
+        this.minDumpInterval = interval;
+        log(LogService.LOG_INFO, "Setting Automatic Memory Dump Interval to %d seconds", getInterval());
+    }
+
+    final long getInterval()
+    {
+        return minDumpInterval / 1000L;
+    }
+
     final void printMemory(final PrintHelper pw)
     {
         pw.title("Overall Memory Use", 0);
         pw.keyVal("Heap Dump Threshold", getThreshold() + "%");
+        pw.keyVal("Heap Dump Interval", getInterval() + " seconds");
         printOverallMemory(pw);
 
         pw.title("Memory Pools", 0);
@@ -502,16 +555,25 @@
         String notifType = notification.getType();
         if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED))
         {
-            log(LogService.LOG_WARNING, "Received Memory Threshold Exceed Notification, dumping Heap");
-            try
+            if (System.currentTimeMillis() >= nextDumpTime)
             {
-                File file = dumpHeap(null, true);
-                log(LogService.LOG_WARNING, "Heap dumped to " + file);
+                log(LogService.LOG_WARNING, "Received Memory Threshold Exceeded Notification, dumping Heap");
+                try
+                {
+                    File file = dumpHeap(null, true);
+                    log(LogService.LOG_WARNING, "Heap dumped to " + file);
+                    nextDumpTime = System.currentTimeMillis() + minDumpInterval;
+                }
+                catch (NoSuchElementException e)
+                {
+                    log(LogService.LOG_ERROR,
+                        "Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
+                }
             }
-            catch (NoSuchElementException e)
+            else
             {
-                log(LogService.LOG_ERROR,
-                    "Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
+                log(LogService.LOG_WARNING,
+                    "Ignoring Memory Threshold Exceeded Notification, minimum dump interval since last dump has not passed yet");
             }
         }
     }
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 fdf0132..f10faba 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
@@ -36,6 +36,8 @@
 dump.overview = Memory Usage Overview
 dump.threshold = Automatic Heap Dump Threshold (%)
 dump.threshold.set = Set Threshold
+dump.interval = Automatic Heap Dump Interval (s)
+dump.interval.set = Set Interval
 
 dump.pools.name = Name
 dump.pools.score = Usage Score
diff --git a/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle_de.properties b/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle_de.properties
index 76c4c56..7f844d6 100644
--- a/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle_de.properties
+++ b/webconsole-plugins/memoryusage/src/main/resources/OSGI-INF/l10n/bundle_de.properties
@@ -36,6 +36,8 @@
 dump.overview = Hauptspeicher Nutzung Übersicht
 dump.threshold = Speicher Abbild Schwellwert (%) 
 dump.threshold.set = Schwellwert setzen
+dump.interval = Speicher Abbild Intervall (s)
+dump.interval.set = Intervall setzen
 
 dump.pools.name = Name
 dump.pools.score = Nutzung
diff --git a/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html b/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html
index 0ca537e..8a8c2fd 100644
--- a/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html
+++ b/webconsole-plugins/memoryusage/src/main/resources/templates/memoryusage.html
@@ -172,6 +172,16 @@
                 </form>
             </td>
         </tr>
+        <tr>
+            <td>${dump.interval}</td>
+            <td>
+                <form method="post" action="">
+                    <input type="hidden" name="command" value="interval" />
+                    <input type="text" name="interval" value="${__interval__}" />
+                    <input type="submit" name="submit" value="${dump.interval.set}" />
+                </form>
+            </td>
+        </tr>
     </tbody>
 </table>