/** * Copyright 2015-2017 The OpenZipkin Authors * * 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 zipkin.internal; import java.util.ArrayDeque; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import zipkin.Span; /** * Convenience type representing a tree. This is here because multiple facets in zipkin require * traversing the trace tree. For example, looking at network boundaries to correct clock skew, or * counting requests imply visiting the tree. * * @param <V> the node's value. Ex a full span or a tuple like {@code (serviceName, isLocal)} */ public final class Node<V> { /** Set via {@link #addChild(Node)} */ private Node<V> parent; /** mutable as some transformations, such as clock skew, adjust this. */ private V value; /** mutable to avoid allocating lists for childless nodes */ private List<Node<V>> children = Collections.emptyList(); private boolean missingRootDummyNode; /** Returns the parent, or null if root */ @Nullable public Node<V> parent() { return parent; } public V value() { return value; } public Node<V> value(V newValue) { this.value = newValue; return this; } public Node<V> addChild(Node<V> child) { child.parent = this; if (children.equals(Collections.emptyList())) children = new LinkedList<>(); children.add(child); return this; } /** Returns the children of this node. */ public Collection<Node<V>> children() { return children; } /** Traverses the tree, breadth-first. */ public Iterator<Node<V>> traverse() { return new BreadthFirstIterator<>(this); } public boolean isSyntheticRootForPartialTree() { return missingRootDummyNode; } static final class BreadthFirstIterator<V> implements Iterator<Node<V>> { private final Queue<Node<V>> queue = new ArrayDeque<>(); BreadthFirstIterator(Node<V> root) { queue.add(root); } @Override public boolean hasNext() { return !queue.isEmpty(); } @Override public Node<V> next() { Node<V> result = queue.remove(); queue.addAll(result.children); return result; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } /** * @param trace spans that belong to the same {@link Span#traceId trace}, in any order. */ static Node<Span> constructTree(List<Span> trace) { TreeBuilder<Span> treeBuilder = new TreeBuilder<>(); for (Span s : trace) { treeBuilder.addNode(s.parentId, s.id, s); } return treeBuilder.build(); } /** * Some operations do not require the entire span object. This creates a tree given (parent id, * id) pairs. * * @param <V> same type as {@link Node#value} */ public static final class TreeBuilder<V> { Node<V> rootNode = null; // Nodes representing the trace tree Map<Long, Node<V>> idToNode = new LinkedHashMap<>(); // Collect the parent-child relationships between all spans. Map<Long, Long> idToParent = new LinkedHashMap<>(idToNode.size()); public void addNode(Long parentId, long id, @Nullable V value) { Node<V> node = new Node<V>().value(value); if (parentId == null) { // special-case root, and attribute missing parents to it. In // other words, assume that the first root is the "real" root. if (rootNode == null) { rootNode = node; } else { idToNode.put(id, node); idToParent.put(id, null); } } else { idToNode.put(id, node); idToParent.put(id, parentId); } } /** Builds a tree from calls to {@link #addNode}, or returns an empty tree. */ public Node<V> build() { // Materialize the tree using parent - child relationships for (Map.Entry<Long, Long> entry : idToParent.entrySet()) { Node<V> node = idToNode.get(entry.getKey()); Node<V> parent = idToNode.get(entry.getValue()); if (parent == null) { // handle headless trace if (rootNode == null) { rootNode = new Node<>(); rootNode.missingRootDummyNode = true; } rootNode.addChild(node); } else { parent.addChild(node); } } return rootNode != null ? rootNode : new Node<>(); } } }