Coverage Report - sk.baka.webvm.analyzer.ProblemAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
ProblemAnalyzer
70%
119/168
63%
48/76
4.25
 
 1  
 /**
 2  
  * Copyright 2009 Martin Vysny.
 3  
  *
 4  
  * This file is part of WebVM.
 5  
  *
 6  
  * WebVM is free software: you can redistribute it and/or modify
 7  
  * it under the terms of the GNU General Public License as published by
 8  
  * the Free Software Foundation, either version 3 of the License, or
 9  
  * (at your option) any later version.
 10  
  *
 11  
  * WebVM is distributed in the hope that it will be useful,
 12  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  
  * GNU General Public License for more details.
 15  
  *
 16  
  * You should have received a copy of the GNU General Public License
 17  
  * along with WebVM.  If not, see <http://www.gnu.org/licenses/>.
 18  
  */
 19  
 package sk.baka.webvm.analyzer;
 20  
 
 21  
 import sk.baka.webvm.analyzer.hostos.Memory;
 22  
 import java.io.File;
 23  
 import java.lang.management.ManagementFactory;
 24  
 import java.lang.management.MemoryPoolMXBean;
 25  
 import java.lang.management.MemoryUsage;
 26  
 import java.lang.management.ThreadInfo;
 27  
 import java.lang.management.ThreadMXBean;
 28  
 import java.lang.reflect.Method;
 29  
 import java.util.ArrayList;
 30  
 import java.util.List;
 31  
 import java.util.Properties;
 32  
 import java.util.logging.Level;
 33  
 import java.util.logging.Logger;
 34  
 import org.apache.commons.io.FileSystemUtils;
 35  
 import org.apache.commons.io.FileUtils;
 36  
 import sk.baka.tools.JavaUtils;
 37  
 import sk.baka.webvm.ThreadDump;
 38  
 import sk.baka.webvm.config.Config;
 39  
 import sk.baka.webvm.misc.MgmtUtils;
 40  
 import static sk.baka.webvm.misc.Constants.*;
 41  
 
 42  
 /**
 43  
  * Analyzes VM problems.
 44  
  * @author Martin Vysny
 45  
  */
 46  2
 public final class ProblemAnalyzer {
 47  
 
 48  1
     private static final Logger LOG = Logger.getLogger(ProblemAnalyzer.class.getName());
 49  
 
 50  
     /**
 51  
      * Finds deadlocked threads. Uses {@link ThreadMXBean#findDeadlockedThreads} on 1.6, falls back to {@link ThreadMXBean#findMonitorDeadlockedThreads()} on 1.5.
 52  
      * @param bean the thread mx bean
 53  
      * @return ids of deadlocked threads, null if no threads are deadlocked.
 54  
      */
 55  
     public static long[] findDeadlockedThreads(final ThreadMXBean bean) {
 56  
         long[] dt;
 57  
         // try to get the "findDeadlockedThreads()" 1.6 method.
 58  15
         Method m = null;
 59  
         try {
 60  15
             m = ThreadMXBean.class.getMethod("findDeadlockedThreads");
 61  0
         } catch (NoSuchMethodException ex) {
 62  
             // nope. We are on 1.5. We'll use findMonitorDeadlockedThreads()
 63  15
         }
 64  15
         if (m != null) {
 65  
             try {
 66  15
                 dt = (long[]) m.invoke(bean);
 67  0
             } catch (Exception ex) {
 68  0
                 throw new RuntimeException(ex);
 69  15
             }
 70  
         } else {
 71  0
             dt = bean.findMonitorDeadlockedThreads();
 72  
         }
 73  15
         return dt;
 74  
     }
 75  
 
 76  
     /**
 77  
      * Parses init values from given application.
 78  
      * @param config
 79  
      */
 80  
     public void configure(final Config config) {
 81  2
         this.config = new Config(config);
 82  2
     }
 83  2
     private Config config = null;
 84  
 
 85  
     /**
 86  
      * Parses given property and returns it as an integer. Allows default value to be returned in case of null.
 87  
      * @param props the properties
 88  
      * @param propName the property name
 89  
      * @param defaultValue returned when given property is not specified.
 90  
      * @return parsed property value
 91  
      */
 92  
     public static int parse(final Properties props, final String propName, final int defaultValue) {
 93  0
         String arg = props.getProperty(propName);
 94  0
         if (arg == null) {
 95  0
             return defaultValue;
 96  
         }
 97  0
         arg = arg.trim();
 98  
         try {
 99  0
             return Integer.parseInt(arg);
 100  0
         } catch (final Exception ex) {
 101  0
             LOG.log(Level.SEVERE, propName + ": failed to parse '" + arg + "'", ex);
 102  
         }
 103  0
         return defaultValue;
 104  
     }
 105  
     /**
 106  
      * The "Free disk space" problem class.
 107  
      */
 108  
     public static final String CLASS_FREE_DISK_SPACE = "Free disk space";
 109  
 
 110  
     private String getFreeDiskSpaceDesc() {
 111  12
         return "Triggered when there is less than " + config.minFreeDiskSpaceMb + "Mb of free space on some drive";
 112  
     }
 113  
     /**
 114  
      * The "Deadlocked threads" problem class.
 115  
      */
 116  
     public static final String CLASS_DEADLOCKED_THREADS = "Deadlocked threads";
 117  
     private static final String CLASS_DEADLOCKED_THREADS_DESC = "Triggered when there are some deadlocked threads. Finds cycles of threads that are in "
 118  
             + "deadlock waiting to acquire object monitors (also "
 119  
             + "<a href=\"http://java.sun.com/javase/6/docs/api/java/lang/management/LockInfo.html#OwnableSynchronizer\">ownable synchronizers"
 120  
             + "</a> when running on Java 6).";
 121  
     /**
 122  
      * The "GC CPU Usage" problem class.
 123  
      */
 124  
     public static final String CLASS_GC_CPU_USAGE = "GC CPU Usage";
 125  
 
 126  
     private String getGcCpuUsageDesc() {
 127  12
         return "Triggered when GC uses " + config.gcCpuTreshold + "% or more of CPU continuously for "
 128  
                 + (config.gcCpuTresholdSamples * HistorySampler.HISTORY_VMSTAT.getHistorySampleDelayMs() / MILLIS_IN_SECOND) + " seconds";
 129  
     }
 130  
     /**
 131  
      * The "GC Memory cleanup" problem class.
 132  
      */
 133  
     public static final String CLASS_GC_MEMORY_CLEANUP = "GC Memory cleanup";
 134  
 
 135  
     private String getGcMemoryCleanupDesc() {
 136  12
         return "Triggered when GC cannot make available more than "
 137  
                 + (HUNDRED_PERCENT - config.memAfterGcUsageTreshold) + "% of memory";
 138  
     }
 139  
     /**
 140  
      * The "Memory status" problem class.
 141  
      */
 142  
     public static final String CLASS_MEMORY_USAGE = "Memory usage";
 143  
 
 144  
     private String getMemUsageDesc() {
 145  12
         return "Triggered: never. Reports memory pools which are at least " + config.memUsageTreshold + "% full";
 146  
     }
 147  
     /**
 148  
      * The "Host Memory" problem class.
 149  
      */
 150  
     public static final String CLASS_HOST_MEMORY_USAGE = "Host Virtual Mem";
 151  
 
 152  
     private String getHostMemoryUsageDesc() {
 153  12
         return "Triggered when host uses " + config.hostVirtMem + "% or more virtual memory";
 154  
     }
 155  
 
 156  
     /**
 157  
      * Diagnose the VM and returns a list of problem reports.
 158  
      * @param history current history.
 159  
      * @return the list of reports.
 160  
      */
 161  
     public List<ProblemReport> getProblems(final List<HistorySample> history) {
 162  12
         final List<ProblemReport> result = new ArrayList<ProblemReport>();
 163  12
         result.add(getDeadlockReport());
 164  12
         result.add(getGCCPUUsageReport(history));
 165  12
         result.add(getMemStatReport());
 166  12
         result.add(getGCMemUsageReport());
 167  12
         result.add(getFreeDiskspaceReport());
 168  12
         result.add(getHostVirtMemReport());
 169  12
         return result;
 170  
     }
 171  
 
 172  
     /**
 173  
      * Prepares the {@link #CLASS_HOST_MEMORY_USAGE} report.
 174  
      * @return report
 175  
      */
 176  
     public ProblemReport getHostVirtMemReport() {
 177  12
         final MemoryUsage phys = Memory.getPhysicalMemory();
 178  12
         if (phys == null) {
 179  0
             return new ProblemReport(false, CLASS_HOST_MEMORY_USAGE, "Host memory reporting unsupported on this platform", getHostMemoryUsageDesc());
 180  
         }
 181  
         // buffer/cache information is available?
 182  12
         final boolean cbAvailable = (phys.getCommitted() == phys.getUsed());
 183  12
         final StringBuilder sb = new StringBuilder();
 184  12
         if (cbAvailable) {
 185  0
             sb.append("buffers/cache detection not supported, disabled\n");
 186  
         }
 187  12
         final MemoryUsage swap = Memory.getSwap();
 188  12
         sb.append("Physical memory used: ");
 189  12
         sb.append(phys.getCommitted() * HUNDRED_PERCENT / phys.getMax());
 190  12
         sb.append("%, minus buffers/cache: ");
 191  12
         sb.append(phys.getUsed() * HUNDRED_PERCENT / phys.getMax());
 192  12
         sb.append("%\nSwap used: ").append(MgmtUtils.getUsagePerc(swap));
 193  12
         sb.append('\n');
 194  12
         final long total = phys.getMax() + (swap == null ? 0 : swap.getMax());
 195  12
         final long used = phys.getUsed() + (swap == null ? 0 : swap.getUsed());
 196  12
         final long usedPerc = used * HUNDRED_PERCENT / total;
 197  12
         sb.append("Total virtual memory usage: ");
 198  12
         sb.append(usedPerc);
 199  12
         sb.append('%');
 200  12
         final boolean isProblem = !cbAvailable && (usedPerc >= config.hostVirtMem);
 201  12
         return new ProblemReport(isProblem, CLASS_HOST_MEMORY_USAGE, sb.toString(), getHostMemoryUsageDesc());
 202  
     }
 203  
 
 204  
     /**
 205  
      * Prepares the {@link #CLASS_GC_CPU_USAGE} report.
 206  
      * @param history the history
 207  
      * @return report
 208  
      */
 209  
     public ProblemReport getGCCPUUsageReport(final List<HistorySample> history) {
 210  12
         int tresholdViolationCount = 0;
 211  12
         int maxTresholdViolationCount = 0;
 212  12
         int totalAvgTreshold = 0;
 213  12
         int avgTresholdViolation = 0;
 214  12
         int maxAvgTresholdViolation = 0;
 215  12
         for (final HistorySample h : history) {
 216  1
             totalAvgTreshold += h.gcCpuUsage;
 217  1
             if (h.gcCpuUsage >= config.gcCpuTreshold) {
 218  0
                 tresholdViolationCount++;
 219  0
                 avgTresholdViolation += h.gcCpuUsage;
 220  0
                 if (maxTresholdViolationCount < tresholdViolationCount) {
 221  0
                     maxTresholdViolationCount = tresholdViolationCount;
 222  0
                     maxAvgTresholdViolation = avgTresholdViolation;
 223  
                 }
 224  
             } else {
 225  1
                 tresholdViolationCount = 0;
 226  1
                 avgTresholdViolation = 0;
 227  
             }
 228  
         }
 229  12
         maxAvgTresholdViolation = maxTresholdViolationCount == 0 ? 0 : maxAvgTresholdViolation / maxTresholdViolationCount;
 230  12
         totalAvgTreshold = history.size() == 0 ? 0 : totalAvgTreshold / history.size();
 231  12
         if (maxTresholdViolationCount >= config.gcCpuTresholdSamples) {
 232  0
             return new ProblemReport(true, CLASS_GC_CPU_USAGE, "GC spent more than " + config.gcCpuTreshold + "% (avg. "
 233  
                     + maxAvgTresholdViolation + "%) of CPU for " + (maxTresholdViolationCount * HistorySampler.HISTORY_VMSTAT.getHistorySampleDelayMs() / MILLIS_IN_SECOND) + " seconds",
 234  
                     getGcCpuUsageDesc());
 235  
         }
 236  12
         return new ProblemReport(false, CLASS_GC_CPU_USAGE, "Avg. GC CPU usage last "
 237  
                 + (history.size() * HistorySampler.HISTORY_VMSTAT.getHistorySampleDelayMs() / MILLIS_IN_SECOND) + " seconds: " + totalAvgTreshold + "%",
 238  
                 getGcCpuUsageDesc());
 239  
     }
 240  
 
 241  
     /**
 242  
      * Prepares the {@link #CLASS_MEMORY_STATUS} report.
 243  
      * @return report
 244  
      */
 245  
     public ProblemReport getMemStatReport() {
 246  12
         final List<MemoryPoolMXBean> beans = ManagementFactory.getMemoryPoolMXBeans();
 247  12
         if (JavaUtils.isBlank(beans)) {
 248  0
             return new ProblemReport(false, CLASS_MEMORY_USAGE, "INFO: No memory pool information", getMemUsageDesc());
 249  
         }
 250  12
         final StringBuilder sb = new StringBuilder();
 251  12
         for (final MemoryPoolMXBean bean : beans) {
 252  60
             final MemoryUsage usage = bean.getUsage();
 253  60
             if (usage == null || !bean.isCollectionUsageThresholdSupported() || !bean.isUsageThresholdSupported()) {
 254  24
                 continue;
 255  
             }
 256  24
             final long used = usage.getUsed() * HUNDRED_PERCENT / usage.getMax();
 257  24
             if (used >= config.memUsageTreshold) {
 258  0
                 sb.append("INFO: Pool [");
 259  0
                 sb.append(bean.getName());
 260  0
                 sb.append("] is now ");
 261  0
                 sb.append(used);
 262  0
                 sb.append("% full\n");
 263  
             }
 264  24
         }
 265  12
         if (sb.length() == 0) {
 266  12
             return new ProblemReport(false, CLASS_MEMORY_USAGE, "Heap usage: " + MgmtUtils.getUsagePerc(MgmtUtils.getHeapFromRuntime()), getMemUsageDesc());
 267  
         }
 268  0
         sb.append("\nTry performing a GC: this should decrease the memory usage. If not, you may need to increase the memory or check for memory leaks");
 269  0
         return new ProblemReport(false, CLASS_MEMORY_USAGE, sb.toString(), getMemUsageDesc());
 270  
     }
 271  
 
 272  
     /**
 273  
      * Prepares the {@link #CLASS_GC_MEMORY_CLEANUP} report.
 274  
      * @return report
 275  
      */
 276  
     public ProblemReport getGCMemUsageReport() {
 277  12
         final List<MemoryPoolMXBean> beans = ManagementFactory.getMemoryPoolMXBeans();
 278  12
         if (JavaUtils.isBlank(beans)) {
 279  0
             return new ProblemReport(false, CLASS_GC_MEMORY_CLEANUP, "INFO: No memory pool information", getGcMemoryCleanupDesc());
 280  
         }
 281  12
         final StringBuilder sb = new StringBuilder();
 282  12
         for (final MemoryPoolMXBean bean : beans) {
 283  60
             final MemoryUsage usage = bean.getCollectionUsage();
 284  60
             if (usage == null || !bean.isCollectionUsageThresholdSupported() || !bean.isUsageThresholdSupported()) {
 285  24
                 continue;
 286  
             }
 287  24
             if (usage.getMax() <= 0) {
 288  0
                 continue;
 289  
             }
 290  24
             final long used = usage.getUsed() * HUNDRED_PERCENT / usage.getMax();
 291  24
             if (used >= config.memAfterGcUsageTreshold) {
 292  0
                 sb.append("Pool [");
 293  0
                 sb.append(bean.getName());
 294  0
                 sb.append("] is ");
 295  0
                 sb.append(used);
 296  0
                 sb.append("% full after GC\n");
 297  
             }
 298  24
         }
 299  12
         if (sb.length() == 0) {
 300  12
             return new ProblemReport(false, CLASS_GC_MEMORY_CLEANUP, "OK", getGcMemoryCleanupDesc());
 301  
         }
 302  0
         sb.append("\nYou may need to increase the memory or check for memory leaks");
 303  0
         return new ProblemReport(true, CLASS_GC_MEMORY_CLEANUP, sb.toString(), getGcMemoryCleanupDesc());
 304  
     }
 305  
 
 306  
     /**
 307  
      * Prepares the {@link #CLASS_DEADLOCKED_THREADS} report.
 308  
      * @return report
 309  
      */
 310  
     public static ProblemReport getDeadlockReport() {
 311  13
         final StringBuilder sb = new StringBuilder();
 312  13
         final ThreadMXBean bean = ManagementFactory.getThreadMXBean();
 313  13
         if (bean == null) {
 314  0
             return new ProblemReport(false, CLASS_DEADLOCKED_THREADS, "INFO: Report Unavailable - ThreadMXBean null", CLASS_DEADLOCKED_THREADS_DESC);
 315  
         }
 316  13
         final long[] dt = findDeadlockedThreads(bean);
 317  13
         if (JavaUtils.isEmpty(dt)) {
 318  7
             return new ProblemReport(false, CLASS_DEADLOCKED_THREADS, "None", CLASS_DEADLOCKED_THREADS_DESC);
 319  
         }
 320  18
         for (final long thread : dt) {
 321  12
             final ThreadInfo info = bean.getThreadInfo(thread, Integer.MAX_VALUE);
 322  12
             sb.append("Locked thread: ");
 323  12
             sb.append(ThreadDump.getThreadMetadata(info));
 324  12
             sb.append('\n');
 325  12
             sb.append("Stacktrace:");
 326  12
             sb.append(printStackTrace(info.getStackTrace()));
 327  
         }
 328  6
         return new ProblemReport(true, CLASS_DEADLOCKED_THREADS, sb.toString(), CLASS_DEADLOCKED_THREADS_DESC);
 329  
     }
 330  
 
 331  
     private static String printStackTrace(final StackTraceElement[] stacktrace) {
 332  12
         if (JavaUtils.isEmpty(stacktrace)) {
 333  0
             return "unknown\n";
 334  
         }
 335  12
         final StringBuilder sb = new StringBuilder();
 336  12
         sb.append('\n');
 337  84
         for (int i = 0; i < stacktrace.length; i++) {
 338  72
             sb.append("\tat ");
 339  72
             sb.append(stacktrace[i]);
 340  72
             sb.append('\n');
 341  
         }
 342  12
         return sb.toString();
 343  
     }
 344  
 
 345  
     /**
 346  
      * Analyzes free disk space.
 347  
      * @return free disk space report.
 348  
      */
 349  
     public ProblemReport getFreeDiskspaceReport() {
 350  12
         final StringBuilder sb = new StringBuilder();
 351  12
         boolean problem = false;
 352  24
         for (final File root : File.listRoots()) {
 353  
             try {
 354  12
                 final long freeSpaceKb = FileSystemUtils.freeSpaceKb(root.getAbsolutePath());
 355  12
                 final long freeSpaceMb = freeSpaceKb / KIBIBYTES;
 356  12
                 if (freeSpaceMb < config.minFreeDiskSpaceMb) {
 357  0
                     problem = true;
 358  0
                     sb.append("Low disk space: ");
 359  
                 }
 360  12
                 sb.append(root.getAbsolutePath());
 361  12
                 sb.append("  ");
 362  12
                 sb.append(FileUtils.byteCountToDisplaySize(freeSpaceKb * KIBIBYTES));
 363  12
                 sb.append(" free\n");
 364  0
             } catch (Exception ex) {
 365  0
                 LOG.log(Level.INFO, "Failed to get free space on " + root.getAbsolutePath(), ex);
 366  0
                 sb.append("Failed to get free space on ");
 367  0
                 sb.append(root.getAbsolutePath());
 368  0
                 sb.append(": ");
 369  0
                 sb.append(ex.toString());
 370  0
                 sb.append("\n");
 371  12
             }
 372  
         }
 373  12
         if (sb.length() == 0) {
 374  0
             sb.append("OK");
 375  
         }
 376  12
         return new ProblemReport(problem, CLASS_FREE_DISK_SPACE, sb.toString(), getFreeDiskSpaceDesc());
 377  
     }
 378  
 }