
org.glowroot.agent.live.LiveJvmServiceImpl Maven / Gradle / Ivy
/*
* Copyright 2015 the original author or authors.
*
* Licensed 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.glowroot.agent.live;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;
import javax.management.Descriptor;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.TabularData;
import org.glowroot.agent.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.google.common.base.Joiner;
import org.glowroot.agent.shaded.google.common.base.MoreObjects;
import org.glowroot.agent.shaded.google.common.base.Splitter;
import org.glowroot.agent.shaded.google.common.base.StandardSystemProperty;
import org.glowroot.agent.shaded.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.google.common.collect.ImmutableSet;
import org.glowroot.agent.shaded.google.common.collect.Lists;
import org.glowroot.agent.shaded.google.common.collect.Maps;
import org.glowroot.agent.shaded.google.common.collect.Ordering;
import org.glowroot.agent.shaded.google.common.collect.Sets;
import org.glowroot.agent.shaded.slf4j.Logger;
import org.glowroot.agent.shaded.slf4j.LoggerFactory;
import org.glowroot.agent.impl.TransactionCollector;
import org.glowroot.agent.impl.TransactionRegistry;
import org.glowroot.agent.util.LazyPlatformMBeanServer;
import org.glowroot.agent.util.PatternObjectNameQueryExp;
import org.glowroot.common.live.ImmutableAvailability;
import org.glowroot.common.live.ImmutableCapabilities;
import org.glowroot.common.live.ImmutableHeapFile;
import org.glowroot.common.live.ImmutableMBeanMeta;
import org.glowroot.common.live.ImmutableProcessInfo;
import org.glowroot.common.live.LiveJvmService;
public class LiveJvmServiceImpl implements LiveJvmService {
private static final Logger logger = LoggerFactory.getLogger(LiveJvmServiceImpl.class);
private static final String HOT_SPOT_DIAGNOSTIC_MBEAN_NAME =
"com.sun.management:type=HotSpotDiagnostic";
private static final ImmutableSet numericAttributeTypes =
ImmutableSet.of("long", "int", "double", "float", "java.lang.Long", "java.lang.Integer",
"java.lang.Double", "java.lang.Float");
private final LazyPlatformMBeanServer lazyPlatformMBeanServer;
private final ThreadDumpService threadDumpService;
private final Availability threadAllocatedBytesAvailability;
private final String processId;
public LiveJvmServiceImpl(LazyPlatformMBeanServer lazyPlatformMBeanServer,
TransactionRegistry transactionRegistry, TransactionCollector transactionCollector,
Availability threadAllocatedBytesAvailability) {
this.lazyPlatformMBeanServer = lazyPlatformMBeanServer;
threadDumpService =
new ThreadDumpService(transactionRegistry, transactionCollector);
this.threadAllocatedBytesAvailability = threadAllocatedBytesAvailability;
this.processId = parseProcessId(ManagementFactory.getRuntimeMXBean().getName());
}
@Override
public Map getMBeanTree(MBeanTreeRequest request) throws Exception {
Set objectNames = lazyPlatformMBeanServer.queryNames(null, null);
// can't use Maps.newTreeMap() because of OpenJDK6 type inference bug
// see https://code.google.com/p/guava-libraries/issues/detail?id=635
Map sortedRootNodes =
new TreeMap(String.CASE_INSENSITIVE_ORDER);
for (ObjectName objectName : objectNames) {
String domain = objectName.getDomain();
MBeanTreeInnerNode node = sortedRootNodes.get(domain);
if (node == null) {
node = new MBeanTreeInnerNode(domain);
sortedRootNodes.put(domain, node);
}
List propertyValues = ObjectNames.getPropertyValues(objectName);
for (int i = 0; i < propertyValues.size() - 1; i++) {
node = node.getOrCreateNode(propertyValues.get(i));
}
String name = objectName.toString();
String value = propertyValues.get(propertyValues.size() - 1);
if (request.expanded().contains(name)) {
Map sortedAttributeMap =
getMBeanSortedAttributeMap(objectName);
node.addLeafNode(new MBeanTreeLeafNode(value, name, true, sortedAttributeMap));
} else {
node.addLeafNode(new MBeanTreeLeafNode(value, name, false, null));
}
}
return sortedRootNodes;
}
@Override
public Map getMBeanSortedAttributeMap(String serverId,
String objectName) throws Exception {
return getMBeanSortedAttributeMap(ObjectName.getInstance(objectName));
}
@Override
public List getMatchingMBeanObjectNames(String serverId, String partialMBeanObjectName,
int limit) throws InterruptedException {
Set objectNames = lazyPlatformMBeanServer.queryNames(null,
new ObjectNameQueryExp(partialMBeanObjectName));
List names = Lists.newArrayList();
for (ObjectName objectName : objectNames) {
names.add(objectName.toString());
}
ImmutableList sortedNames =
Ordering.from(String.CASE_INSENSITIVE_ORDER).immutableSortedCopy(names);
if (sortedNames.size() > limit) {
sortedNames = sortedNames.subList(0, limit);
}
return sortedNames;
}
@Override
public MBeanMeta getMBeanMeta(String serverId, String mbeanObjectName) throws Exception {
Set objectNames = getObjectNames(mbeanObjectName);
ImmutableList attributeNames = Ordering.from(String.CASE_INSENSITIVE_ORDER)
.immutableSortedCopy(getAttributeNames(objectNames));
boolean pattern = mbeanObjectName.contains("*");
return ImmutableMBeanMeta.builder()
.unmatched(objectNames.isEmpty() && pattern)
.unavailable(objectNames.isEmpty() && !pattern)
.addAllAttributeNames(attributeNames)
.build();
}
@Override
public ProcessInfo getProcessInfo(String serverId) {
String command = System.getProperty("sun.java.command");
String mainClass = "";
List arguments = ImmutableList.of();
if (command != null) {
int index = command.indexOf(' ');
if (index == -1) {
mainClass = command;
} else {
mainClass = command.substring(0, index);
arguments =
Lists.newArrayList(Splitter.on(' ').split(command.substring(index + 1)));
}
}
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
String jvm = "";
String javaVmName = StandardSystemProperty.JAVA_VM_NAME.value();
if (javaVmName != null) {
jvm = javaVmName + " (" + StandardSystemProperty.JAVA_VM_VERSION.value() + ", "
+ System.getProperty("java.vm.info") + ")";
}
String java = "";
String javaVersion = StandardSystemProperty.JAVA_VERSION.value();
if (javaVersion != null) {
java = "version " + javaVersion + ", vendor "
+ StandardSystemProperty.JAVA_VM_VENDOR.value();
}
String javaHome = MoreObjects.firstNonNull(StandardSystemProperty.JAVA_HOME.value(), "");
return ImmutableProcessInfo.builder()
.startTime(runtimeMXBean.getStartTime())
.uptime(runtimeMXBean.getUptime())
.pid(processId)
.mainClass(mainClass)
.mainClassArguments(arguments)
.jvm(jvm)
.java(java)
.javaHome(javaHome)
.jvmArguments(runtimeMXBean.getInputArguments())
.build();
}
@Override
public AllThreads getAllThreads() {
return threadDumpService.getAllThreads();
}
@Override
public String getHeapDumpDefaultDirectory(String serverId) {
String heapDumpPath = getHeapDumpPathFromCommandLine();
if (heapDumpPath == null) {
String javaTempDir =
MoreObjects.firstNonNull(StandardSystemProperty.JAVA_IO_TMPDIR.value(), ".");
heapDumpPath = new File(javaTempDir).getAbsolutePath();
}
return heapDumpPath;
}
@Override
public long getAvailableDiskSpace(String serverId, String directory) throws IOException {
File dir = new File(directory);
if (!dir.exists()) {
throw new IOException("Directory doesn't exist");
}
if (!dir.isDirectory()) {
throw new IOException("Path is not a directory");
}
return dir.getFreeSpace();
}
@Override
public HeapFile dumpHeap(String serverId, String directory) throws Exception {
File dir = new File(directory);
if (!dir.exists()) {
throw new IOException("Directory doesn't exist");
}
if (!dir.isDirectory()) {
throw new IOException("Path is not a directory");
}
String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
File file = new File(dir, "heap-dump-" + timestamp + ".hprof");
int i = 1;
while (file.exists()) {
// this seems unlikely now that timestamp is included in filename
i++;
file = new File(dir, "heap-dump-" + timestamp + "-" + i + ".hprof");
}
ObjectName objectName = ObjectName.getInstance(HOT_SPOT_DIAGNOSTIC_MBEAN_NAME);
lazyPlatformMBeanServer.invoke(objectName, "dumpHeap",
new Object[] {file.getAbsolutePath(), false},
new String[] {"java.lang.String", "boolean"});
return ImmutableHeapFile.of(file.getAbsolutePath(), file.length());
}
@Override
public void gc() {
// using MemoryMXBean.gc() instead of System.gc() in hope that it will someday bypass
// -XX:+DisableExplicitGC (see https://bugs.openjdk.java.net/browse/JDK-6396411)
ManagementFactory.getMemoryMXBean().gc();
}
@Override
public Capabilities getCapabilities(String serverId) {
return ImmutableCapabilities.builder()
.threadCpuTime(getThreadCpuTimeAvailability())
.threadContentionTime(getThreadContentionAvailability())
.threadAllocatedBytes(threadAllocatedBytesAvailability)
.build();
}
private Map getMBeanSortedAttributeMap(ObjectName objectName)
throws Exception {
MBeanInfo mBeanInfo = lazyPlatformMBeanServer.getMBeanInfo(objectName);
// can't use Maps.newTreeMap() because of OpenJDK6 type inference bug
// see https://code.google.com/p/guava-libraries/issues/detail?id=635
Map sortedAttributeMap =
new TreeMap(String.CASE_INSENSITIVE_ORDER);
for (MBeanAttributeInfo attribute : mBeanInfo.getAttributes()) {
Object value;
try {
value = lazyPlatformMBeanServer.getAttribute(objectName, attribute.getName());
} catch (Exception e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
Throwable rootCause = getRootCause(e);
value = "<" + rootCause.getClass().getName() + ": " + rootCause.getMessage() + ">";
}
sortedAttributeMap.put(attribute.getName(), getMBeanAttributeValue(value));
}
return sortedAttributeMap;
}
private static Throwable getRootCause(Throwable t) {
Throwable cause = t.getCause();
if (cause == null) {
return t;
} else {
return getRootCause(cause);
}
}
// see list of allowed attribute value types:
// http://docs.oracle.com/javase/7/docs/api/javax/management/openmbean/OpenType.html
// #ALLOWED_CLASSNAMES_LIST
private static @Nullable Object getMBeanAttributeValue(@Nullable Object value) {
if (value == null) {
return null;
} else if (value instanceof CompositeData) {
return getCompositeDataValue((CompositeData) value);
} else if (value instanceof TabularData) {
return getTabularDataValue((TabularData) value);
} else if (value.getClass().isArray()) {
return getArrayValue(value);
} else if (value instanceof Number) {
return value;
} else {
return value.toString();
}
}
private static Object getCompositeDataValue(CompositeData compositeData) {
// linked hash map used to preserve attribute ordering
Map valueMap = Maps.newLinkedHashMap();
for (String key : compositeData.getCompositeType().keySet()) {
valueMap.put(key, getMBeanAttributeValue(compositeData.get(key)));
}
return valueMap;
}
private static Object getTabularDataValue(TabularData tabularData) {
// linked hash map used to preserve row ordering
Map> rowMap = Maps.newLinkedHashMap();
Set attributeNames = tabularData.getTabularType().getRowType().keySet();
for (Object key : tabularData.keySet()) {
// TabularData.keySet() returns "Set> but is declared Set> for
// compatibility reasons" (see javadocs) so safe to cast to List>
List> keyList = (List>) key;
@SuppressWarnings("argument.type.incompatible")
String keyString = Joiner.on(", ").join(keyList);
@SuppressWarnings("argument.type.incompatible")
CompositeData compositeData = tabularData.get(keyList.toArray());
// linked hash map used to preserve attribute ordering
Map valueMap = Maps.newLinkedHashMap();
for (String attributeName : attributeNames) {
valueMap.put(attributeName,
getMBeanAttributeValue(compositeData.get(attributeName)));
}
rowMap.put(keyString, valueMap);
}
return rowMap;
}
private static Object getArrayValue(Object value) {
int length = Array.getLength(value);
List*@Nullable*/Object> valueList = Lists.newArrayListWithCapacity(length);
for (int i = 0; i < length; i++) {
Object val = Array.get(value, i);
valueList.add(getMBeanAttributeValue(val));
}
return valueList;
}
private Set getObjectNames(String objectName) throws Exception {
if (objectName.contains("*")) {
return lazyPlatformMBeanServer.queryNames(null,
new PatternObjectNameQueryExp(objectName));
} else {
return ImmutableSet.of(ObjectName.getInstance(objectName));
}
}
private Set getAttributeNames(Set objectNames) {
Set attributeNames = Sets.newHashSet();
for (ObjectName objectName : objectNames) {
try {
MBeanInfo mbeanInfo = lazyPlatformMBeanServer.getMBeanInfo(objectName);
attributeNames.addAll(getAttributeNames(mbeanInfo, objectName));
} catch (Exception e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
}
}
return attributeNames;
}
private Set getAttributeNames(MBeanInfo mbeanInfo, ObjectName objectName) {
Set attributeNames = Sets.newHashSet();
for (MBeanAttributeInfo attribute : mbeanInfo.getAttributes()) {
if (attribute.isReadable()) {
try {
Object value =
lazyPlatformMBeanServer.getAttribute(objectName, attribute.getName());
addNumericAttributes(attribute, value, attributeNames);
} catch (Exception e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
}
}
}
return attributeNames;
}
private static void addNumericAttributes(MBeanAttributeInfo attribute, Object value,
Set attributeNames) {
String attributeType = attribute.getType();
if (numericAttributeTypes.contains(attributeType)) {
attributeNames.add(attribute.getName());
} else if (attributeType.equals("java.lang.String") && value instanceof String) {
try {
Double.parseDouble((String) value);
attributeNames.add(attribute.getName());
} catch (NumberFormatException e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
}
} else if (attributeType.equals(CompositeData.class.getName())) {
Descriptor descriptor = attribute.getDescriptor();
Object descriptorFieldValue = descriptor.getFieldValue("openType");
if (descriptorFieldValue instanceof CompositeType) {
CompositeType compositeType = (CompositeType) descriptorFieldValue;
attributeNames
.addAll(getCompositeTypeAttributeNames(attribute, value, compositeType));
}
}
}
private static List getCompositeTypeAttributeNames(MBeanAttributeInfo attribute,
Object compositeData, CompositeType compositeType) {
List attributeNames = Lists.newArrayList();
for (String itemName : compositeType.keySet()) {
OpenType> itemType = compositeType.getType(itemName);
if (itemType == null) {
continue;
}
String className = itemType.getClassName();
Class> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
logger.warn(e.getMessage(), e);
continue;
}
if (Number.class.isAssignableFrom(clazz)) {
attributeNames.add(attribute.getName() + '/' + itemName);
} else if (clazz == String.class && compositeData instanceof CompositeData) {
Object val = ((CompositeData) compositeData).get(itemName);
if (val instanceof String) {
try {
Double.parseDouble((String) val);
attributeNames.add(attribute.getName() + '/' + itemName);
} catch (NumberFormatException e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
}
}
}
}
return attributeNames;
}
private static @Nullable String getHeapDumpPathFromCommandLine() {
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
for (String arg : runtimeMXBean.getInputArguments()) {
if (arg.startsWith("-XX:HeapDumpPath=")) {
return arg.substring("-XX:HeapDumpPath=".length());
}
}
return null;
}
private static Availability getThreadCpuTimeAvailability() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (!threadMXBean.isThreadCpuTimeSupported()) {
return ImmutableAvailability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadCpuTimeSupported() returned false");
}
if (!threadMXBean.isThreadCpuTimeEnabled()) {
return ImmutableAvailability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadCpuTimeEnabled() returned false");
}
return ImmutableAvailability.of(true, "");
}
private static Availability getThreadContentionAvailability() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (!threadMXBean.isThreadContentionMonitoringSupported()) {
return ImmutableAvailability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadContentionMonitoringSupported() returned false");
}
if (!threadMXBean.isThreadContentionMonitoringEnabled()) {
return ImmutableAvailability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadContentionMonitoringEnabled() returned false");
}
return ImmutableAvailability.of(true, "");
}
@VisibleForTesting
static String parseProcessId(String runtimeName) {
int index = runtimeName.indexOf('@');
if (index > 0) {
String pid = runtimeName.substring(0, index);
try {
Long.parseLong(pid);
return pid;
} catch (NumberFormatException e) {
logger.debug(e.getMessage(), e);
return "";
}
} else {
return "";
}
}
@SuppressWarnings("serial")
private static class ObjectNameQueryExp implements QueryExp {
private final String textUpper;
private ObjectNameQueryExp(String text) {
this.textUpper = text.toUpperCase(Locale.ENGLISH);
}
@Override
public boolean apply(ObjectName name) {
return name.toString().toUpperCase(Locale.ENGLISH).contains(textUpper);
}
@Override
public void setMBeanServer(MBeanServer s) {}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy