/*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @author Andrew Dinn
* @author Scott.Stark
*/
package org.jboss.byteman.sample.helper;
import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.helper.Helper;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* <p>
* Helper class used by ThreadHistoryMonitorHelper script to trace thread operations. This
* is essentially an extension of the ThreadMonitorHelper which uses maps to store the thread
* history rather than writing it out.
* <p>
* The helper also implements ThreadHistoryMonitorHelperMXBean to allow this class to be
* registered as an mbean @see #registerHelperMBean(String).
*
*/
public class ThreadHistoryMonitorHelper extends Helper
implements ThreadHistoryMonitorHelperMXBean
{
private static Map<ThreadMonitored, ThreadMonitored> monitoredThreads =
new ConcurrentHashMap<ThreadMonitored, ThreadMonitored>();
private static Queue<ThreadMonitorEvent> createList = new ConcurrentLinkedQueue<ThreadMonitorEvent>();
private static Queue<ThreadMonitorEvent> startList = new ConcurrentLinkedQueue<ThreadMonitorEvent>();
private static Queue<ThreadMonitorEvent> exitList = new ConcurrentLinkedQueue<ThreadMonitorEvent>();
private static Queue<ThreadMonitorEvent> runList = new ConcurrentLinkedQueue<ThreadMonitorEvent>();
private static Queue<ThreadMonitorEvent> interruptedList = new ConcurrentLinkedQueue<ThreadMonitorEvent>();
/** The first instance of ThreadHistoryMonitorHelper that will be used as the mbean
* by {@link #registerHelperMBean(String)}.
*/
private static ThreadHistoryMonitorHelper INSTANCE;
/** org.jboss.byteman.sample.helper.debug system property debug mode flag */
private static boolean DEBUG;
/**
* Looks to the org.jboss.byteman.sample.helper.debug system property to
* set the class DEBUG mode flag.
*/
public static void activated() {
DEBUG = Boolean.getBoolean("org.jboss.byteman.sample.helper.debug");
if(DEBUG)
System.err.println("ThreadHistoryMonitorHelper.activated, ");
}
public static void installed(Rule rule) {
if(DEBUG)
System.err.println("ThreadHistoryMonitorHelper.installed, "+rule);
}
protected ThreadHistoryMonitorHelper(Rule rule) {
super(rule);
INSTANCE = this;
}
/**
* Register the INSTANCE as an mbean under the given name.
* @param name - the object name string to register the INSTANCE under
*/
public void registerHelperMBean(String name) {
synchronized (createList) {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
ObjectName oname = new ObjectName(name);
if(mbs.isRegistered(oname) == false)
mbs.registerMBean(INSTANCE, oname);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* trace creation of the supplied thread to System.out
*
* this should only be triggered from the {@link Thread} constructor
*
* @param thread the newly created thread
* @param depth unused
*/
public void traceCreate(Thread thread, int depth)
{
ThreadMonitored monitoredThread = getMonitoredThread(thread);
ThreadMonitored createdByMonitoredThread = getMonitoredThread(Thread.currentThread());
monitoredThread.setCreatedBy(createdByMonitoredThread);
ThreadMonitorEvent event = newThreadEvent(monitoredThread, thread, ThreadMonitorEventType.CREATE);
createList.add(event);
}
/**
* trace start of the supplied thread to System.out
*
* this should only be triggered from the call to java.lang.Thread.start"
*
* @param thread the newly starting thread
*/
public void traceStart(Thread thread)
{
ThreadMonitored monitoredThread = getMonitoredThread(thread);
ThreadMonitorEvent event = newThreadEvent(monitoredThread, thread, ThreadMonitorEventType.START);
startList.add(event);
}
/**
* trace exit of the supplied thread to System.out
*
* this should only be triggered from the call to {@link Thread} exit method
*
* @param thread the exiting thread
*/
public void traceExit(Thread thread)
{
ThreadMonitored monitoredThread = getMonitoredThread(thread);
ThreadMonitorEvent event = newThreadEvent(monitoredThread, thread, ThreadMonitorEventType.EXIT);
exitList.add(event);
}
/**
* trace interrupted of the supplied thread to System.out
*
* this should only be triggered from the call to {@link Thread#interrupt()}
*
* @param thread the interrupting thread
*/
public void traceInterrupt(Thread thread)
{
ThreadMonitored monitoredThread = getMonitoredThread(thread);
ThreadMonitorEvent event = newThreadEvent(monitoredThread, thread, ThreadMonitorEventType.INTERRUPT);
interruptedList.add(event);
}
/**
* trace run of the supplied Runnable to System.out
*
* this should only be triggered from a call to an implementation of {@link Runnable#run()}
*
* @param runnable the runnable being run
*/
public void traceRun(Runnable runnable)
{
Thread thread = Thread.currentThread();
ThreadMonitored monitoredThread = getMonitoredThread(thread);
monitoredThread.setRunnableClass(runnable.getClass());
ThreadMonitorEvent event = newThreadEvent(monitoredThread, thread, ThreadMonitorEventType.RUN);
runList.add(event);
}
public ThreadMonitorEvent[] getCreateEvents() {
ThreadMonitorEvent[] events = new ThreadMonitorEvent[createList.size()];
return createList.toArray(events);
}
public ThreadMonitorEvent[] getStartEvents() {
ThreadMonitorEvent[] events = new ThreadMonitorEvent[startList.size()];
return startList.toArray(events);
}
public ThreadMonitorEvent[] getExitEvents() {
ThreadMonitorEvent[] events = new ThreadMonitorEvent[exitList.size()];
return exitList.toArray(events);
}
public ThreadMonitorEvent[] getRunEvents() {
ThreadMonitorEvent[] events = new ThreadMonitorEvent[runList.size()];
return runList.toArray(events);
}
public String getEventReport() throws IOException {
StringWriter sw = new StringWriter();
Formatter format = new Formatter(sw);
writeFullEvents(format, "Thread.create", createList);
writeFullEvents(format, "Thread.start", startList);
writeFullEvents(format, "Thread.exit", exitList);
writeFullEvents(format, "Runable.run", runList);
sw.close();
return sw.toString();
}
public void writeEventsToFile(String type, String path) throws IOException {
FileWriter fw = new FileWriter(path);
Formatter format = new Formatter(fw);
if(type == null || type.length() == 0 || type.equalsIgnoreCase("create"))
writeFullEvents(format, "Thread.create Events", createList);
if(type == null || type.length() == 0 || type.equalsIgnoreCase("start"))
writeFullEvents(format, "Thread.start Events", startList);
if(type == null || type.length() == 0 || type.equalsIgnoreCase("exit"))
writeFullEvents(format, "Thread.exit Events", exitList);
if(type == null || type.length() == 0 || type.equalsIgnoreCase("run"))
writeFullEvents(format, "Runable.run Events", runList);
fw.close();
}
/**
* Write all events to the file given by path
*
* @param path path to file
* @throws IOException if an io error occurs
*/
public void writeAllEventsToFile(String path) throws IOException {
System.err.println("writeAllEventsToFile: "+path);
writeAllEventsToFile(path, 0);
}
/**
* Write all events to the file given by path, repeating sampleCount times
* at 5 second intervals. The actual filename of each sample report will be either
* path-n where n = [0,sampleCount] if path does not contain a suffix, for example:
* /tmp/report-0
* /tmp/report-1
* /tmp/report-3
* or
* pathbase-n.suffix if there is a '.' delimited suffix (.txt), for example:
* /tmp/report-0.txt
* /tmp/report-1.txt
* /tmp/report-3.txt
* @param path - the path to the event report file
* @param sampleCount - the number of samples to take
* @throws IOException - thrown on any IO failure
*/
public synchronized void writeAllEventsToFile(String path, int sampleCount) throws IOException {
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
ArrayList<ScheduledFuture> tasks = new ArrayList<ScheduledFuture>();
// Look for path suffix
String suffix = null;
String base = path;
int lastDot = path.lastIndexOf('.');
if(lastDot > 0) {
suffix = path.substring(lastDot);
base = path.substring(0, lastDot);
}
for(int n = 0; n <= sampleCount; n ++) {
final String samplePath = base + "-" + n + (suffix != null ? suffix : "");
int delay = 5*n;
ScheduledFuture future = ses.schedule(new Runnable() {
@Override
public void run() {
try {
doWriteAllEvents(samplePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}, delay, TimeUnit.SECONDS);
tasks.add(future);
}
// Wait for tasks to complete
for(ScheduledFuture future : tasks) {
try {
future.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void doWriteAllEvents(String path) throws IOException {
FileWriter fw = new FileWriter(path);
Formatter format = new Formatter(fw);
writeFullEvents(format, "Thread.create", createList);
writeFullEvents(format, "Thread.start", startList);
writeFullEvents(format, "Thread.exit", exitList);
writeFullEvents(format, "Runable.run", runList);
// thread which were started but not exited yet
Map<ThreadMonitored, ThreadMonitorEvent> runningThreads = getThreadEventMap(startList);
for(ThreadMonitorEvent exitEvent: exitList) {
runningThreads.remove(exitEvent.getMonitoredThread());
}
writeThreadNames(format, "Thread.start but not exit", runningThreads.values());
fw.close();
System.err.println("Wrote events to: " + path);
}
private void writeFullEvents(Formatter fw, String title, Collection<ThreadMonitorEvent> events) {
int count = 0;
List<String> threadNames = new ArrayList<String>();
fw.format("+++ Begin %s Events, count=%d +++\n", title, events.size());
for(ThreadMonitorEvent event : events) {
ThreadMonitored thread = event.getMonitoredThread();
if(thread.getRunnableClass() != null) {
fw.format("#%d [%s], %s:%s(runnable=%s, by=%s)\n%s\n", count++, title, thread.getThreadName(), thread.getThreadId(),
thread.getRunnableClass(), thread.getCreatedBy(), event.getFullStack());
} else {
fw.format("#%d [%s], %s:%s(by=%s)\n%s\n", count++, title, thread.getThreadName(), thread.getThreadId(), thread.getCreatedBy(), event.getFullStack());
}
threadNames.add(thread.toString());
}
fw.format("+++ End %s Events +++\n", title);
writeThreadNames(fw, title, events);;
}
private void writeThreadNames(Formatter fw, String title, Collection<ThreadMonitorEvent> events) {
List<String> threadNames = new ArrayList<String>();
for(ThreadMonitorEvent event : events) {
threadNames.add(event.getMonitoredThread().toString());
}
fw.format("+++ Begin %s Thread Names (count=%s) +++\n", title, threadNames.size());
for(String name : threadNames) {
fw.format("%s\n", name);
}
fw.format("+++ End %s Thread Names +++\n", title);
}
/**
* Common ThreadMonitorEvent creation method.
*
* @param thread - the thread associated with the event
* @param eventType - the type of the event.
* @return the ThreadMonitorEvent instance for the event.
*/
public ThreadMonitorEvent newThreadEvent(ThreadMonitored threadMonitored, Thread thread, ThreadMonitorEventType eventType) {
StringBuffer line = new StringBuffer();
StringBuilder buffer = new StringBuilder();
StackTraceElement[] stack = getStack();
ArrayList<String> stackInfo = new ArrayList<String>();
int l = stack.length;
int t = super.triggerIndex(stack);
line.append("*** Thread ");
line.append(eventType);
line.append(" ");
line.append(thread.getName());
line.append(" ");
line.append(thread.getClass().getCanonicalName());
line.append('\n');
stackInfo.add(line.toString());
line.setLength(0);
for(int n = t; n < l; n ++) {
StackTraceElement frame = stack[n];
super.printlnFrame(line, frame);
buffer.append(line);
stackInfo.add(line.toString());
line.setLength(0);
}
//
String fullStack = buffer.toString();
ThreadMonitorEvent threadEvent = new ThreadMonitorEvent(threadMonitored, eventType, stackInfo, fullStack);
return threadEvent;
}
/**
* Returning monitored thread belonging to the provided thread object.
* If such monitored thread does not exist (is not known to {@link ThreadHistoryMonitorHelper})
* then brand new {@link ThreadMonitored} object is created and is added to the list
* checked by the helper class.
*
* @param thread thread that belonging ThreadMonitorThread is search for
* @return ThreadMonitorThread which belongs to provided thread or null in no such known
*/
private ThreadMonitored getMonitoredThread(Thread thread) {
ThreadMonitored mt = ThreadMonitored.newMonitoredThread(thread);
if(monitoredThreads.containsKey(mt)) {
return monitoredThreads.get(mt);
} else {
monitoredThreads.put(mt, mt);
return mt;
}
}
private Map<ThreadMonitored, ThreadMonitorEvent> getThreadEventMap(Iterable<ThreadMonitorEvent> events) {
Map<ThreadMonitored, ThreadMonitorEvent> threadEventMap = new HashMap<ThreadMonitored, ThreadMonitorEvent>();
for(ThreadMonitorEvent event: events) {
threadEventMap.put(event.getMonitoredThread(), event);
}
return threadEventMap;
}
}