/*
* Copyright 2015 NAVER Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.navercorp.pinpoint.web.calltree.span;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* @author jaehong.kim
*/
public class CallTreeIterator implements Iterator<CallTreeNode> {
private List<CallTreeNode> nodes = new LinkedList<>();
private int index = -1;
public CallTreeIterator(final CallTreeNode root) {
if (root == null) {
return;
}
addNode(root);
if (root.hasChild()) {
populate(root.getChild());
}
index = -1;
}
void populate(CallTreeNode node) {
if (node == null) {
return;
}
addNode(node);
if (node.hasChild()) {
populate(node.getChild());
}
// change logic from recursive to loop, because of avoid call-stack-overflow.
CallTreeNode sibling = node.getSibling();
while (sibling != null) {
addNode(sibling);
if (sibling.hasChild()) {
populate(sibling.getChild());
}
sibling = sibling.getSibling();
}
}
void addNode(CallTreeNode node) {
nodes.add(node);
index++;
final SpanAlign align = node.getValue();
align.setGap(getGap());
align.setDepth(node.getDepth());
align.setExecutionMilliseconds(getExecutionTime());
}
public long getGap() {
final CallTreeNode current = getCurrent();
if (current.isRoot()) {
return 0;
}
if (current.getValue().isAsyncFirst()) {
final CallTreeNode parent = getAsyncParent(current);
if (parent == null) {
return 0;
}
// skip sibling.
return current.getValue().getStartTime() - parent.getValue().getStartTime();
}
final CallTreeNode prev = getPrev();
if (prev == null) {
throw new IllegalStateException("A non-root CallTreeNode must have a previous node");
}
return current.getValue().getStartTime() - getLastExecuteTime(current, prev);
}
public long getLastExecuteTime(final CallTreeNode current, final CallTreeNode prev) {
if (prev.getDepth() < current.getDepth()) {
// push and not closed.
return prev.getValue().getStartTime();
}
CallTreeNode node = prev;
if (prev.getDepth() > current.getDepth()) {
// pop prev sibling.
node = getPrevSibling(current);
}
while (true) {
if (!node.getValue().isAsyncFirst()) {
// not async first.
return node.getValue().getLastTime();
} else if (isFirstChild(node)) {
// first child
return node.getParent().getValue().getStartTime();
}
// pop prev sibling.
node = getPrevSibling(node);
}
}
CallTreeNode getPrevSibling(final CallTreeNode node) {
CallTreeNode sibling = node.getParent().getChild();
while (node != sibling.getSibling()) {
sibling = sibling.getSibling();
if(sibling == null) {
throw new IllegalStateException("Not found prev sibling " + node);
}
}
return sibling;
}
boolean isFirstChild(final CallTreeNode node) {
return node.getParent().getChild() == node;
}
CallTreeNode getAsyncParent(final CallTreeNode node) {
final int asyncId = node.getValue().getSpanEventBo().getAsyncId();
CallTreeNode parent = node.getParent();
while (parent != null && !parent.isRoot()) {
if (!parent.getValue().isSpan() && asyncId == parent.getValue().getSpanEventBo().getNextAsyncId()) {
return parent;
}
parent = parent.getParent();
}
return null;
}
public long getExecutionTime() {
final CallTreeNode current = getCurrent();
final SpanAlign align = current.getValue();
if (!current.hasChild()) {
return align.getElapsed();
}
return align.getElapsed() - getChildrenTotalElapsedTime(current);
}
long getChildrenTotalElapsedTime(final CallTreeNode node) {
long totalElapsed = 0;
CallTreeNode child = node.getChild();
while (child != null) {
SpanAlign align = child.getValue();
if (!align.isSpan() && !align.isAsyncFirst()) {
// skip span and first async event;
totalElapsed += align.getElapsed();
}
child = child.getSibling();
}
return totalElapsed;
}
@Override
public boolean hasNext() {
return index < nodes.size() - 1;
}
@Override
public CallTreeNode next() {
if (!hasNext()) {
return null;
}
index++;
return nodes.get(index);
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
public boolean hasPrev() {
return index > 0;
}
public CallTreeNode prev() {
if (!hasPrev()) {
return null;
}
index--;
return nodes.get(index);
}
public CallTreeNode getCurrent() {
return nodes.get(index);
}
public CallTreeNode getPrev() {
if (!hasPrev()) {
return null;
}
return nodes.get(index - 1);
}
public CallTreeNode getNext() {
if (!hasNext()) {
return null;
}
return nodes.get(index + 1);
}
public List<SpanAlign> values() {
List<SpanAlign> values = new ArrayList<>();
for (CallTreeNode node : nodes) {
values.add(node.getValue());
}
return values;
}
public int size() {
return nodes.size();
}
public boolean isEmpty() {
return size() == 0;
}
public String toString() {
final StringBuilder sb = new StringBuilder();
for (CallTreeNode node : nodes) {
for (int i = 0; i <= node.getDepth(); i++) {
sb.append("#");
}
sb.append(" : ").append(node);
sb.append("\n");
}
return sb.toString();
}
}