/** * jvmtop - java monitoring for the command-line * * Copyright (C) 2013 by Patric Rufflar. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.jvmtop.monitor; import java.io.IOException; import java.lang.management.ClassLoadingMXBean; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; import java.lang.reflect.InvocationTargetException; import java.rmi.ConnectException; import java.util.Collection; import java.util.Comparator; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.jvmtop.openjdk.tools.ConnectionState; import com.jvmtop.openjdk.tools.LocalVirtualMachine; import com.jvmtop.openjdk.tools.ProxyClient; import com.sun.tools.attach.AttachNotSupportedException; /** * VMInfo retrieves or updates the metrics for a specific remote jvm, * using ProxyClient. * * TODO: refactor this class, seperate: * - connect / attach code * - updating metrics * - model * * @author paru * */ public class VMInfo { /** * Comparator providing ordering of VMInfo objects by the current heap usage of their monitored jvms * * @author paru * */ private static final class UsedHeapComparator implements Comparator<VMInfo> { @Override public int compare(VMInfo o1, VMInfo o2) { return Long.valueOf(o1.getHeapUsed()).compareTo( Long.valueOf(o2.getHeapUsed())); } } /** * Comparator providing ordering of VMInfo objects by the current CPU usage of their monitored jvms * * @author paru * */ private static final class CPULoadComparator implements Comparator<VMInfo> { @Override public int compare(VMInfo o1, VMInfo o2) { return Double.valueOf(o2.getCpuLoad()).compareTo( Double.valueOf(o1.getCpuLoad())); } } private ProxyClient proxyClient = null; //private VirtualMachine vm = null; private OperatingSystemMXBean osBean; private RuntimeMXBean runtimeMXBean; private Collection<java.lang.management.GarbageCollectorMXBean> gcMXBeans; private long lastGcTime; private long lastUpTime = -1; private long lastCPUTime = -1; private long gcCount = 0; private double cpuLoad = 0.0; private double gcLoad = 0.0; private MemoryMXBean memoryMXBean; private MemoryUsage heapMemoryUsage; private MemoryUsage nonHeapMemoryUsage; private ThreadMXBean threadMXBean; private VMInfoState state_ = VMInfoState.INIT; private String rawId_ = null; private LocalVirtualMachine localVm_; public static final Comparator<VMInfo> USED_HEAP_COMPARATOR = new UsedHeapComparator(); public static final Comparator<VMInfo> CPU_LOAD_COMPARATOR = new CPULoadComparator(); private long deltaUptime_; private long deltaCpuTime_; private long deltaGcTime_; private int updateErrorCount_ = 0; private long totalLoadedClassCount_; private ClassLoadingMXBean classLoadingMXBean_; private boolean deadlocksDetected_ = false; private String vmVersion_ = null; private String osUser_; private long threadCount_; private Map<String, String> systemProperties_; /** * @param lastCPUProcessTime * @param proxyClient * @param vm * @throws RuntimeException */ public VMInfo(ProxyClient proxyClient, LocalVirtualMachine localVm, String rawId) throws Exception { super(); localVm_ = localVm; rawId_ = rawId; this.proxyClient = proxyClient; //this.vm = vm; state_ = VMInfoState.ATTACHED; update(); } /** * TODO: refactor to constructor? * @param vmMap * @param localvm * @param vmid * @param vmInfo * @return */ public static VMInfo processNewVM(LocalVirtualMachine localvm, int vmid) { try { if (localvm == null || !localvm.isAttachable()) { Logger.getLogger("jvmtop").log(Level.FINE, "jvm is not attachable (PID=" + vmid + ")"); return VMInfo.createDeadVM(vmid, localvm); } return attachToVM(localvm, vmid); } catch (Exception e) { Logger.getLogger("jvmtop").log(Level.FINE, "error during attach (PID=" + vmid + ")", e); return VMInfo.createDeadVM(vmid, localvm); } } /** * * Creates a new VMInfo which is attached to a given LocalVirtualMachine * * @param localvm * @param vmid * @return * @throws AttachNotSupportedException * @throws IOException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws InvocationTargetException * @throws Exception */ private static VMInfo attachToVM(LocalVirtualMachine localvm, int vmid) throws AttachNotSupportedException, IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, Exception { //VirtualMachine vm = VirtualMachine.attach("" + vmid); try { ProxyClient proxyClient = ProxyClient.getProxyClient(localvm); proxyClient.connect(); if (proxyClient.getConnectionState() == ConnectionState.DISCONNECTED) { Logger.getLogger("jvmtop").log(Level.FINE, "connection refused (PID=" + vmid + ")"); return createDeadVM(vmid, localvm); } return new VMInfo(proxyClient, localvm, vmid + ""); } catch (ConnectException rmiE) { if (rmiE.getMessage().contains("refused")) { Logger.getLogger("jvmtop").log(Level.FINE, "connection refused (PID=" + vmid + ")", rmiE); return createDeadVM(vmid, localvm, VMInfoState.CONNECTION_REFUSED); } rmiE.printStackTrace(System.err); } catch (IOException e) { if ((e.getCause() != null && e.getCause() instanceof AttachNotSupportedException) || e.getMessage().contains("Permission denied")) { Logger.getLogger("jvmtop").log(Level.FINE, "could not attach (PID=" + vmid + ")", e); return createDeadVM(vmid, localvm, VMInfoState.CONNECTION_REFUSED); } e.printStackTrace(System.err); } catch (Exception e) { Logger.getLogger("jvmtop").log(Level.WARNING, "could not attach (PID=" + vmid + ")", e); } return createDeadVM(vmid, localvm); } private VMInfo() { } /** * Creates a dead VMInfo, representing a jvm which cannot be attached or other monitoring issues occurred. * * @param vmid * @param localVm * @return */ public static VMInfo createDeadVM(int vmid, LocalVirtualMachine localVm) { return createDeadVM(vmid, localVm, VMInfoState.ERROR_DURING_ATTACH); } /** * Creates a dead VMInfo, representing a jvm in a given state * which cannot be attached or other monitoring issues occurred. * @param vmid * @param localVm * @return */ public static VMInfo createDeadVM(int vmid, LocalVirtualMachine localVm, VMInfoState state) { VMInfo vmInfo = new VMInfo(); vmInfo.state_ = state; vmInfo.localVm_ = localVm; return vmInfo; } /** * @return the state */ public VMInfoState getState() { return state_; } /** * Updates all jvm metrics to the most recent remote values * * @throws Exception */ public void update() throws Exception { if (state_ == VMInfoState.ERROR_DURING_ATTACH || state_ == VMInfoState.DETACHED || state_ == VMInfoState.CONNECTION_REFUSED) { return; } if (proxyClient.isDead()) { state_ = VMInfoState.DETACHED; return; } try { proxyClient.flush(); osBean = proxyClient.getSunOperatingSystemMXBean(); runtimeMXBean = proxyClient.getRuntimeMXBean(); gcMXBeans = proxyClient.getGarbageCollectorMXBeans(); classLoadingMXBean_ = proxyClient.getClassLoadingMXBean(); memoryMXBean = proxyClient.getMemoryMXBean(); heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage(); threadMXBean = proxyClient.getThreadMXBean(); //TODO: fetch jvm-constant data only once systemProperties_ = runtimeMXBean.getSystemProperties(); vmVersion_ = extractShortVer(); osUser_ = systemProperties_.get("user.name"); updateInternal(); deadlocksDetected_ = threadMXBean.findDeadlockedThreads() != null || threadMXBean.findMonitorDeadlockedThreads() != null; } catch (Throwable e) { Logger.getLogger("jvmtop").log(Level.FINE, "error during update", e); updateErrorCount_++; if (updateErrorCount_ > 10) { state_ = VMInfoState.DETACHED; } else { state_ = VMInfoState.ATTACHED_UPDATE_ERROR; } } } /** * calculates internal delta metrics * @throws Exception */ private void updateInternal() throws Exception { long uptime = runtimeMXBean.getUptime(); long cpuTime = proxyClient.getProcessCpuTime(); //long cpuTime = osBean.getProcessCpuTime(); long gcTime = sumGCTimes(); gcCount = sumGCCount(); if (lastUpTime > 0 && lastCPUTime > 0 && gcTime > 0) { deltaUptime_ = uptime - lastUpTime; deltaCpuTime_ = (cpuTime - lastCPUTime) / 1000000; deltaGcTime_ = gcTime - lastGcTime; gcLoad = calcLoad(deltaCpuTime_, deltaGcTime_); cpuLoad = calcLoad(deltaUptime_, deltaCpuTime_); } lastUpTime = uptime; lastCPUTime = cpuTime; lastGcTime = gcTime; totalLoadedClassCount_ = classLoadingMXBean_.getTotalLoadedClassCount(); threadCount_ = threadMXBean.getThreadCount(); } /** * calculates a "load", given on two deltas * @param deltaUptime * @param deltaTime * @return */ private double calcLoad(double deltaUptime, double deltaTime) { if (deltaTime <= 0 || deltaUptime == 0) { return 0.0; } return Math.min(99.0, deltaTime / (deltaUptime * osBean.getAvailableProcessors())); } /** * Returns the sum of all GC times * @return */ private long sumGCTimes() { long sum = 0; for (java.lang.management.GarbageCollectorMXBean mxBean : gcMXBeans) { sum += mxBean.getCollectionTime(); } return sum; } /** * Returns the sum of all GC invocations * @return */ private long sumGCCount() { long sum = 0; for (java.lang.management.GarbageCollectorMXBean mxBean : gcMXBeans) { sum += mxBean.getCollectionCount(); } return sum; } public long getHeapUsed() { return heapMemoryUsage.getUsed(); } public long getHeapMax() { return heapMemoryUsage.getMax(); } public long getNonHeapUsed() { return nonHeapMemoryUsage.getUsed(); } public long getNonHeapMax() { return nonHeapMemoryUsage.getMax(); } public long getTotalLoadedClassCount() { return totalLoadedClassCount_; } public boolean hasDeadlockThreads() { return deadlocksDetected_; } public long getThreadCount() { return threadCount_; } /** * @return the cpuLoad */ public double getCpuLoad() { return cpuLoad; } /** * @return the gcLoad */ public double getGcLoad() { return gcLoad; } /** * @return the proxyClient */ public ProxyClient getProxyClient() { return proxyClient; } public String getDisplayName() { return localVm_.displayName(); } public Integer getId() { return localVm_.vmid(); } public String getRawId() { return rawId_; } public long getGcCount() { return gcCount; } /** * @return the vm */ /* public VirtualMachine getVm() { return vm; } */ public String getVMVersion() { return vmVersion_; } public String getOSUser() { return osUser_; } public long getGcTime() { return lastGcTime; } public RuntimeMXBean getRuntimeMXBean() { return runtimeMXBean; } public Collection<java.lang.management.GarbageCollectorMXBean> getGcMXBeans() { return gcMXBeans; } public MemoryMXBean getMemoryMXBean() { return memoryMXBean; } public ThreadMXBean getThreadMXBean() { return threadMXBean; } public OperatingSystemMXBean getOSBean() { return osBean; } public long getDeltaUptime() { return deltaUptime_; } public long getDeltaCpuTime() { return deltaCpuTime_; } public long getDeltaGcTime() { return deltaGcTime_; } public Map<String, String> getSystemProperties() { return systemProperties_; } /** * Extracts the jvmtop "short version" out of different properties * TODO: should this be refactored? * @param runtimeMXBean * @return */ private String extractShortVer() { String vmVer = systemProperties_.get("java.runtime.version"); String vmVendor = systemProperties_.get("java.vendor"); Pattern pattern = Pattern.compile("[0-9]\\.([0-9])\\.0_([0-9]+)-.*"); Matcher matcher = pattern.matcher(vmVer); if (matcher.matches()) { return vmVendor.charAt(0) + matcher.group(1) + "U" + matcher.group(2); } else { pattern = Pattern.compile(".*-(.*)_.*"); matcher = pattern.matcher(vmVer); if (matcher.matches()) { return vmVendor.charAt(0) + matcher.group(1).substring(2, 6); } return vmVer; } } }