/** * 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.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import zipkin.DependencyLink; import zipkin.Span; import static java.util.logging.Level.FINE; /** * This parses a span tree into dependency links used by Web UI. Ex. http://zipkin/dependency * * <p>This implementation traverses the tree, and only creates links between {@link * DependencyLinkSpan.Kind#SERVER server} spans. One exception is at the bottom of the trace tree. * {@link DependencyLinkSpan.Kind#CLIENT client} spans that record their {@link * DependencyLinkSpan#peerService peer} are included, as this accounts for uninstrumented services. */ public final class DependencyLinker { private static final Logger logger = Logger.getLogger(DependencyLinker.class.getName()); private final Map<Pair<String>, Long> linkMap = new LinkedHashMap<>(); /** * @param spans spans where all spans have the same trace id */ public DependencyLinker putTrace(Collection<Span> spans) { if (spans.isEmpty()) return this; List<DependencyLinkSpan> linkSpans = new LinkedList<>(); for (Span s : MergeById.apply(spans)) { linkSpans.add(DependencyLinkSpan.from(s)); } return putTrace(linkSpans.iterator()); } /** * @param spans spans where all spans have the same trace id */ public DependencyLinker putTrace(Iterator<DependencyLinkSpan> spans) { if (!spans.hasNext()) return this; Node.TreeBuilder<DependencyLinkSpan> builder = new Node.TreeBuilder<>(); while (spans.hasNext()) { DependencyLinkSpan next = spans.next(); builder.addNode(next.parentId, next.id, next); } Node<DependencyLinkSpan> tree = builder.build(); if (logger.isLoggable(FINE)) logger.fine("traversing trace tree, breadth-first"); for (Iterator<Node<DependencyLinkSpan>> i = tree.traverse(); i.hasNext(); ) { Node<DependencyLinkSpan> current = i.next(); DependencyLinkSpan currentSpan = current.value(); if (logger.isLoggable(FINE)) { logger.fine("processing " + currentSpan); } if (current.isSyntheticRootForPartialTree()) { logger.fine("skipping synthetic node for broken span tree"); continue; } String child; String parent; switch (currentSpan.kind) { case SERVER: child = currentSpan.service; parent = currentSpan.peerService; if (current == tree) { // we are the root-most span. if (parent == null) { logger.fine("root's peer is unknown; skipping"); continue; } } break; case CLIENT: child = currentSpan.peerService; parent = currentSpan.service; break; default: logger.fine("non-rpc span; skipping"); continue; } if (logger.isLoggable(FINE) && parent == null) { logger.fine("cannot determine parent, looking for first server ancestor"); } // Local spans may be between the current node and its remote ancestor // Look up the stack until we see a service name, and assume that's the client Node<DependencyLinkSpan> ancestor = current.parent(); while (ancestor != null && parent == null) { if (logger.isLoggable(FINE)) { logger.fine("processing ancestor " + ancestor.value()); } DependencyLinkSpan ancestorLink = ancestor.value(); if (!ancestor.isSyntheticRootForPartialTree() && ancestorLink.kind == DependencyLinkSpan.Kind.SERVER) { parent = ancestorLink.service; break; } ancestor = ancestor.parent(); } if (parent == null || child == null) { logger.fine("cannot find server ancestor; skipping"); continue; } else if (logger.isLoggable(FINE)) { logger.fine("incrementing link " + parent + " -> " + child); } Pair<String> key = Pair.create(parent, child); if (linkMap.containsKey(key)) { linkMap.put(key, linkMap.get(key) + 1); } else { linkMap.put(key, 1L); } } return this; } public List<DependencyLink> link() { // links are merged by mapping to parent/child and summing corresponding links List<DependencyLink> result = new ArrayList<>(linkMap.size()); for (Map.Entry<Pair<String>, Long> entry : linkMap.entrySet()) { result.add(DependencyLink.create(entry.getKey()._1, entry.getKey()._2, entry.getValue())); } return result; } /** links are merged by mapping to parent/child and summing corresponding links */ public static List<DependencyLink> merge(Iterable<DependencyLink> in) { Map<Pair<String>, Long> links = new LinkedHashMap<>(); for (DependencyLink link : in) { Pair<String> parentChild = Pair.create(link.parent, link.child); long callCount = links.containsKey(parentChild) ? links.get(parentChild) : 0L; callCount += link.callCount; links.put(parentChild, callCount); } List<DependencyLink> result = new ArrayList<>(links.size()); for (Map.Entry<Pair<String>, Long> link : links.entrySet()) { result.add(DependencyLink.create(link.getKey()._1, link.getKey()._2, link.getValue())); } return result; } }