org.glowroot.local.ui.JvmJsonService Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2013-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.local.ui;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import org.glowroot.shaded.fasterxml.jackson.core.JsonGenerator;
import org.glowroot.shaded.fasterxml.jackson.databind.ObjectMapper;
import org.glowroot.shaded.google.common.base.Joiner;
import org.glowroot.shaded.google.common.base.MoreObjects;
import org.glowroot.shaded.google.common.base.Splitter;
import org.glowroot.shaded.google.common.base.StandardSystemProperty;
import org.glowroot.shaded.google.common.base.Strings;
import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.collect.Lists;
import org.glowroot.shaded.google.common.collect.Maps;
import org.glowroot.shaded.google.common.collect.Ordering;
import org.glowroot.shaded.google.common.io.CharStreams;
import org.glowroot.shaded.google.common.primitives.Ints;
import org.glowroot.shaded.google.common.primitives.Longs;
import org.immutables.value.Value;
import org.glowroot.shaded.slf4j.Logger;
import org.glowroot.shaded.slf4j.LoggerFactory;
import org.glowroot.collector.GaugePoint;
import org.glowroot.collector.PatternObjectNameQueryExp;
import org.glowroot.common.Clock;
import org.glowroot.common.ObjectMappers;
import org.glowroot.common.Styles;
import org.glowroot.config.ConfigService;
import org.glowroot.config.GaugeConfig;
import org.glowroot.config.GaugeConfigBase;
import org.glowroot.config.MBeanAttribute;
import org.glowroot.jvm.Availability;
import org.glowroot.jvm.HeapDumps;
import org.glowroot.jvm.LazyPlatformMBeanServer;
import org.glowroot.jvm.OptionalService;
import org.glowroot.jvm.ThreadAllocatedBytes;
import org.glowroot.local.store.GaugePointDao;
import org.glowroot.markers.UsedByJsonBinding;
import org.glowroot.transaction.TransactionCollector;
import org.glowroot.transaction.TransactionRegistry;
import org.glowroot.transaction.model.Transaction;
import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;
@JsonService
class JvmJsonService {
private static final Logger logger = LoggerFactory.getLogger(JvmJsonService.class);
private static final ObjectMapper mapper = ObjectMappers.create();
private static final Ordering unmatchedThreadInfoOrdering =
new Ordering() {
@Override
public int compare(@Nullable ThreadInfo left, @Nullable ThreadInfo right) {
checkNotNull(left);
checkNotNull(right);
if (left.getThreadId() == Thread.currentThread().getId()) {
return 1;
} else if (right.getThreadId() == Thread.currentThread().getId()) {
return -1;
}
int result =
Ints.compare(right.getStackTrace().length, left.getStackTrace().length);
if (result == 0) {
return left.getThreadName().compareToIgnoreCase(right.getThreadName());
}
return result;
}
};
private final LazyPlatformMBeanServer lazyPlatformMBeanServer;
private final GaugePointDao gaugePointDao;
private final ConfigService configService;
private final TransactionRegistry transactionRegistry;
private final TransactionCollector transactionCollector;
private final OptionalService threadAllocatedBytes;
private final OptionalService heapDumps;
private final @Nullable String processId;
private final Clock clock;
private final long gaugeCollectionIntervalMillis;
JvmJsonService(LazyPlatformMBeanServer lazyPlatformMBeanServer, GaugePointDao gaugePointDao,
ConfigService configService, TransactionRegistry transactionRegistry,
TransactionCollector transactionCollector,
OptionalService threadAllocatedBytes,
OptionalService heapDumps, @Nullable String processId,
long gaugeCollectionIntervalMillis, Clock clock) {
this.lazyPlatformMBeanServer = lazyPlatformMBeanServer;
this.gaugePointDao = gaugePointDao;
this.configService = configService;
this.transactionRegistry = transactionRegistry;
this.transactionCollector = transactionCollector;
this.threadAllocatedBytes = threadAllocatedBytes;
this.heapDumps = heapDumps;
this.processId = processId;
this.gaugeCollectionIntervalMillis = gaugeCollectionIntervalMillis;
this.clock = clock;
}
@GET("/backend/jvm/gauge-points")
String getGaugePoints(String queryString) throws Exception {
GaugePointRequest request = QueryStrings.decode(queryString, GaugePointRequest.class);
int rollupLevel = gaugePointDao.getRollupLevelForView(request.from(), request.to());
long intervalMillis;
if (rollupLevel == 0) {
intervalMillis = gaugeCollectionIntervalMillis;
} else {
intervalMillis = configService.getRollupConfigs().get(rollupLevel - 1).intervalMillis();
}
double gapMillis = intervalMillis * 1.5;
// 2x in order to deal with displaying deltas
long revisedFrom = request.from() - 2 * intervalMillis;
long revisedTo = request.to() + intervalMillis;
long liveCaptureTime = clock.currentTimeMillis();
List dataSeriesList = Lists.newArrayList();
for (String gaugeName : request.gaugeNames()) {
List gaugePoints =
getGaugePoints(revisedFrom, revisedTo, gaugeName, rollupLevel, liveCaptureTime);
if (!gaugePoints.isEmpty()) {
dataSeriesList.add(convertToDataSeriesWithGaps(gaugeName, gaugePoints, gapMillis));
}
}
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeObjectField("dataSeries", dataSeriesList);
jg.writeEndObject();
jg.close();
return sb.toString();
}
@GET("/backend/jvm/all-gauges")
String getAllGaugeNames() throws Exception {
List gauges = Lists.newArrayList();
for (GaugeConfig gaugeConfig : configService.getGaugeConfigs()) {
List mbeanObjectNames =
getMatchingMBeanObjectNames(gaugeConfig.mbeanObjectName());
for (String mbeanObjectName : mbeanObjectNames) {
for (MBeanAttribute mbeanAttribute : gaugeConfig.mbeanAttributes()) {
gauges.add(Gauge.of(mbeanObjectName + "," + mbeanAttribute.name(),
mbeanAttribute.everIncreasing(),
GaugeConfigBase.display(mbeanObjectName) + '/'
+ mbeanAttribute.name()));
}
}
}
ImmutableList sortedGauges = Gauge.ordering.immutableSortedCopy(gauges);
return mapper.writeValueAsString(sortedGauges);
}
@GET("/backend/jvm/mbean-tree")
String getMBeanTree(String queryString) throws Exception {
MBeanTreeRequest request = QueryStrings.decode(queryString, MBeanTreeRequest.class);
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 mapper.writeValueAsString(sortedRootNodes);
}
@GET("/backend/jvm/mbean-attribute-map")
String getMBeanAttributeMap(String queryString) throws Exception {
MBeanAttributeMapRequest request =
QueryStrings.decode(queryString, MBeanAttributeMapRequest.class);
ObjectName objectName = ObjectName.getInstance(request.objectName());
Map attributeMap = getMBeanSortedAttributeMap(objectName);
return mapper.writeValueAsString(attributeMap);
}
@POST("/backend/jvm/perform-gc")
void performGC() throws IOException {
// 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();
}
@GET("/backend/jvm/thread-dump")
String getThreadDump() throws IOException {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
Map transactionsBefore = Maps.newHashMap();
for (Transaction transaction : transactionRegistry.getTransactions()) {
transactionsBefore.put(transaction.getThreadId(), transaction);
}
ThreadInfo[] threadInfos =
threadBean.getThreadInfo(threadBean.getAllThreadIds(), Integer.MAX_VALUE);
final Map matchedTransactions = Maps.newHashMap();
for (Transaction transaction : transactionRegistry.getTransactions()) {
if (transactionsBefore.get(transaction.getThreadId()) == transaction) {
matchedTransactions.put(transaction.getThreadId(), transaction);
}
}
long currentThreadId = Thread.currentThread().getId();
ThreadInfo currentThreadInfo = null;
List matchedThreadInfos = Lists.newArrayList();
List unmatchedThreadInfos = Lists.newArrayList();
for (ThreadInfo threadInfo : threadInfos) {
long threadId = threadInfo.getThreadId();
if (threadId == currentThreadId) {
currentThreadInfo = threadInfo;
} else if (matchedTransactions.containsKey(threadId)) {
matchedThreadInfos.add(threadInfo);
} else {
unmatchedThreadInfos.add(threadInfo);
}
}
// sort descending by duration
Collections.sort(matchedThreadInfos, new Comparator() {
@Override
public int compare(ThreadInfo left, ThreadInfo right) {
Transaction leftTransaction = matchedTransactions.get(left.getThreadId());
Transaction rightTransaction = matchedTransactions.get(right.getThreadId());
// left and right are from matchedThreadInfos so have corresponding transactions
checkNotNull(leftTransaction);
checkNotNull(rightTransaction);
return Longs.compare(rightTransaction.getDuration(), leftTransaction.getDuration());
}
});
// sort descending by stack trace length
Collections.sort(unmatchedThreadInfos, unmatchedThreadInfoOrdering);
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeArrayFieldStart("matchedThreads");
for (ThreadInfo threadInfo : matchedThreadInfos) {
Transaction matchedTransaction = matchedTransactions.get(threadInfo.getThreadId());
writeThreadInfo(threadInfo, matchedTransaction, jg);
}
jg.writeEndArray();
jg.writeArrayFieldStart("unmatchedThreads");
for (ThreadInfo threadInfo : unmatchedThreadInfos) {
writeThreadInfo(threadInfo, null, jg);
}
jg.writeEndArray();
if (currentThreadInfo != null) {
jg.writeFieldName("currentThread");
Transaction matchedTransaction =
matchedTransactions.get(currentThreadInfo.getThreadId());
writeThreadInfo(currentThreadInfo, matchedTransaction, jg);
}
jg.writeEndObject();
jg.close();
return sb.toString();
}
@GET("/backend/jvm/heap-dump-defaults")
String getHeapDumpDefaults() throws Exception {
String heapDumpPath = getHeapDumpPathFromCommandLine();
if (Strings.isNullOrEmpty(heapDumpPath)) {
String javaTempDir =
MoreObjects.firstNonNull(StandardSystemProperty.JAVA_IO_TMPDIR.value(), ".");
heapDumpPath = new File(javaTempDir).getAbsolutePath();
}
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeStringField("directory", heapDumpPath);
jg.writeEndObject();
jg.close();
return sb.toString();
}
@POST("/backend/jvm/check-disk-space")
String checkDiskSpace(String content) throws IOException {
RequestWithDirectory request = mapper.readValue(content, RequestWithDirectory.class);
File dir = new File(request.directory());
if (!dir.exists()) {
return "{\"error\": \"Directory doesn't exist\"}";
}
if (!dir.isDirectory()) {
return "{\"error\": \"Path is not a directory\"}";
}
long diskSpace = new File(request.directory()).getFreeSpace();
return Long.toString(diskSpace);
}
@POST("/backend/jvm/dump-heap")
String dumpHeap(String content) throws Exception {
// this command is filtered out of the UI when service is null
HeapDumps service = checkNotNull(heapDumps.getService(),
"Heap dump service is not available: %s", heapDumps.getAvailability().getReason());
RequestWithDirectory request = mapper.readValue(content, RequestWithDirectory.class);
File dir = new File(request.directory());
if (!dir.exists()) {
return "{\"error\": \"Directory doesn't exist\"}";
}
if (!dir.isDirectory()) {
return "{\"error\": \"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");
}
service.dumpHeap(file.getAbsolutePath());
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeStringField("filename", file.getAbsolutePath());
jg.writeNumberField("size", file.length());
jg.writeEndObject();
jg.close();
return sb.toString();
}
@GET("/backend/jvm/process-info")
String getProcess() throws Exception {
String command = System.getProperty("sun.java.command");
String mainClass = null;
List arguments = ImmutableList.of();
if (command != null) {
int index = command.indexOf(' ');
if (index > 0) {
mainClass = command.substring(0, index);
arguments =
Lists.newArrayList(Splitter.on(' ').split(command.substring(index + 1)));
}
}
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
String jvm = StandardSystemProperty.JAVA_VM_NAME.value() + " ("
+ StandardSystemProperty.JAVA_VM_VERSION.value() + ", "
+ System.getProperty("java.vm.info") + ")";
String java = "version " + StandardSystemProperty.JAVA_VERSION.value() + ", vendor "
+ StandardSystemProperty.JAVA_VM_VENDOR.value();
String javaHome = StandardSystemProperty.JAVA_HOME.value();
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeNumberField("startTime", runtimeMXBean.getStartTime());
jg.writeNumberField("uptime", runtimeMXBean.getUptime());
jg.writeStringField("pid", MoreObjects.firstNonNull(processId, ""));
jg.writeStringField("mainClass", mainClass);
jg.writeFieldName("mainClassArguments");
mapper.writeValue(jg, arguments);
jg.writeStringField("jvm", jvm);
jg.writeStringField("java", java);
jg.writeStringField("javaHome", javaHome);
jg.writeFieldName("jvmArguments");
mapper.writeValue(jg, runtimeMXBean.getInputArguments());
jg.writeEndObject();
jg.close();
return sb.toString();
}
@GET("/backend/jvm/system-properties")
String getSystemProperties() throws IOException {
Properties properties = System.getProperties();
// can't use Maps.newTreeMap() because of OpenJDK6 type inference bug
// see https://code.google.com/p/guava-libraries/issues/detail?id=635
Map sortedProperties =
new TreeMap(String.CASE_INSENSITIVE_ORDER);
for (Enumeration> e = properties.propertyNames(); e.hasMoreElements();) {
Object obj = e.nextElement();
if (obj instanceof String) {
String propertyName = (String) obj;
String propertyValue = properties.getProperty(propertyName);
if (propertyValue != null) {
sortedProperties.put(propertyName, propertyValue);
}
}
}
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartArray();
for (Entry entry : sortedProperties.entrySet()) {
jg.writeStartObject();
jg.writeStringField("name", entry.getKey());
jg.writeStringField("value", entry.getValue());
jg.writeEndObject();
}
jg.writeEndArray();
jg.close();
return sb.toString();
}
@GET("/backend/jvm/capabilities")
String getCapabilities() throws IOException {
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeFieldName("threadCpuTime");
mapper.writeValue(jg, getThreadCpuTimeAvailability());
jg.writeFieldName("threadContentionTime");
mapper.writeValue(jg, getThreadContentionAvailability());
jg.writeFieldName("threadAllocatedBytes");
mapper.writeValue(jg, threadAllocatedBytes.getAvailability());
jg.writeFieldName("heapDump");
mapper.writeValue(jg, heapDumps.getAvailability());
jg.writeEndObject();
jg.close();
return sb.toString();
}
private List getGaugePoints(long from, long to, String gaugeName, int rollupLevel,
long liveCaptureTime) throws SQLException {
ImmutableList gaugePoints =
gaugePointDao.readGaugePoints(gaugeName, from, to, rollupLevel);
if (rollupLevel == 0) {
return gaugePoints;
}
long nonRolledUpFrom = from;
if (!gaugePoints.isEmpty()) {
long lastRolledUpTime = gaugePoints.get(gaugePoints.size() - 1).captureTime();
nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1);
}
List allGaugePoints = Lists.newArrayList(gaugePoints);
allGaugePoints.addAll(gaugePointDao.readManuallyRolledUpGaugePoints(nonRolledUpFrom, to,
gaugeName, rollupLevel, liveCaptureTime));
return allGaugePoints;
}
private List getMatchingMBeanObjectNames(String mbeanObjectName)
throws InterruptedException {
if (!mbeanObjectName.contains("*")) {
return ImmutableList.of(mbeanObjectName);
}
Set objectNames = lazyPlatformMBeanServer.queryNames(null,
new PatternObjectNameQueryExp(mbeanObjectName));
List mbeanObjectNames = Lists.newArrayList();
for (ObjectName objectName : objectNames) {
mbeanObjectNames
.add(objectName.getDomain() + ":" + objectName.getKeyPropertyListString());
}
return mbeanObjectNames;
}
private void writeThreadInfo(ThreadInfo threadInfo, @Nullable Transaction matchedTransaction,
JsonGenerator jg) throws IOException {
jg.writeStartObject();
if (matchedTransaction != null) {
jg.writeStringField("transactionType", matchedTransaction.getTransactionType());
jg.writeStringField("transactionName", matchedTransaction.getTransactionName());
jg.writeNumberField("transactionDuration", matchedTransaction.getDuration());
if (transactionCollector.shouldStoreSlow(matchedTransaction)) {
jg.writeStringField("traceId", matchedTransaction.getId());
}
}
jg.writeStringField("name", threadInfo.getThreadName());
jg.writeStringField("state", threadInfo.getThreadState().name());
jg.writeStringField("lockName", threadInfo.getLockName());
jg.writeArrayFieldStart("stackTrace");
for (StackTraceElement stackTraceElement : threadInfo.getStackTrace()) {
jg.writeString(stackTraceElement.toString());
}
jg.writeEndArray();
jg.writeEndObject();
}
private static DataSeries convertToDataSeriesWithGaps(String dataSeriesName,
List gaugePoints, double gapMillis) {
DataSeries dataSeries = new DataSeries(dataSeriesName);
GaugePoint lastGaugePoint = null;
for (GaugePoint gaugePoint : gaugePoints) {
if (lastGaugePoint != null
&& gaugePoint.captureTime() - lastGaugePoint.captureTime() > gapMillis) {
dataSeries.addNull();
}
dataSeries.add(gaugePoint.captureTime(), gaugePoint.value());
lastGaugePoint = gaugePoint;
}
return dataSeries;
}
private static Availability getThreadCpuTimeAvailability() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (!threadMXBean.isThreadCpuTimeSupported()) {
return Availability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadCpuTimeSupported() returned false");
}
if (!threadMXBean.isThreadCpuTimeEnabled()) {
return Availability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadCpuTimeEnabled() returned false");
}
return Availability.of(true, "");
}
private static Availability getThreadContentionAvailability() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (!threadMXBean.isThreadContentionMonitoringSupported()) {
return Availability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadContentionMonitoringSupported() returned false");
}
if (!threadMXBean.isThreadContentionMonitoringEnabled()) {
return Availability.of(false, "java.lang.management.ThreadMXBean"
+ ".isThreadContentionMonitoringEnabled() returned false");
}
return Availability.of(true, "");
}
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 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 interface MBeanTreeNode {
static final Ordering ordering = new Ordering() {
@Override
public int compare(@Nullable MBeanTreeNode left, @Nullable MBeanTreeNode right) {
checkNotNull(left);
checkNotNull(right);
return left.getNodeName().compareToIgnoreCase(right.getNodeName());
}
};
String getNodeName();
}
@Value.Immutable
abstract static class GaugePointRequestBase {
abstract long from();
abstract long to();
abstract ImmutableList gaugeNames();
}
@Value.Immutable
@Styles.AllParameters
abstract static class GaugeBase {
static final Ordering ordering = new Ordering() {
@Override
public int compare(@Nullable Gauge left, @Nullable Gauge right) {
checkNotNull(left);
checkNotNull(right);
return left.display().compareToIgnoreCase(right.display());
}
};
public abstract String name();
public abstract boolean everIncreasing();
public abstract String display();
}
@UsedByJsonBinding
static class MBeanTreeInnerNode implements MBeanTreeNode {
private final String name;
// not using Map here since its possible for multiple leafs with same name
// e.g. d:type=Foo,name=Bar and d:type=Foo,nonsense=Bar
// both translate to a leaf named Bar under d/Foo
private final List childNodes = Lists.newArrayList();
private final Map innerNodes = Maps.newHashMap();
private MBeanTreeInnerNode(String name) {
this.name = name;
}
@Override
public String getNodeName() {
return name;
}
public List getChildNodes() {
return MBeanTreeNode.ordering.sortedCopy(childNodes);
}
private MBeanTreeInnerNode getOrCreateNode(String name) {
MBeanTreeInnerNode innerNode = innerNodes.get(name);
if (innerNode == null) {
innerNode = new MBeanTreeInnerNode(name);
innerNodes.put(name, innerNode);
childNodes.add(innerNode);
}
return innerNode;
}
private void addLeafNode(MBeanTreeLeafNode leafNode) {
childNodes.add(leafNode);
}
}
@UsedByJsonBinding
static class MBeanTreeLeafNode implements MBeanTreeNode {
// nodeName may not be unique
private final String nodeName;
private final String objectName;
private final boolean expanded;
private final @Nullable Map attributeMap;
private MBeanTreeLeafNode(String nodeName, String objectName, boolean expanded,
@Nullable Map attributeMap) {
this.nodeName = nodeName;
this.objectName = objectName;
this.expanded = expanded;
this.attributeMap = attributeMap;
}
@Override
public String getNodeName() {
return nodeName;
}
public String getObjectName() {
return objectName;
}
public boolean isExpanded() {
return expanded;
}
public @Nullable Map getAttributeMap() {
return attributeMap;
}
}
@Value.Immutable
abstract static class MBeanTreeRequestBase {
abstract public List expanded();
}
@Value.Immutable
abstract static class MBeanAttributeMapRequestBase {
abstract String objectName();
}
@Value.Immutable
abstract static class RequestWithDirectoryBase {
abstract String directory();
}
@Value.Immutable
abstract static class ThreadDumpItemBase {
abstract String objectName();
}
}