All Downloads are FREE. Search and download functionalities are using the official Maven repository.

blasd.apex.core.thread.ApexThreadDump Maven / Gradle / Ivy

The newest version!
/**
 * The MIT License
 * Copyright (c) 2014 Benoit Lacelle
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package blasd.apex.core.thread;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.joda.time.LocalDateTime;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

/**
 * A convenience class for getting a thread dump. See com.codahale.metrics.jvm.ThreadDump
 * 
 * @author Benoit Lacelle
 */
@ManagedResource
public class ApexThreadDump implements IApexThreadDumper {
	private final ThreadMXBean threadMXBean;

	/**
	 * 
	 * @param threadMXBean
	 *            may be ManagementFactory.getThreadMXBean()
	 */
	public ApexThreadDump(ThreadMXBean threadMXBean) {
		this.threadMXBean = threadMXBean;
	}

	/**
	 * Dumps all of the threads' current information to an output stream.
	 * 
	 * @param out
	 *            an output stream
	 * @throws IOException
	 */
	public void dump(Charset charset, OutputStream out, boolean withMonitorsAndSynchronizers) throws IOException {
		dumpSkeleton(charset, out, withMonitorsAndSynchronizers, (writer, stream) -> {
			stream.forEach(t -> {
				try {
					appendToWritable(writer, t);
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			});
		});
	}

	protected ThreadInfo[] dumpAllThreads(boolean withMonitors, boolean withSynchronizers) {
		return threadMXBean.dumpAllThreads(withMonitors, withSynchronizers);
	}

	protected void dumpSkeleton(Charset charset,
			OutputStream out,
			boolean withMonitorsAndSynchronizers,
			BiConsumer> threadInfoConsumer) throws IOException {
		final PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, charset));

		printHeader(writer);

		final ThreadInfo[] threads = dumpAllThreads(withMonitorsAndSynchronizers, withMonitorsAndSynchronizers);

		// Group the thread with same state together
		Stream stream =
				Arrays.stream(threads).sorted(Comparator.comparing(ti -> ti.getThreadState().ordinal()));

		threadInfoConsumer.accept(writer, stream);

