package org.stagemonitor.tracing.profiler;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.stagemonitor.core.Stagemonitor;
import org.stagemonitor.tracing.TracingPlugin;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class CallStackElement {
private static final boolean useObjectPooling = Stagemonitor.getPlugin(TracingPlugin.class).isProfilerObjectPoolingActive();
private static Queue<CallStackElement> objectPool;
static {
if (useObjectPooling) {
objectPool = new ArrayBlockingQueue<CallStackElement>(100000);
}
}
private static final String HORIZONTAL;
private static final String HORIZONTAL_ANGLE;
private static final String ANGLE;
static {
HORIZONTAL = new String(new char[]{9474, ' ', ' ', ' '});
ANGLE = new String(new char[] {9492, 9472, 9472, ' '});
HORIZONTAL_ANGLE = new String(new char[]{9500, 9472, 9472, ' '});
}
@JsonIgnore
private CallStackElement parent;
private String signature;
private long executionTime;
private List<CallStackElement> children = new LinkedList<CallStackElement>();
public static CallStackElement createRoot(String signature) {
return CallStackElement.create(null, signature, System.nanoTime());
}
public static CallStackElement create(CallStackElement parent, String signature) {
return CallStackElement.create(parent, signature, System.nanoTime());
}
/**
* This static factory method also sets the parent-child relationships.
* @param parent the parent
* @param startTimestamp the timestamp at the beginning of the method
*/
public static CallStackElement create(CallStackElement parent, String signature, long startTimestamp) {
CallStackElement cse;
if (useObjectPooling) {
cse = objectPool.poll();
if (cse == null) {
cse = new CallStackElement();
}
} else {
cse = new CallStackElement();
}
cse.executionTime = startTimestamp;
cse.signature = signature;
if (parent != null) {
cse.parent = parent;
parent.children.add(cse);
}
return cse;
}
public void recycle() {
if (!useObjectPooling) {
return;
}
parent = null;
signature = null;
executionTime = 0;
for (CallStackElement child : children) {
child.recycle();
}
children.clear();
objectPool.offer(this);
}
public void removeCallsFasterThan(long thresholdNs) {
for (Iterator<CallStackElement> iterator = children.iterator(); iterator.hasNext(); ) {
CallStackElement child = iterator.next();
if (child.executionTime < thresholdNs && !child.isIOQuery()) {
iterator.remove();
child.recycle();
} else {
child.removeCallsFasterThan(thresholdNs);
}
}
}
public boolean isIOQuery() {
// that might be a bit ugly, but it saves reference to a boolean and thus memory
return signature.charAt(signature.length() - 1) == ' ';
}
/**
* The execution time of the Method.
* Initially set to the start timestamp
*
* @return the execution time/start timestamp of the method
*/
public long getExecutionTime() {
return executionTime;
}
public void setExecutionTime(long executionTime) {
this.executionTime = executionTime;
}
public long getNetExecutionTime() {
long net = executionTime;
for (CallStackElement child : children) {
net -= child.executionTime;
}
return net;
}
public void incrementExecutionTime(long additionalExecutionTime) {
executionTime += additionalExecutionTime;
}
public List<CallStackElement> getChildren() {
return children;
}
public void setChildren(List<CallStackElement> children) {
this.children = children;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
/**
* Returns <code>null</code>, if the signature is no method signature (such as 'total') ClassName#methodName
* otherwise
*
* @return <code>null</code>, if the signature is no method signature (such as 'total') ClassName#methodName
* otherwise
*/
public String getShortSignature() {
if (signature.indexOf('(') == -1 || signature.indexOf(':') != -1) {
return null;
}
String[] split = signature.substring(0, signature.indexOf('(')).split("\\.");
if (split.length > 1) {
return split[split.length - 2] + '#' + split[split.length - 1];
} else {
return split.length == 1 ? split[0] : "null";
}
}
public CallStackElement getParent() {
return parent;
}
public void setParent(CallStackElement parent) {
this.parent = parent;
}
private void removeLastChild() {
children.remove(children.size() - 1).recycle();
}
/**
* Removes this node from the parent
*/
public void remove() {
if (parent != null) {
parent.getChildren().remove(this);
recycle();
}
}
public void executionStopped(long executionTime) {
this.executionTime = executionTime;
}
/**
*
* @param timestamp the stop timestamp
* @param minExecutionTime the threshold for the minimum execution time
* @return the parent of this {@link CallStackElement}
*/
public CallStackElement executionStopped(long timestamp, long minExecutionTime) {
// executionTime is initialized to start timestamp
long localExecutionTime = timestamp - this.executionTime;
this.executionTime = localExecutionTime;
if (localExecutionTime < minExecutionTime && parent != null) {
// <this> is always the last entry in parent.getChildren()
parent.removeLastChild();
}
return parent;
}
@Override
public String toString() {
return toString(false);
}
public String toString(boolean asciiArt) {
final StringBuilder sb = new StringBuilder(3000);
logStats(getExecutionTime(), new LinkedList<String>(), sb, asciiArt);
return sb.toString();
}
public void logStats(long totalExecutionTimeNs, Deque<String> indentationStack, StringBuilder sb,
final boolean asciiArt) {
if (isRoot()) {
sb.append("----------------------------------------------------------------------\n");
sb.append("Selftime (ms) Total (ms) Method signature\n");
sb.append("----------------------------------------------------------------------\n");
}
appendTimesPercentTable(totalExecutionTimeNs, sb, asciiArt);
appendCallTree(indentationStack, sb, asciiArt);
for (CallStackElement callStats : getChildren()) {
if (!isRoot()) {
if (isLastChild()) {
indentationStack.add(" ");
} else {
indentationStack.add(asciiArt ? HORIZONTAL : "| ");
}
}
callStats.logStats(totalExecutionTimeNs, indentationStack, sb, asciiArt);
if (!isRoot()) {
indentationStack.pollLast();
}
}
}
private void appendTimesPercentTable(long totalExecutionTimeNs, StringBuilder sb, boolean asciiArt) {
appendNumber(sb, getNetExecutionTime());
appendPercent(sb, getNetExecutionTime(), totalExecutionTimeNs, asciiArt);
appendNumber(sb, getExecutionTime());
appendPercent(sb, getExecutionTime(), totalExecutionTimeNs, asciiArt);
}
private void appendNumber(StringBuilder sb, long time) {
sb.append(String.format(Locale.US, "%09.2f", time / 1000000.0)).append(" ");
}
private void appendPercent(StringBuilder sb, long time, long totalExecutionTimeNs, boolean asciiArt) {
final double percent = time / (double) totalExecutionTimeNs;
sb.append(String.format(Locale.US, "%03.0f", percent * 100)).append("% ").append(printPercentAsBar(percent, 10, asciiArt)).append(' ');
}
static String printPercentAsBar(double percent, int totalBars, boolean asciiArt) {
int actualBars = (int) (percent * totalBars);
boolean includeHalfBarAtEnd = actualBars * 2 != (int) (percent * totalBars * 2);
StringBuilder sb = new StringBuilder(totalBars);
for (int i = 0; i < totalBars; i++) {
if (i < actualBars) {
sb.append(asciiArt ? (char) 9608 : '|');
} else if (i == actualBars && includeHalfBarAtEnd) {
sb.append(asciiArt ? (char) 9619 : ':');
} else {
sb.append(asciiArt ? (char) 9617 : '-');
}
}
return sb.toString();
}
private void appendCallTree(Deque<String> indentationStack, StringBuilder sb, final boolean asciiArt) {
for (String indentation : indentationStack) {
sb.append(indentation);
}
if (!isRoot()) {
if (isLastChild()) {
sb.append(asciiArt ? ANGLE : "`-- ");
} else {
sb.append(asciiArt ? HORIZONTAL_ANGLE : "|-- ");
}
}
sb.append(getSignature()).append('\n');
}
private boolean isLastChild() {
if (parent == null) {
return true;
}
final List<CallStackElement> parentChildren = parent.getChildren();
return parentChildren.get(parentChildren.size() - 1) == this;
}
private boolean isRoot() {
return parent == null;
}
}