package org.radargun.stages.monitor;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import org.radargun.DistStageAck;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.stages.AbstractDistStage;
import org.radargun.utils.TimeConverter;
import org.radargun.utils.TimeService;
/**
*
* Periodically check for all thread stack traces and print them out.
*
* @author Radim Vansa <rvansa@redhat.com>
*/
@Stage(doc = "Debug usage only. Periodically check for all thread stack traces and print them out.")
public class StackTraceWatchdogStage extends AbstractDistStage {
@Property(converter = TimeConverter.class, doc = "The delay between consecutive checks. Default is 10 seconds.")
private long period = 10000;
@Property(doc = "If set, only those threads which have this mask in the name will be checked. Default is not set.")
private String mask = null;
@Property(doc = "By default the check will print out only those threads which appear to be stuck. If this is set " +
"to false all threads will be printed out. Default is true.")
private boolean onlyStuck = true;
@Property(doc = "Threads with stack lower or equal to this value are never printed (because usually such threads " +
"are parked in thread pools). Default is 10.")
private int shortStack = 10;
@Property(doc = "If set to true the watchdog will not use standard logging for output but will push the output "
+ "to queue consumed (logged) by another thread. Default is false.")
private boolean asyncLogging;
private static final String WATCHDOG = "__watchdog__";
private static final String WATCHDOG_LOGGING = "__watchdog_logging__";
private static WatchDogThread watchDogThread;
private static LoggingThread loggingThread;
private Queue<String> messageQueue = new LinkedList<String>();
private DateFormat formatter = new SimpleDateFormat("HH:mm:ss,SSS");
public StackTraceWatchdogStage() {
// nada
}
public DistStageAck executeOnSlave() {
if (!log.isTraceEnabled()) {
log.warn("Trace is not enabled, nothing to do.");
return successfulResponse();
}
synchronized (StackTraceWatchdogStage.class) {
if (watchDogThread != null) {
log.warn("Watchdog already running");
} else {
watchDogThread = new WatchDogThread();
watchDogThread.setName(WATCHDOG);
watchDogThread.setDaemon(true);
watchDogThread.start();
log.trace("Started watchdog");
}
if (asyncLogging) {
if (loggingThread != null) {
log.warn("Logging thread already running");
} else {
loggingThread = new LoggingThread();
loggingThread.setName(WATCHDOG_LOGGING);
loggingThread.setDaemon(true);
loggingThread.start();
log.trace("Started logging thread");
}
}
}
return successfulResponse();
}
private class WatchDogThread extends Thread {
private Map<Thread, StackTraceElement[]> lastStacks;
@Override
public void run() {
while (true) {
log("Running check");
long pre = TimeService.nanoTime();
Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
long post = TimeService.nanoTime();
log("Thread.getAllStackTraces() took " + (post - pre) + " nanoseconds");
for (Thread t : stacks.keySet()) {
String name = t.getName();
if (!name.equals(WATCHDOG) && (mask == null || name.contains(mask))) {
StackTraceElement[] stack = stacks.get(t);
if (stack.length < shortStack) {
continue;
}
boolean stuck = isStuck(t, stack);
if (!onlyStuck || stuck) {
traceStack(t, stack, stuck);
}
}
}
lastStacks = stacks;
try {
Thread.sleep(period);
} catch (InterruptedException e) {
log.error("Thread has been interrupted", e);
Thread.currentThread().interrupt();
}
}
}
private boolean isStuck(Thread t, StackTraceElement[] stack) {
if (lastStacks == null) return false;
StackTraceElement[] lastStack = lastStacks.get(t);
if (lastStack == null) return false;
return Arrays.deepEquals(lastStack, stack);
}
}
private class LoggingThread extends Thread {
@Override
public void run() {
while (true) {
String message;
synchronized (messageQueue) {
if (messageQueue.isEmpty()) {
try {
messageQueue.wait();
} catch (InterruptedException e) {
log.error("Thread has been interrupted", e);
Thread.currentThread().interrupt();
}
continue;
}
message = messageQueue.poll();
}
log.trace(message);
}
}
}
private void traceStack(Thread t, StackTraceElement[] stack, boolean stuck) {
StringBuilder sb = new StringBuilder();
sb.append("Stack for thread ");
sb.append(t.getName());
if (stuck) sb.append("(possibly stuck)");
sb.append(":\n");
for (StackTraceElement ste : stack) {
sb.append(ste.toString());
sb.append('\n');
}
log(sb.toString());
}
private void log(String message) {
if (asyncLogging) {
synchronized (messageQueue) {
messageQueue.add(formatter.format(new Date()) + ": " + message);
messageQueue.notify();
}
} else {
log.trace(message);
}
}
}