		println(writer);
		writer.flush();
	}

	protected void printHeader(PrintWriter writer) throws IOException {
		// Header in JVisualVM
		// 2016-07-19 12:50:12 pid@hostname
		RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
		println(writer, new LocalDateTime() + " " + runtimeMXBean.getName());
		// Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.66-b17 mixed mode):
		println(writer,
				"Full thread dump " + runtimeMXBean.getVmName()
						+ " ("
						+ runtimeMXBean.getVmVersion()
						+ " by "
						+ runtimeMXBean.getVmVendor()
						+ ")");
	}

	protected void appendToWritable(Appendable writer, ThreadInfo t) throws IOException {
		appendThreadHeader(writer, t);
		appendThreadStack(writer, t);
		appendThreadFooter(writer, t);
	}

	/**
	 * Used in smartThreadDump to prevent printing the example footer if it is empty
	 * 
	 * @param t
	 * @return true if the footer is not empty
	 */
	protected boolean hasFooter(ThreadInfo t) {
		return t.getLockedSynchronizers().length >= 1;
	}

	protected void appendThreadFooter(Appendable writer, ThreadInfo t)
			throws UnsupportedEncodingException, IOException {
		final LockInfo[] locks = t.getLockedSynchronizers();
		if (locks.length > 0) {
			printf(writer, "    Locked synchronizers: count = %d%n", locks.length);
			for (LockInfo l : locks) {
				printf(writer, "      - %s%n", l);
			}
			println(writer);
		}
	}

	protected void appendThreadStack(Appendable writer, ThreadInfo t) throws UnsupportedEncodingException, IOException {
		final StackTraceElement[] elements = t.getStackTrace();
		final MonitorInfo[] monitors = t.getLockedMonitors();

		for (int i = 0; i < elements.length; i++) {
			final StackTraceElement element = elements[i];
			printf(writer, "    at %s%n", element);
			for (int j = 1; j < monitors.length; j++) {
				final MonitorInfo monitor = monitors[j];
				if (monitor.getLockedStackDepth() == i) {
					printf(writer, "      - locked %s%n", monitor);
				}
			}
		}
		println(writer);
	}

	protected void appendThreadHeader(Appendable writer, ThreadInfo t)
			throws UnsupportedEncodingException, IOException {
		printf(writer, "%s id=%d state=%s", t.getThreadName(), t.getThreadId(), t.getThreadState());
		final LockInfo lock = t.getLockInfo();
		if (lock != null && t.getThreadState() != Thread.State.BLOCKED) {
			printf(writer, "%n    - waiting on <0x%08x> (a %s)", lock.getIdentityHashCode(), lock.getClassName());
			printf(writer, "%n    - locked <0x%08x> (a %s)", lock.getIdentityHashCode(), lock.getClassName());
		} else if (lock != null && t.getThreadState() == Thread.State.BLOCKED) {
			printf(writer, "%n    - waiting to lock <0x%08x> (a %s)", lock.getIdentityHashCode(), lock.getClassName());
		}

		if (t.isSuspended()) {
			writer.append(" (suspended)");
		}

		if (t.isInNative()) {
			writer.append(" (running in native)");
		}

		println(writer);
		if (t.getLockOwnerName() != null) {
			printf(writer, "     owned by %s id=%d%n", t.getLockOwnerName(), t.getLockOwnerId());
		}
	}

	protected void printf(Appendable writer, String format, Object... parameters)
			throws UnsupportedEncodingException, IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Charset charset = Charset.defaultCharset();
		final PrintWriter printer = new PrintWriter(new OutputStreamWriter(baos, charset));

		printer.printf(format, parameters);
		printer.flush();

		writer.append(baos.toString(charset.name()));
	}

	protected void println(Appendable writer) throws IOException {
		// @see java.io.PrintWriter.PrintWriter(Writer, boolean)
		writer.append(System.getProperty("line.separator"));
	}

	protected void println(Appendable writer, String string) throws IOException {
		writer.append(string);

		println(writer);
	}

	@ManagedOperation
	@Override
	public String getThreadDumpAsString(boolean withMonitorsAndSynchronizers) {
		ByteArrayOutputStream os = new ByteArrayOutputStream();

		// Do not query monitors and synchronizers are they are not the cause of
		// a FullGC: we prevent not to freeze the JVM collecting these monitors
		try {
			dump(Charset.defaultCharset(), os, withMonitorsAndSynchronizers);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		return os.toString();
	}

	@ManagedOperation
	@Override
	public String getSmartThreadDumpAsString(boolean withMonitorsAndSynchronizers) {
		ByteArrayOutputStream os = new ByteArrayOutputStream();

		Charset charset = Charset.defaultCharset();

		// Do not query monitors and synchronizers are they are not the cause of
		// a FullGC: we prevent not to freeze the JVM collecting these monitors
		try {
			dumpSmart(charset, os, withMonitorsAndSynchronizers);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		return os.toString();
	}

	// qfs-common-worker-76 id=905 state=WAITING
	// - waiting on <0x760d8059> (a jsr166e.ForkJoinPool)
	// - locked <0x760d8059> (a jsr166e.ForkJoinPool)
	// at sun.misc.Unsafe.park(Native Method)
	// at jsr166e.ForkJoinPool.awaitWork(ForkJoinPool.java:1758)
	// at jsr166e.ForkJoinPool.scan(ForkJoinPool.java:1696)
	// at jsr166e.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
	// at jsr166e.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:111)

	// http-nio-184.10.18.189-9080-exec-5 id=1019 state=WAITING
	// - waiting on <0x4f603cd5> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	// - locked <0x4f603cd5> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	// at sun.misc.Unsafe.park(Native Method)
	// at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	// at
	// java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	// at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	// at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
	// at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
	// at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	// at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	// at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	// at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	// at java.lang.Thread.run(Thread.java:745)

	public void dumpSmart(Charset charset, OutputStream out, boolean withMonitorsAndSynchronizers) throws IOException {
		dumpSkeleton(charset, out, withMonitorsAndSynchronizers, (writer, stream) -> {
			// Group the thread with same state together
			stream.collect(Collectors.groupingBy(t -> {
				Writer localWriter = new StringWriter();
				try {
					appendThreadStack(localWriter, t);
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
				return localWriter.toString();
			})).forEach((stack, tis) -> {
				try {
					printThreadGroup(writer, tis);
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			});
		});
	}

	protected void printThreadGroup(PrintWriter writer, List tis)
			throws UnsupportedEncodingException, IOException {
		if (tis.size() == 1) {
			ThreadInfo t = tis.get(0);
			// Full stack
			appendThreadHeader(writer, t);

			appendThreadStack(writer, t);
			appendThreadFooter(writer, t);
		} else {
			ThreadInfo t = tis.get(0);

			writer.write("One header amongst ");
			println(writer, Integer.toString(tis.size()));
			appendThreadHeader(writer, t);
			println(writer, "---------------------");

			appendThreadStack(writer, t);

			// Write the footer example only if it is present. Else we would have an overhead wrapping an empty footer:
			// useless
			if (hasFooter(t)) {
				writer.write("One footer amongst ");
				println(writer, Integer.toString(tis.size()));
				appendThreadFooter(writer, t);
				println(writer, "---------------------");

				// Ensure the row after the footer is empty, and not rightaway the next thread
				println(writer, "\r\n");
			}
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy