
be.bagofwords.application.status.perf.ThreadSampleMonitor Maven / Gradle / Ivy
package be.bagofwords.application.status.perf;
import be.bagofwords.application.ApplicationContextFactory;
import be.bagofwords.application.CloseableComponent;
import be.bagofwords.application.EnvironmentProperties;
import be.bagofwords.application.annotations.EagerBowComponent;
import be.bagofwords.counts.Counter;
import be.bagofwords.ui.UI;
import be.bagofwords.util.SafeThread;
import be.bagofwords.util.Utils;
import be.bagofwords.web.BaseController;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import spark.Request;
import spark.Response;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
@EagerBowComponent
public class ThreadSampleMonitor extends BaseController implements CloseableComponent {
public static final int MAX_NUM_OF_SAMPLES = 10000;
private final TraceSampler traceSampler;
private final Counter relevantTracesCounter;
private final Counter lessRelevantTracesCounter;
private int numOfSamples;
private boolean saveThreadSamplesToFile;
private String locationForSavedThreadSamples;
private String applicationName;
/**
* Constructor to be used in spring context
*/
@Autowired
public ThreadSampleMonitor(EnvironmentProperties environmentProperties, ApplicationContextFactory applicationContextFactory) {
this(environmentProperties.saveThreadSamplesToFile(), environmentProperties.getThreadSampleLocation(), applicationContextFactory.getApplicationName());
}
/**
* Constructor to be used outside spring context
*
* @param saveThreadSamplesToFile
* @param locationForSavedThreadSamples
* @param applicationName
*/
public ThreadSampleMonitor(boolean saveThreadSamplesToFile, String locationForSavedThreadSamples, String applicationName) {
super("/perf");
this.saveThreadSamplesToFile = saveThreadSamplesToFile;
this.locationForSavedThreadSamples = locationForSavedThreadSamples;
this.applicationName = applicationName;
this.relevantTracesCounter = new Counter<>();
this.lessRelevantTracesCounter = new Counter<>();
this.traceSampler = new TraceSampler();
this.traceSampler.start();
this.numOfSamples = 0;
}
@Override
protected synchronized String handleRequest(Request request, Response response) throws Exception {
StringBuilder result = new StringBuilder();
synchronized (relevantTracesCounter) {
synchronized (lessRelevantTracesCounter) {
result.append("Collected " + relevantTracesCounter.getTotal() + " samples.");
result.append("Relevant traces
");
ThreadSamplesPrinter.printTopTraces(result, relevantTracesCounter, numOfSamples);
result.append("
");
result.append("Other traces
");
ThreadSamplesPrinter.printTopTraces(result, lessRelevantTracesCounter, numOfSamples);
result.append("
");
}
}
return result.toString();
}
@Override
public void terminate() {
traceSampler.terminateAndWaitForFinish();
if (saveThreadSamplesToFile) {
saveThreadSamplesToFile();
}
}
private void saveThreadSamplesToFile() {
try {
synchronized (relevantTracesCounter) {
synchronized (lessRelevantTracesCounter) {
File file = new File(locationForSavedThreadSamples + "_" + applicationName + ".txt");
StringBuilder sb = new StringBuilder();
sb.append("Traces for " + applicationName + " on " + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm") + "\n\n");
sb.append("-- Relevant traces --\n\n");
ThreadSamplesPrinter.printTopTraces(sb, relevantTracesCounter, numOfSamples);
sb.append("\n\n-- Less relevant traces --\n\n");
ThreadSamplesPrinter.printTopTraces(sb, lessRelevantTracesCounter, numOfSamples);
FileUtils.writeStringToFile(file, sb.toString());
}
}
} catch (IOException exp) {
UI.writeError("Failed to save thread samples!", exp);
}
}
public void clearSamples() {
synchronized (relevantTracesCounter) {
relevantTracesCounter.clear();
}
synchronized (lessRelevantTracesCounter) {
lessRelevantTracesCounter.clear();
}
numOfSamples = 0;
}
public Counter getRelevantTracesCounter() {
return relevantTracesCounter;
}
public Counter getLessRelevantTracesCounter() {
return lessRelevantTracesCounter;
}
private class TraceSampler extends SafeThread {
public TraceSampler() {
super("traceSampler", true);
}
@Override
protected void runInt() throws Exception {
while (!isTerminateRequested()) {
Map stackTraces = Thread.getAllStackTraces();
numOfSamples++;
for (Thread thread : stackTraces.keySet()) {
StackTraceElement[] thisTrace = stackTraces.get(thread);
String threadName = thread.getName();
if (!threadName.equals("traceSampler") && thisTrace.length > 0) {
String methodName = thisTrace[0].getMethodName();
boolean notRelevantThread = threadName.equals("SparkServerThread") || threadName.equals("Signal Dispatcher") || threadName.equals("Finalizer");
notRelevantThread |= threadName.equals("DateCache") || threadName.startsWith("qtp") || threadName.equals("Reference Handler") || threadName.startsWith("HashSessionScavenger");
notRelevantThread |= methodName.equals("accept0") || methodName.equals("accept") || methodName.equals("epollWait") || methodName.equals("socketAccept");
notRelevantThread |= threadName.equals("ChangedValueListener") && methodName.equals("socketRead0");
notRelevantThread |= methodName.equals("park") && thisTrace[0].getClassName().equals("Unsafe");
notRelevantThread |= inReadNextActionMethod(threadName, thisTrace);
String normalizedThreadName = threadName.replaceAll("[^A-Za-z]", "");
Trace parent = new Trace(normalizedThreadName, null);
addTrace(notRelevantThread, parent);
for (int i = thisTrace.length - 1; i >= 0; i--) {
StackTraceElement element = thisTrace[i];
Trace trace = new Trace(element.getClassName() + "." + element.getMethodName() + "(" + element.getFileName() + ":" + element.getLineNumber() + ")", parent);
addTrace(notRelevantThread, trace);
parent = trace;
}
}
}
synchronized (relevantTracesCounter) {
relevantTracesCounter.trim(MAX_NUM_OF_SAMPLES / 2);
}
synchronized (lessRelevantTracesCounter) {
lessRelevantTracesCounter.trim(MAX_NUM_OF_SAMPLES / 2);
}
Utils.threadSleep(200);
}
}
private void addTrace(boolean notRelevantThread, Trace trace) {
if (notRelevantThread) {
synchronized (lessRelevantTracesCounter) {
lessRelevantTracesCounter.inc(trace);
}
} else {
synchronized (relevantTracesCounter) {
relevantTracesCounter.inc(trace);
}
}
}
private boolean inReadNextActionMethod(String threadName, StackTraceElement[] thisTrace) {
if (threadName.startsWith("DatabaseServerRequestHandler")) {
for (StackTraceElement traceElement : thisTrace) {
if (traceElement.getMethodName().equals("readNextAction")) {
return true;
}
}
}
return false;
}
}
public int getNumOfSamples() {
return numOfSamples;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy