package unittestenvironment.utils;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Die Klasse ThreadCPUTimeMonitor dient dem Ermitteln der
* CPU-Zeit, die von allen Threads des aktuellen Prozesses
* benoetigt wurde.
*
* Zusaetzlich besteht die Moeglichkeit, einzelne Threads
* von dieser Betrachtung auszuschliessen. Hierzu kann ein
* zu ignorierender Thread ueber eine entsprechende
* Methode an die Klasse ThreadCPUTimeMonitor uebergeben
* werden.
*
* @author Patrick Fries
*/
public class ThreadCPUTimeMonitor {
/** Speichert alle Threads und deren ermittelte CPU-Zeit */
private Map<Thread, MutableLong> threadCPUTimes;
/** Speichert die Threads, die ignoriert werden sollen */
private Set<Thread> threadsToIgnore;
/** Speichert die Wurzel-Thread-Gruppe */
private ThreadGroup rootThreadGroup;
/** Dient dem Ermitteln von CPU-Zeiten einzelner Threads */
private ThreadMXBean threadMXBean;
/**
* Initialisiert ein neues ThreadCPUTimeMonitor-Objekt.
*/
public ThreadCPUTimeMonitor() {
this.threadCPUTimes = new HashMap<Thread, MutableLong>();
this.threadsToIgnore = new HashSet<Thread>();
this.rootThreadGroup = this.findRootThreadGroup();
this.threadMXBean = ManagementFactory.getThreadMXBean();
this.collectThreads();
// Dieser Thread ruft in periodischen Abstaenden die Methode
// collectThreads auf, um staendig nach neuen Threads zu suchen,
// die noch nicht erfasst wurden
final Thread collectorThread = new Thread("collector") {
@Override
public void run() {
while (true) {
ThreadCPUTimeMonitor.this.collectThreads();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
collectorThread.start();
this.threadsToIgnore.add(collectorThread);
}
/**
* Liefert die von allen betracheten Threads bisher benoetigte CPU-Zeit
* @param printCPUTimes - Bestimmt, ob die CPU-Zeiten der betrachteten Threads
* auf der Standardausgabe ausgegeben werden
* @return CPU-Zeit, die von allen betrachteten Threads bisher benoetigt wurde
*/
public long calculateCPUTimeOfMonitoredThreads(final boolean printCPUTimes) {
long totalCPUTime = 0;
synchronized (this.threadCPUTimes) {
for (final Entry<Thread, MutableLong> entry : this.threadCPUTimes.entrySet()) {
final Thread currentThread = entry.getKey();
final MutableLong currentThreadCPUTime = entry.getValue();
// Sollte der betrachtete Thread nicht mehr aktiv sein oder in der
// Liste der zu ignorierenden Threads vorhanden sein,
// so wird er hier nicht betrachtet
final boolean isAlive = currentThread.isAlive();
final boolean isIgnored = this.threadsToIgnore.contains(currentThread);
if (isAlive && !isIgnored) {
// Aktualisieren der CPU-Zeit des betracheten Threads
final long cpuTime =
this.threadMXBean.getThreadCpuTime(currentThread.getId()) / 1000000;
if (cpuTime > 0) {
currentThreadCPUTime.value = cpuTime;
}
if (printCPUTimes) {
System.out.println(currentThread.getName() + ": " + cpuTime);
}
// Erhoehen der zu ermittelnden CPU-Zeit aller beobachteten
// Threads
totalCPUTime += cpuTime;
} else {
if (printCPUTimes) {
final String prolog = currentThread.getName() + ": "
+ currentThreadCPUTime.value;
if (!isAlive) {
System.out.println(prolog + " (DEAD)");
} else if (isIgnored) {
System.out.println(prolog + " (IGNORED)");
} else {
System.out.println(prolog + " (UNKNOWN)");
}
}
}
}
}
if (printCPUTimes) {
System.out.println("---------------------------");
System.out.flush();
}
return totalCPUTime;
}
/**
* Sucht nach Threads, die noch nicht erfasst wurden
*/
private void collectThreads() {
final int estimatedThreadCount = this.rootThreadGroup.activeCount() * 2;
final Thread[] threads = new Thread[estimatedThreadCount];
this.rootThreadGroup.enumerate(threads, true);
synchronized (this.threadCPUTimes) {
for (final Thread currentThread : threads) {
if (currentThread == null) {
break;
}
if (!this.threadCPUTimes.containsKey(currentThread)) {
this.threadCPUTimes.put(currentThread, new MutableLong());
}
}
}
}
/**
* Fuegt den uebergebenen Thread zur Liste der zu ignorierenden
* Threads hinzu
* @param thread - Zu ignorierender Thread
*/
public void addThreadToIgnore(final Thread thread) {
synchronized (this.threadCPUTimes) {
this.threadsToIgnore.add(thread);
}
}
/**
* Liefert die "oberste" ThreadGroup zurueck
* @return "oberste" ThreadGroup
*/
private ThreadGroup findRootThreadGroup() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
while (group.getParent() != null) {
group = group.getParent();
}
return group;
}
/**
* Hilfklasse, die einen veraenderbaren long-Wert
* kapselt.
*
* @author Patrick Fries
*/
private class MutableLong {
/** Gekapselter long-Wert */
private long value;
/** Initialisiert eine neue MutableLong-Instanz */
public MutableLong() {
}
/**
* Liefert den Wert des gekapselten long-Werts
* @return Gekapselter long-Wert
*/
public long getValue() {
return this.value;
}
}
}