/* Copyright (c) 2013-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.metrics;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
final class CallStack implements Serializable {
private static final long serialVersionUID = -2123539518339716822L;
static final ThreadLocal<CallStack> CALL_STACK = new ThreadLocal<CallStack>();
private boolean root;
private String name;
private long nanoTime = -1L;
private boolean success;
private ArrayList<CallStack> children;
private long startTimeMillis;
private long startTimeNanos;
private CallStack(String name, boolean root, long startTimeMillis, long startTimeNanos) {
this.name = name;
this.root = root;
this.startTimeMillis = startTimeMillis;
this.startTimeNanos = startTimeNanos;
}
public static CallStack push(String name, long startTimeMillis, long startTimeNanos) {
CallStack root = CALL_STACK.get();
final CallStack newElement;
if (root == null) {
root = new CallStack(name, true, startTimeMillis, startTimeNanos);
newElement = root;
CALL_STACK.set(root);
} else {
CallStack current = root.current();
newElement = current.add(name, startTimeMillis, startTimeNanos);
}
return newElement;
}
public static CallStack pop(long nanoTime, boolean success) {
CallStack root = CALL_STACK.get();
checkNotNull(root, "called pop with no prior push?");
CallStack current = root.current();
current.setEndTime(nanoTime, success);
if (current.isRoot()) {
CALL_STACK.remove();
}
return current;
}
private CallStack current() {
return current(this);
}
private static CallStack current(CallStack parent) {
if (parent.children == null) {
return parent;
}
for (int i = parent.children.size() - 1; i >= 0; i--) {
CallStack lastAlive = parent.children.get(i);
if (!lastAlive.isFinished()) {
return lastAlive.current();
}
}
checkState(!parent.isFinished());
return parent;
}
boolean isFinished() {
return nanoTime != -1L;
}
public void setEndTime(long nanoTime, boolean success) {
this.nanoTime = nanoTime;
this.success = success;
}
public long getEllapsedNanos() {
return nanoTime = startTimeNanos;
}
public long getStartTimeMillis() {
return startTimeMillis;
}
private CallStack add(final String name, long startTimeMillis, long startTimeNanos) {
CallStack cs = new CallStack(name, false, startTimeMillis, startTimeNanos);
if (this.children == null) {
this.children = Lists.newArrayListWithCapacity(4);
}
this.children.add(cs);
return cs;
}
boolean isRoot() {
return root;
}
public String getName() {
return this.name;
}
public long getNanoTime() {
return this.nanoTime;
}
public boolean isSuccess() {
return this.success;
}
@Override
public String toString() {
return toString(TimeUnit.NANOSECONDS);
}
public String toString(TimeUnit durationUnit) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
dump(new PrintStream(out), durationUnit);
String string = out.toString();
return string;
}
public String toString(final double durationFactor, final String unitName, final long totalNanos) {
final double percent = (this.getEllapsedNanos() * 100.0) / totalNanos;
return String.format("%s -> %,.2f %s (%.2f%%), success: %s", name,
(durationFactor * nanoTime), unitName, percent, success);
}
public void dump(PrintStream stream, TimeUnit durationUnit) {
final double durationFactor = 1.0 / durationUnit.toNanos(1L);
dump(stream, this, 0, durationFactor, durationUnit.name(), this.getEllapsedNanos());
stream.flush();
}
private void dump(PrintStream out, final CallStack stackElement, final int indentLevel,
final double durationFactor, final String unitName, final long totalNanos) {
if (indentLevel > 0) {
out.print(Strings.repeat(" ", indentLevel));
}
out.println(stackElement.toString(durationFactor, unitName, totalNanos));
if (stackElement.children != null) {
for (CallStack childElem : stackElement.children) {
dump(out, childElem, indentLevel + 1, durationFactor, unitName, totalNanos);
}
}
}
}