All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zipkin2.internal.DependencyLinker Maven / Gradle / Ivy

There is a newer version: 3.4.2
Show newest version
/*
 * Copyright 2015-2024 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 zipkin2.internal;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import zipkin2.DependencyLink;
import zipkin2.Span;
import zipkin2.Span.Kind;

import static java.util.logging.Level.FINE;

/**
 * This parses a span tree into dependency links used by Web UI. Ex. http://zipkin/dependency
 *
 * 

This implementation traverses the tree, and only creates links between {@link Kind#SERVER * server} spans. One exception is at the bottom of the trace tree. {@link Kind#CLIENT client} spans * that record their {@link Span#remoteEndpoint()} are included, as this accounts for uninstrumented * services. Spans with {@link Span#kind()} unset, but {@link Span#remoteEndpoint()} set are treated * the same as client spans. */ public final class DependencyLinker { final Logger logger; final SpanNode.Builder builder; final Map callCounts = new LinkedHashMap<>(); final Map errorCounts = new LinkedHashMap<>(); public DependencyLinker() { this(Logger.getLogger(DependencyLinker.class.getName())); } DependencyLinker(Logger logger) { this.logger = logger; this.builder = SpanNode.newBuilder(logger); } /** All {@code spans} must have the same trace id. */ public DependencyLinker putTrace(List spans) { if (spans.isEmpty()) return this; SpanNode traceTree = builder.build(spans); if (logger.isLoggable(FINE)) logger.fine("traversing trace tree, breadth-first"); for (Iterator i = traceTree.traverse(); i.hasNext(); ) { SpanNode current = i.next(); Span currentSpan = current.span(); if (logger.isLoggable(FINE)) { logger.fine("processing " + currentSpan); } Kind kind = currentSpan.kind(); // When processing links to a client span, we prefer the server's name. If we have no child // spans, we proceed to use the name the client chose. if (Kind.CLIENT.equals(kind) && !current.children().isEmpty()) { continue; } String serviceName = currentSpan.localServiceName(); String remoteServiceName = currentSpan.remoteServiceName(); if (kind == null) { // Treat unknown type of span as a client span if we know both sides if (serviceName != null && remoteServiceName != null) { kind = Kind.CLIENT; } else { logger.fine("non remote span; skipping"); continue; } } String child; String parent; switch (kind) { case SERVER: case CONSUMER: child = serviceName; parent = remoteServiceName; if (current == traceTree) { // we are the root-most span. if (parent == null) { logger.fine("root's client is unknown; skipping"); continue; } } break; case CLIENT: case PRODUCER: parent = serviceName; child = remoteServiceName; break; default: logger.fine("unknown kind; skipping"); continue; } boolean isError = currentSpan.tags().containsKey("error"); if (kind == Kind.PRODUCER || kind == Kind.CONSUMER) { if (parent == null || child == null) { logger.fine("cannot link messaging span to its broker; skipping"); } else { addLink(parent, child, isError); } continue; } // Local spans may be between the current node and its remote parent Span remoteAncestor = firstRemoteAncestor(current); String remoteAncestorName; if (remoteAncestor != null && (remoteAncestorName = remoteAncestor.localServiceName()) != null) { // Some users accidentally put the remote service name on client annotations. // Check for this and backfill a link from the nearest remote to that service as necessary. if (kind == Kind.CLIENT && serviceName != null && !remoteAncestorName.equals(serviceName)) { logger.fine("detected missing link to client span"); addLink(remoteAncestorName, serviceName, false); // we don't know if there's an error here } if (kind == Kind.SERVER || parent == null) parent = remoteAncestorName; // When an RPC is split between spans, we skip the child (server side). If our parent is a // client, we need to check it for errors. if (!isError && Kind.CLIENT.equals(remoteAncestor.kind()) && currentSpan.parentId() != null && currentSpan.parentId().equals(remoteAncestor.id())) { isError = remoteAncestor.tags().containsKey("error"); } } if (parent == null || child == null) { logger.fine("cannot find remote ancestor; skipping"); continue; } addLink(parent, child, isError); } return this; } Span firstRemoteAncestor(SpanNode current) { SpanNode ancestor = current.parent(); while (ancestor != null) { Span maybeRemote = ancestor.span(); if (maybeRemote != null && maybeRemote.kind() != null) { if (logger.isLoggable(FINE)) logger.fine("found remote ancestor " + maybeRemote); return maybeRemote; } ancestor = ancestor.parent(); } return null; } void addLink(String parent, String child, boolean isError) { if (logger.isLoggable(FINE)) { logger.fine("incrementing " + (isError ? "error " : "") + "link " + parent + " -> " + child); } Pair key = new Pair(parent, child); if (callCounts.containsKey(key)) { callCounts.put(key, callCounts.get(key) + 1); } else { callCounts.put(key, 1L); } if (!isError) return; if (errorCounts.containsKey(key)) { errorCounts.put(key, errorCounts.get(key) + 1); } else { errorCounts.put(key, 1L); } } public List link() { return link(callCounts, errorCounts); } /** links are merged by mapping to parent/child and summing corresponding links */ public static List merge(Iterable in) { Map callCounts = new LinkedHashMap<>(); Map errorCounts = new LinkedHashMap<>(); for (DependencyLink link : in) { Pair parentChild = new Pair(link.parent(), link.child()); long callCount = callCounts.getOrDefault(parentChild, 0L); callCount += link.callCount(); callCounts.put(parentChild, callCount); long errorCount = errorCounts.containsKey(parentChild) ? errorCounts.get(parentChild) : 0L; errorCount += link.errorCount(); errorCounts.put(parentChild, errorCount); } return link(callCounts, errorCounts); } static List link(Map callCounts, Map errorCounts) { List result = new ArrayList<>(callCounts.size()); for (Map.Entry entry : callCounts.entrySet()) { Pair parentChild = entry.getKey(); result.add(DependencyLink.newBuilder() .parent(parentChild.left) .child(parentChild.right) .callCount(entry.getValue()) .errorCount(errorCounts.getOrDefault(parentChild, 0L)) .build()); } return result; } static final class Pair { final String left, right; Pair(String left, String right) { this.left = left; this.right = right; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Pair)) return false; Pair that = (DependencyLinker.Pair) o; return left.equals(that.left) && right.equals(that.right); } @Override public int hashCode() { int h$ = 1; h$ *= 1000003; h$ ^= left.hashCode(); h$ *= 1000003; h$ ^= right.hashCode(); return h$; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy