/* * 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.*; /** * @author jaehong.kim */ public class SpanCallTree implements CallTree { private static final int MIN_DEPTH = -1; private static final int LEVEL_DEPTH = -1; private static final int ROOT_DEPTH = 0; private CallTreeNode root; private CallTreeNode cursor; public SpanCallTree(final SpanAlign spanAlign) { this.root = new CallTreeNode(null, spanAlign); this.cursor = this.root; } public CallTreeNode getRoot() { return root; } public CallTreeIterator iterator() { return new CallTreeIterator(root); } public boolean isEmpty() { return root.getValue() == null; } public void add(final CallTree tree) { final CallTreeNode node = tree.getRoot(); if (node == null) { // skip return; } if (!cursor.hasChild()) { node.setParent(cursor); cursor.setChild(node); return; } CallTreeNode sibling = findLastSibling(cursor.getChild()); node.setParent(sibling.getParent()); sibling.setSibling(node); } // test only public void add(final int parentDepth, final CallTree tree) { if (parentDepth < MIN_DEPTH) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid parent depth. parent depth=" + parentDepth + ", cursor=" + cursor + ", tree=" + tree); } final CallTreeNode node = tree.getRoot(); if (node == null) { // skip return; } if (parentDepth == LEVEL_DEPTH || parentDepth == cursor.getDepth()) { // validate if (cursor.isRoot()) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid depth. depth=" + parentDepth + ", cursor=" + cursor + ", tree=" + tree); } if (!cursor.hasChild()) { node.setParent(cursor); cursor.setChild(node); return; } CallTreeNode sibling = findLastSibling(cursor.getChild()); node.setParent(cursor); sibling.setSibling(node); return; } // greater if (parentDepth > cursor.getDepth()) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid depth. depth=" + parentDepth + ", cursor=" + cursor + ", align=" + tree); } // lesser if (cursor.getDepth() - parentDepth < ROOT_DEPTH) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid depth. depth=" + parentDepth + ", cursor=" + cursor + ", align=" + tree); } final CallTreeNode parent = findUpperLevelLastSibling(parentDepth, cursor); cursor = parent; if (!cursor.hasChild()) { node.setParent(cursor); cursor.setChild(node); return; } CallTreeNode sibling = findLastSibling(cursor.getChild()); node.setParent(cursor); sibling.setSibling(node); } public void add(final int depth, final SpanAlign spanAlign) { if (hasCorrupted(spanAlign)) { throw new CorruptedSpanCallTreeNodeException("invalid sequence", "corrupted event. depth=" + depth + ", cursor=" + cursor + ", align=" + spanAlign); } if (depth == LEVEL_DEPTH || depth == cursor.getDepth()) { // validate if (cursor.isRoot()) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid depth. depth=" + depth + ", cursor=" + cursor + ", align=" + spanAlign); } CallTreeNode sibling = findLastSibling(cursor); sibling.setSibling(spanAlign); cursor = sibling.getSibling(); return; } // greater if (depth > cursor.getDepth()) { // validate if (depth > cursor.getDepth() + 1) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid depth. depth=" + depth + ", cursor=" + cursor + ", align=" + spanAlign); } if (!cursor.hasChild()) { cursor.setChild(spanAlign); cursor = cursor.getChild(); return; } CallTreeNode sibling = findLastSibling(cursor.getChild()); sibling.setSibling(spanAlign); cursor = sibling.getSibling(); return; } // lesser if (cursor.getDepth() - depth <= ROOT_DEPTH) { throw new CorruptedSpanCallTreeNodeException("invalid depth", "invalid depth. depth=" + depth + ", cursor=" + cursor + ", align=" + spanAlign); } final CallTreeNode node = findUpperLevelLastSibling(depth, cursor); node.setSibling(spanAlign); cursor = node.getSibling(); } boolean hasCorrupted(final SpanAlign spanAlign) { if (spanAlign.isSpan()) { return false; } if (cursor.getValue().isSpan()) { return spanAlign.getSpanEventBo().getSequence() != 0; } return cursor.getValue().getSpanEventBo().getSequence() + 1 != spanAlign.getSpanEventBo().getSequence(); } CallTreeNode findUpperLevelLastSibling(final int level, final CallTreeNode node) { final CallTreeNode parent = findUpperLevel(level, node); return findLastSibling(parent); } CallTreeNode findUpperLevel(final int level, final CallTreeNode node) { CallTreeNode parent = node.getParent(); while (parent.getDepth() != level) { parent = parent.getParent(); } return parent; } CallTreeNode findLastSibling(final CallTreeNode node) { CallTreeNode lastSibling = node; while (lastSibling.getSibling() != null) { lastSibling = lastSibling.getSibling(); } return lastSibling; } public void sort() { travel(root); } void travel(CallTreeNode node) { sortChildSibling(node); if (node.hasChild()) { travel(node.getChild()); } // change logic from recursive to loop, because of avoid call-stack-overflow. CallTreeNode sibling = node.getSibling(); while(sibling != null) { sortChildSibling(sibling); if(sibling.hasChild()) { travel(sibling.getChild()); } sibling = sibling.getSibling(); } } void sortChildSibling(final CallTreeNode parent) { if (!parent.hasChild() || !parent.getChild().hasSibling()) { // no child or no child sibling. return; } final List<CallTreeNode> events = new ArrayList<>(); final LinkedList<CallTreeNode> spans = new LinkedList<>(); splitChildSiblingNodes(parent, events, spans); if (spans.isEmpty()) { // not found span return; } // order by abs. Collections.sort(spans, new Comparator<CallTreeNode>() { @Override public int compare(CallTreeNode source, CallTreeNode target) { return (int) (source.getValue().getStartTime() - target.getValue().getStartTime()); } }); // sort final List<CallTreeNode> nodes = new ArrayList<>(); for (CallTreeNode event : events) { while (spans.peek() != null && event.getValue().getStartTime() > spans.peek().getValue().getStartTime()) { nodes.add(spans.poll()); } nodes.add(event); } nodes.addAll(spans); // reform sibling CallTreeNode prev = null; for (CallTreeNode node : nodes) { final CallTreeNode reset = null; node.setSibling(reset); if (prev == null) { parent.setChild(node); prev = node; } else { prev.setSibling(node); prev = node; } } } private void splitChildSiblingNodes(final CallTreeNode parent, List<CallTreeNode> events, List<CallTreeNode> spans) { CallTreeNode node = parent.getChild(); if (node.getValue().isSpan()) { spans.add(node); } else { events.add(node); } while (node.hasSibling()) { node = node.getSibling(); if (node.getValue().isSpan()) { spans.add(node); } else { events.add(node); } } } }