/*
* Copyright Terracotta, Inc.
*
* 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.ehcache;
import com.sun.management.HotSpotDiagnosticMXBean;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Calendar;
import java.util.Date;
import javax.management.MBeanServer;
/**
* Provides methods to produce diagnostic output.
*/
@SuppressWarnings({ "UnusedDeclaration", "WeakerAccess" })
public final class Diagnostics {
private static final String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
private static final String HEAP_DUMP_FILENAME_TEMPLATE = "java_%1$04d_%2$tFT%2$tH%2$tM%2$tS.%2$tL.hprof";
private static final File WORKING_DIRECTORY = new File(System.getProperty("user.dir"));
/**
* Private niladic constructor to prevent instantiation.
*/
private Diagnostics() {
}
/**
* Writes a complete thread dump to {@code System.err}.
*/
public static void threadDump() {
threadDump(System.err);
}
/**
* Writes a complete thread dump to the designated {@code PrintStream}.
*
* @param out the {@code PrintStream} to which the thread dump is written
*/
public static void threadDump(final PrintStream out) {
if (out == null) {
throw new NullPointerException("out");
}
final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
final Calendar when = Calendar.getInstance();
final ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(
threadMXBean.isObjectMonitorUsageSupported(), threadMXBean.isSynchronizerUsageSupported());
out.format("%nFull thread dump %1$tF %1$tT.%1$tL %1$tz%n", when);
for (final ThreadInfo threadInfo : threadInfos) {
out.print(format(threadInfo));
}
}
/**
* Format a {@code ThreadInfo} instance <i>without</i> a stack depth limitation. This method reproduces the
* formatting performed in {@code java.lang.management.ThreadInfo.toString()} without the stack depth limit.
*
* @param threadInfo the {@code ThreadInfo} instance to foramt
*
* @return a {@code CharSequence} instance containing the formatted {@code ThreadInfo}
*/
private static CharSequence format(final ThreadInfo threadInfo) {
StringBuilder sb = new StringBuilder(4096);
Thread.State threadState = threadInfo.getThreadState();
sb.append('"')
.append(threadInfo.getThreadName())
.append('"')
.append(" Id=")
.append(threadInfo.getThreadId())
.append(' ')
.append(threadState);
if (threadInfo.getLockName() != null) {
sb.append(" on ").append(threadInfo.getLockName());
}
if (threadInfo.getLockOwnerName() != null) {
sb.append(" owned by ").append('"').append(threadInfo.getLockOwnerName()).append('"')
.append(" Id=").append(threadInfo.getLockOwnerId());
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
StackTraceElement[] stackTrace = threadInfo.getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
StackTraceElement element = stackTrace[i];
sb.append("\tat ").append(element);
sb.append('\n');
if (i == 0) {
if (threadInfo.getLockInfo() != null) {
switch (threadState) {
case BLOCKED:
sb.append("\t- blocked on ").append(threadInfo.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on ").append(threadInfo.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on ").append(threadInfo.getLockInfo());
sb.append('\n');
break;
default:
}
}
}
for (MonitorInfo monitorInfo : threadInfo.getLockedMonitors()) {
if (monitorInfo.getLockedStackDepth() == i) {
sb.append("\t- locked ").append(monitorInfo);
sb.append('\n');
}
}
}
LockInfo[] lockedSynchronizers = threadInfo.getLockedSynchronizers();
if (lockedSynchronizers.length > 0) {
sb.append("\n\tNumber of locked synchronizers = ").append(lockedSynchronizers.length);
sb.append('\n');
for (LockInfo lockedSynchronizer : lockedSynchronizers) {
sb.append("\t- ").append(lockedSynchronizer);
sb.append('\n');
}
}
sb.append('\n');
return sb;
}
/**
* Take a Java heap dump into a file whose name is produced from the template
* <code>{@value #HEAP_DUMP_FILENAME_TEMPLATE}</code> where {@code 1$} is the PID of
* the current process obtained from {@link #getPid()}.
*
* @param dumpLiveObjects if {@code true}, only "live" (reachable) objects are dumped;
* if {@code false}, all objects in the heap are dumped
*
* @return the name of the dump file; the file is written to the current directory (generally {@code user.dir})
*/
public static String dumpHeap(final boolean dumpLiveObjects) {
String dumpName;
final int pid = getPid();
final Date currentTime = new Date();
if (pid > 0) {
dumpName = String.format(HEAP_DUMP_FILENAME_TEMPLATE, pid, currentTime);
} else {
dumpName = String.format(HEAP_DUMP_FILENAME_TEMPLATE, 0, currentTime);
}
dumpName = new File(WORKING_DIRECTORY, dumpName).getAbsolutePath();
try {
dumpHeap(dumpLiveObjects, dumpName);
} catch (IOException e) {
System.err.printf("Unable to write heap dump to %s: %s%n", dumpName, e);
e.printStackTrace(System.err);
return null;
}
return dumpName;
}
/**
* Write a Java heap dump to the named file. If the dump file exists, this method will
* fail.
*
* @param dumpLiveObjects if {@code true}, only "live" (reachable) objects are dumped;
* if {@code false}, all objects in the heap are dumped
* @param dumpName the name of the file to which the heap dump is written; relative names
* are relative to the current directory ({@code user.dir}). If the value
* of {@code dumpName} does not end in {@code .hprof}, it is appended.
*
* @throws IOException if thrown while loading the HotSpot Diagnostic MXBean or writing the heap dump
*
* @see <a href="http://docs.oracle.com/javase/8/docs/jre/api/management/extension/com/sun/management/HotSpotDiagnosticMXBean.html">
* com.sun.management.HotSpotDiagnosticMXBean</a>
*/
public static void dumpHeap(final boolean dumpLiveObjects, String dumpName) throws IOException {
if (dumpName == null) {
throw new NullPointerException("dumpName");
}
if (!dumpName.endsWith(".hprof")) {
dumpName += ".hprof";
}
final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
final HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean =
ManagementFactory.newPlatformMXBeanProxy(server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME, HotSpotDiagnosticMXBean.class);
hotSpotDiagnosticMXBean.dumpHeap(dumpName, dumpLiveObjects);
}
/**
* Gets the PID of the current process. This method is dependent upon "common"
* operation of the {@code java.lang.management.RuntimeMXBean#getName()} method.
*
* @return the PID of the current process or {@code -1} if the PID can not be determined
*/
public static int getPid() {
// Expected to be of the form "<pid>@<hostname>"
final String jvmProcessName = ManagementFactory.getRuntimeMXBean().getName();
try {
return Integer.valueOf(jvmProcessName.substring(0, jvmProcessName.indexOf('@')));
} catch (NumberFormatException e) {
return -1;
} catch (IndexOutOfBoundsException e) {
return -1;
}
}
}