From 8f43a55e83352d14afb7907868d345271b09e2e4 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 17 Jul 2017 16:51:43 +0800 Subject: [PATCH] Drops internal DependencyLinkSpan for Span2 This drops the internal type of DependencyLinkSpan in favor of the Span2 type coming in #1499. Doing so now gives us practice, solves a few bugs along the way. When Span2 becomes non-internal, the change will be a simple package rename. --- ....java => DependencyLinkSpan2Iterator.java} | 74 +++-- .../zipkin/storage/mysql/MySQLSpanStore.java | 6 +- ...a => DependencyLinkSpan2IteratorTest.java} | 122 ++++----- .../zipkin/internal/DependencyLinkSpan.java | 245 ----------------- .../zipkin/internal/DependencyLinker.java | 163 +++++++---- .../src/main/java/zipkin/internal/Node.java | 24 +- .../internal/DependencyLinkSpanTest.java | 221 --------------- .../zipkin/internal/DependencyLinkerTest.java | 257 +++++++++++------- 8 files changed, 411 insertions(+), 701 deletions(-) rename zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/{DependencyLinkSpanIterator.java => DependencyLinkSpan2Iterator.java} (61%) rename zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/{DependencyLinkSpanIteratorTest.java => DependencyLinkSpan2IteratorTest.java} (57%) delete mode 100644 zipkin/src/main/java/zipkin/internal/DependencyLinkSpan.java delete mode 100644 zipkin/src/test/java/zipkin/internal/DependencyLinkSpanTest.java diff --git a/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkSpanIterator.java b/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkSpan2Iterator.java similarity index 61% rename from zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkSpanIterator.java rename to zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkSpan2Iterator.java index b759aa64fa4..6736a8eafc0 100644 --- a/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkSpanIterator.java +++ b/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkSpan2Iterator.java @@ -16,28 +16,32 @@ import java.util.Iterator; import org.jooq.Record; import org.jooq.TableField; -import zipkin.internal.DependencyLinkSpan; +import zipkin.Endpoint; import zipkin.internal.Nullable; import zipkin.internal.PeekingIterator; +import zipkin.internal.Span2; import zipkin.storage.mysql.internal.generated.tables.ZipkinSpans; import static zipkin.Constants.CLIENT_ADDR; import static zipkin.Constants.CLIENT_SEND; import static zipkin.Constants.SERVER_ADDR; import static zipkin.Constants.SERVER_RECV; +import static zipkin.internal.Util.equal; import static zipkin.storage.mysql.internal.generated.tables.ZipkinAnnotations.ZIPKIN_ANNOTATIONS; /** - * Convenience that lazy converts rows into {@linkplain DependencyLinkSpan} objects. + * Lazy converts rows into {@linkplain Span2} objects suitable for dependency links. This takes + * short-cuts to require less data. For example, it folds shared RPC spans into one, and doesn't + * include tags, non-core annotations or time units. * *

Out-of-date schemas may be missing the trace_id_high field. When present, this becomes {@link - * DependencyLinkSpan.TraceId#hi} used as the left-most 16 characters of the traceId in logging + * Span2#traceIdHigh()} used as the left-most 16 characters of the traceId in logging * statements. */ -final class DependencyLinkSpanIterator implements Iterator { +final class DependencyLinkSpan2Iterator implements Iterator { /** Assumes the input records are sorted by trace id, span id */ - static final class ByTraceId implements Iterator> { + static final class ByTraceId implements Iterator> { final PeekingIterator delegate; final boolean hasTraceIdHigh; @@ -53,10 +57,10 @@ static final class ByTraceId implements Iterator> { return delegate.hasNext(); } - @Override public Iterator next() { + @Override public Iterator next() { currentTraceIdHi = hasTraceIdHigh ? traceIdHigh(delegate) : null; currentTraceIdLo = delegate.peek().getValue(ZipkinSpans.ZIPKIN_SPANS.TRACE_ID); - return new DependencyLinkSpanIterator(delegate, currentTraceIdHi, currentTraceIdLo); + return new DependencyLinkSpan2Iterator(delegate, currentTraceIdHi, currentTraceIdLo); } @Override public void remove() { @@ -68,7 +72,7 @@ static final class ByTraceId implements Iterator> { @Nullable final Long traceIdHi; final long traceIdLo; - DependencyLinkSpanIterator(PeekingIterator delegate, Long traceIdHi, long traceIdLo) { + DependencyLinkSpan2Iterator(PeekingIterator delegate, Long traceIdHi, long traceIdLo) { this.delegate = delegate; this.traceIdHi = traceIdHi; this.traceIdLo = traceIdLo; @@ -83,17 +87,11 @@ public boolean hasNext() { } @Override - public DependencyLinkSpan next() { + public Span2 next() { Record row = delegate.peek(); long spanId = row.getValue(ZipkinSpans.ZIPKIN_SPANS.ID); - DependencyLinkSpan.Builder result = DependencyLinkSpan.builder( - traceIdHi != null ? traceIdHi : 0L, - traceIdLo, - row.getValue(ZipkinSpans.ZIPKIN_SPANS.PARENT_ID), - spanId - ); - + String srService = null, csService = null, caService = null, saService = null; while (hasNext()) { // there are more values for this trace if (spanId != delegate.peek().getValue(ZipkinSpans.ZIPKIN_SPANS.ID)) { break; // if we are in a new span @@ -105,18 +103,48 @@ public DependencyLinkSpan next() { if (key == null || value == null) continue; // neither client nor server switch (key) { case CLIENT_ADDR: - result.caService(value); + caService = value; break; case CLIENT_SEND: - result.csService(value); + csService = value; break; case SERVER_ADDR: - result.saService(value); + saService = value; break; case SERVER_RECV: - result.srService(value); + srService = value; } } + + // The client address is more authoritative than the client send owner. + if (caService == null) caService = csService; + + // Finagle labels two sides of the same socket ("ca", "sa") with the same name. + // Skip the client side, so it isn't mistaken for a loopback request + if (equal(saService, caService)) caService = null; + + Span2.Builder result = Span2.builder() + .traceIdHigh(traceIdHi != null ? traceIdHi : 0L) + .traceId(traceIdLo) + .parentId(row.getValue(ZipkinSpans.ZIPKIN_SPANS.PARENT_ID)) + .id(spanId); + + if (srService != null) { + return result.kind(Span2.Kind.SERVER) + .localEndpoint(ep(srService)) + .remoteEndpoint(ep(caService)) + .build(); + } else if (saService != null) { + return result + .kind(csService != null ? Span2.Kind.CLIENT : null) + .localEndpoint(ep(caService)) + .remoteEndpoint(ep(saService)) + .build(); + } else if (csService != null) { + return result.kind(Span2.Kind.SERVER) + .localEndpoint(ep(caService)) + .build(); + } return result.build(); } @@ -129,8 +157,12 @@ static long traceIdHigh(PeekingIterator delegate) { return delegate.peek().getValue(ZipkinSpans.ZIPKIN_SPANS.TRACE_ID_HIGH); } - static String emptyToNull(Record next, TableField field) { + static @Nullable String emptyToNull(Record next, TableField field) { String result = next.getValue(field); return result != null && !"".equals(result) ? result : null; } + + static Endpoint ep(@Nullable String serviceName) { + return serviceName != null ? Endpoint.builder().serviceName(serviceName).build() : null; + } } diff --git a/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/MySQLSpanStore.java b/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/MySQLSpanStore.java index 75c5480b657..f3c8b321e3b 100644 --- a/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/MySQLSpanStore.java +++ b/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/MySQLSpanStore.java @@ -40,11 +40,11 @@ import zipkin.DependencyLink; import zipkin.Endpoint; import zipkin.Span; -import zipkin.internal.DependencyLinkSpan; import zipkin.internal.DependencyLinker; import zipkin.internal.GroupByTraceId; import zipkin.internal.Nullable; import zipkin.internal.Pair; +import zipkin.internal.Span2; import zipkin.storage.QueryRequest; import zipkin.storage.SpanStore; import zipkin.storage.mysql.internal.generated.tables.ZipkinAnnotations; @@ -324,8 +324,8 @@ List aggregateDependencies(long endTs, @Nullable Long lookback, // Grouping so that later code knows when a span or trace is finished. .groupBy(schema.dependencyLinkGroupByFields).fetchLazy(); - Iterator> traces = - new DependencyLinkSpanIterator.ByTraceId(cursor.iterator(), schema.hasTraceIdHigh); + Iterator> traces = + new DependencyLinkSpan2Iterator.ByTraceId(cursor.iterator(), schema.hasTraceIdHigh); if (!traces.hasNext()) return Collections.emptyList(); diff --git a/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkSpanIteratorTest.java b/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkSpan2IteratorTest.java similarity index 57% rename from zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkSpanIteratorTest.java rename to zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkSpan2IteratorTest.java index 7bc7f8b2812..21f2d9cc544 100644 --- a/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkSpanIteratorTest.java +++ b/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkSpan2IteratorTest.java @@ -19,16 +19,15 @@ import org.jooq.impl.DSL; import org.junit.Test; import zipkin.Constants; -import zipkin.internal.DependencyLinkSpan; import zipkin.internal.PeekingIterator; +import zipkin.internal.Span2; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static zipkin.storage.mysql.internal.generated.tables.ZipkinAnnotations.ZIPKIN_ANNOTATIONS; import static zipkin.storage.mysql.internal.generated.tables.ZipkinSpans.ZIPKIN_SPANS; -// TODO: this class temporarily uses reflection until zipkin2 span replaces DependencyLinkSpan -public class DependencyLinkSpanIteratorTest { +public class DependencyLinkSpan2IteratorTest { Long traceIdHigh = null; long traceId = 1L; Long parentId = null; @@ -36,50 +35,50 @@ public class DependencyLinkSpanIteratorTest { /** You cannot make a dependency link unless you know the the local or peer endpoint. */ @Test public void whenNoServiceLabelsExist_kindIsUnknown() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "cs", null) ); - DependencyLinkSpan span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("UNKNOWN"); - assertThat(span).extracting("service").containsNull(); - assertThat(span).extracting("peerService").containsNull(); + Span2 span = iterator.next(); + assertThat(span.kind()).isNull(); + assertThat(span.localEndpoint()).isNull(); + assertThat(span.remoteEndpoint()).isNull(); } - @Test public void whenOnlyAddressLabelsExist_kindIsClient() { - DependencyLinkSpanIterator iterator = iterator( + @Test public void whenOnlyAddressLabelsExist_kindIsNull() { + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", "service1"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sa", "service2") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("CLIENT"); - assertThat(span).extracting("service").containsOnly("service1"); - assertThat(span).extracting("peerService").containsOnly("service2"); + assertThat(span.kind()).isNull(); + assertThat(span.localEndpoint().serviceName).isEqualTo("service1"); + assertThat(span.remoteEndpoint().serviceName).isEqualTo("service2"); } /** The linker is biased towards server spans, or client spans that know the peer localEndpoint(). */ @Test public void whenServerLabelsAreMissing_kindIsUnknownAndLabelsAreCleared() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", "service1") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("UNKNOWN"); - assertThat(span).extracting("service").containsNull(); - assertThat(span).extracting("peerService").containsNull(); + assertThat(span.kind()).isNull(); + assertThat(span.localEndpoint()).isNull(); + assertThat(span.remoteEndpoint()).isNull(); } /** {@link Constants#SERVER_RECV} is only applied when the local span is acting as a server */ @Test public void whenSrServiceExists_kindIsServer() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "sr", "service") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("SERVER"); - assertThat(span).extracting("service").containsOnly("service"); - assertThat(span).extracting("peerService").containsNull(); + assertThat(span.kind()).isEqualTo(Span2.Kind.SERVER); + assertThat(span.localEndpoint().serviceName).isEqualTo("service"); + assertThat(span.remoteEndpoint()).isNull(); } /** @@ -87,15 +86,15 @@ public class DependencyLinkSpanIteratorTest { * span */ @Test public void whenSrAndCaServiceExists_caIsThePeer() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", "service1"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sr", "service2") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("SERVER"); - assertThat(span).extracting("service").containsOnly("service2"); - assertThat(span).extracting("peerService").containsOnly("service1"); + assertThat(span.kind()).isEqualTo(Span2.Kind.SERVER); + assertThat(span.localEndpoint().serviceName).isEqualTo("service2"); + assertThat(span.remoteEndpoint().serviceName).isEqualTo("service1"); } /** @@ -103,57 +102,58 @@ public class DependencyLinkSpanIteratorTest { * span */ @Test public void whenSrAndCsServiceExists_caIsThePeer() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "cs", "service1"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sr", "service2") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("SERVER"); - assertThat(span).extracting("service").containsOnly("service2"); - assertThat(span).extracting("peerService").containsOnly("service1"); + assertThat(span.kind()).isEqualTo(Span2.Kind.SERVER); + assertThat(span.localEndpoint().serviceName).isEqualTo("service2"); + assertThat(span.remoteEndpoint().serviceName).isEqualTo("service1"); } /** {@link Constants#CLIENT_ADDR} is more authoritative than {@link Constants#CLIENT_SEND} */ @Test public void whenCrAndCaServiceExists_caIsThePeer() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "cs", "foo"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", "service1"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sr", "service2") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("SERVER"); - assertThat(span).extracting("service").containsOnly("service2"); - assertThat(span).extracting("peerService").containsOnly("service1"); + assertThat(span.kind()).isEqualTo(Span2.Kind.SERVER); + assertThat(span.localEndpoint().serviceName).isEqualTo("service2"); + assertThat(span.remoteEndpoint().serviceName).isEqualTo("service1"); } /** Finagle labels two sides of the same socket "ca", "sa" with the local endpoint name */ @Test public void specialCasesFinagleLocalSocketLabeling_client() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( + newRecord().values(traceIdHigh, traceId, parentId, spanId, "cs", "service"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", "service"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sa", "service") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); // When there's no "sr" annotation, we assume it is a client. - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("CLIENT"); - assertThat(span).extracting("service").containsNull(); - assertThat(span).extracting("peerService").containsOnly("service"); + assertThat(span.kind()).isEqualTo(Span2.Kind.CLIENT); + assertThat(span.localEndpoint()).isNull(); + assertThat(span.remoteEndpoint().serviceName).isEqualTo("service"); } @Test public void specialCasesFinagleLocalSocketLabeling_server() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", "service"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sa", "service"), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sr", "service") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); // When there is an "sr" annotation, we know it is a server - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("SERVER"); - assertThat(span).extracting("service").containsOnly("service"); - assertThat(span).extracting("peerService").containsNull(); + assertThat(span.kind()).isEqualTo(Span2.Kind.SERVER); + assertThat(span.localEndpoint().serviceName).isEqualTo("service"); + assertThat(span.remoteEndpoint()).isNull(); } /** @@ -161,33 +161,33 @@ public class DependencyLinkSpanIteratorTest { * caller, than a client span lacking its receiver. */ @Test public void csWithoutSaIsServer() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "cs", "service1") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("SERVER"); - assertThat(span).extracting("service").containsOnly("service1"); - assertThat(span).extracting("peerService").containsNull(); + assertThat(span.kind()).isEqualTo(Span2.Kind.SERVER); + assertThat(span.localEndpoint().serviceName).isEqualTo("service1"); + assertThat(span.remoteEndpoint()).isNull(); } /** Service links to empty string are confusing and offer no value. */ @Test public void emptyToNull() { - DependencyLinkSpanIterator iterator = iterator( + DependencyLinkSpan2Iterator iterator = iterator( newRecord().values(traceIdHigh, traceId, parentId, spanId, "ca", ""), newRecord().values(traceIdHigh, traceId, parentId, spanId, "cs", ""), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sa", ""), newRecord().values(traceIdHigh, traceId, parentId, spanId, "sr", "") ); - DependencyLinkSpan span = iterator.next(); + Span2 span = iterator.next(); - assertThat(span).extracting("kind").extracting(Object::toString).containsOnly("UNKNOWN"); - assertThat(span).extracting("service").containsNull(); - assertThat(span).extracting("peerService").containsNull(); + assertThat(span.kind()).isNull(); + assertThat(span.localEndpoint()).isNull(); + assertThat(span.remoteEndpoint()).isNull(); } - static DependencyLinkSpanIterator iterator(Record... records) { - return new DependencyLinkSpanIterator( + static DependencyLinkSpan2Iterator iterator(Record... records) { + return new DependencyLinkSpan2Iterator( new PeekingIterator<>(asList(records).iterator()), records[0].get(ZIPKIN_SPANS.TRACE_ID_HIGH), records[0].get(ZIPKIN_SPANS.TRACE_ID) ); diff --git a/zipkin/src/main/java/zipkin/internal/DependencyLinkSpan.java b/zipkin/src/main/java/zipkin/internal/DependencyLinkSpan.java deleted file mode 100644 index 1fc6bc887d2..00000000000 --- a/zipkin/src/main/java/zipkin/internal/DependencyLinkSpan.java +++ /dev/null @@ -1,245 +0,0 @@ -/** - * 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 zipkin.Annotation; -import zipkin.BinaryAnnotation; -import zipkin.Constants; -import zipkin.Span; - -import static zipkin.internal.Util.checkNotNull; -import static zipkin.internal.Util.equal; -import static zipkin.internal.Util.writeHexLong; - -/** - * Internal type used by {@link DependencyLinker linker} that holds the minimum state needed to - * aggregate {@link zipkin.DependencyLink dependency links}. - */ -// fields not exposed as public to further discourage use as a general type -public final class DependencyLinkSpan { - - /** Unique 8 or 16-byte identifier for a trace, set on all spans within it. */ - static final class TraceId { - - /** 0 may imply 8-byte identifiers are in use */ - final long hi; - final long lo; - - TraceId(long hi, long lo) { - this.hi = hi; - this.lo = lo; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (o instanceof TraceId) { - TraceId that = (TraceId) o; - return (this.hi == that.hi) - && (this.lo == that.lo); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (hi >>> 32) ^ hi; - h *= 1000003; - h ^= (lo >>> 32) ^ lo; - return h; - } - - /** Returns the hex representation of the span's trace ID */ - @Override public String toString() { - if (hi != 0) { - char[] result = new char[32]; - writeHexLong(result, 0, hi); - writeHexLong(result, 16, lo); - return new String(result); - } - char[] result = new char[16]; - writeHexLong(result, 0, lo); - return new String(result); - } - } - - /** - * Indicates the primary span type. - */ - enum Kind { - CLIENT, - /** The span includes a {@link zipkin.Constants#SERVER_RECV}. */ - SERVER, - UNKNOWN - } - - final TraceId traceId; - @Nullable - final Long parentId; - final long id; - final Kind kind; - @Nullable - final String service; - @Nullable - final String peerService; - - DependencyLinkSpan(TraceId traceId, Long parentId, long id, Kind kind, String service, - String peerService) { - this.traceId = traceId; - this.parentId = parentId; - this.id = id; - this.kind = checkNotNull(kind, "kind"); - this.service = service; - this.peerService = peerService; - } - - @Override public String toString() { - StringBuilder json = new StringBuilder(); - json.append("{\"traceId\": \"").append(Util.toLowerHex(traceId.hi, traceId.lo)).append('\"'); - if (parentId != null) { - json.append(", \"parentId\": \"").append(Util.toLowerHex(parentId)).append('\"'); - } - json.append(", \"id\": \"").append(Util.toLowerHex(id)).append('\"'); - json.append(", \"kind\": \"").append(kind).append('\"'); - if (service != null) json.append(", \"service\": \"").append(service).append('\"'); - if (peerService != null) json.append(", \"peerService\": \"").append(peerService).append('\"'); - return json.append("}").toString(); - } - - /** Only considers ID fields, as these spans are not expected to repeat */ - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (o instanceof DependencyLinkSpan) { - DependencyLinkSpan that = (DependencyLinkSpan) o; - return equal(this.traceId, that.traceId) - && equal(this.parentId, that.parentId) - && (this.id == that.id); - } - return false; - } - - /** Only considers ID fields, as these spans are not expected to repeat */ - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= traceId.hashCode(); - h *= 1000003; - h ^= (parentId == null) ? 0 : parentId.hashCode(); - h *= 1000003; - h ^= (id >>> 32) ^ id; - return h; - } - - public static Builder builder(long traceIdHigh, long traceIdLow, Long parentId, long spanId) { - return new Builder(new TraceId(traceIdHigh, traceIdLow), parentId, spanId); - } - - public static DependencyLinkSpan from(Span s) { - TraceId traceId = new TraceId(s.traceIdHigh, s.traceId); - DependencyLinkSpan.Builder linkSpan = new DependencyLinkSpan.Builder(traceId, s.parentId, s.id); - for (BinaryAnnotation a : s.binaryAnnotations) { - if (a.key.equals(Constants.CLIENT_ADDR) && a.endpoint != null) { - linkSpan.caService(a.endpoint.serviceName); - } else if (a.key.equals(Constants.SERVER_ADDR) && a.endpoint != null) { - linkSpan.saService(a.endpoint.serviceName); - } - } - for (Annotation a : s.annotations) { - if (a.value.equals(Constants.SERVER_RECV) && a.endpoint != null) { - linkSpan.srService(a.endpoint.serviceName); - } else if (a.value.equals(Constants.CLIENT_SEND) && a.endpoint != null) { - linkSpan.csService(a.endpoint.serviceName); - } - } - return linkSpan.build(); - } - - public static final class Builder { - final TraceId traceId; - final Long parentId; - final long spanId; - String srService; - String csService; - String caService; - String saService; - - Builder(TraceId traceId, Long parentId, long spanId) { - this.traceId = traceId; - this.spanId = spanId; - this.parentId = parentId; - } - - /** - * {@link zipkin.Constants#SERVER_RECV} is the preferred name of server, and this is a - * traditional span. - */ - public Builder srService(String srService) { - if ("".equals(srService)) srService = null; - this.srService = srService; - return this; - } - - /** - * {@link zipkin.Constants#CLIENT_SEND} is read to see calls into the root span from - * instrumented clients. - */ - public Builder csService(String csService) { - if ("".equals(csService)) csService = null; - this.csService = csService; - return this; - } - - /** - * {@link zipkin.Constants#CLIENT_ADDR} is read to see calls into the root span from - * uninstrumented clients. - */ - public Builder caService(String caService) { - if ("".equals(caService)) caService = null; - this.caService = caService; - return this; - } - - /** - * {@link zipkin.Constants#SERVER_ADDR} is only read at the leaf, when a client calls an - * un-instrumented server. - */ - public Builder saService(String saService) { - if ("".equals(saService)) saService = null; - this.saService = saService; - return this; - } - - public DependencyLinkSpan build() { - // The client address is more authoritative than the client send owner. - if (caService == null) caService = csService; - - // Finagle labels two sides of the same socket ("ca", "sa") with the same name. - // Skip the client side, so it isn't mistaken for a loopback request - if (equal(saService, caService)) caService = null; - - if (srService != null) { - return new DependencyLinkSpan(traceId, parentId, spanId, Kind.SERVER, srService, caService); - } else if (saService != null) { - return new DependencyLinkSpan(traceId, parentId, spanId, Kind.CLIENT, caService, saService); - } else if (csService != null) { - return new DependencyLinkSpan(traceId, parentId, spanId, Kind.SERVER, caService, null); - } - return new DependencyLinkSpan(traceId, parentId, spanId, Kind.UNKNOWN, null, null); - } - } -} diff --git a/zipkin/src/main/java/zipkin/internal/DependencyLinker.java b/zipkin/src/main/java/zipkin/internal/DependencyLinker.java index 3748065ce07..abd9f067d7b 100644 --- a/zipkin/src/main/java/zipkin/internal/DependencyLinker.java +++ b/zipkin/src/main/java/zipkin/internal/DependencyLinker.java @@ -23,17 +23,18 @@ import java.util.logging.Logger; import zipkin.DependencyLink; import zipkin.Span; +import zipkin.internal.Span2.Kind; import static java.util.logging.Level.FINE; -import static zipkin.internal.Util.checkNotNull; /** * 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 - * 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. + *

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 Span2#remoteEndpoint()} are included, as this accounts for + * uninstrumented services. Spans with {@link Span2#kind()} unset, but {@link + * Span2#remoteEndpoint()} set are treated the same as client spans. */ public final class DependencyLinker { private final Logger logger; @@ -53,34 +54,52 @@ public DependencyLinker() { public DependencyLinker putTrace(Collection spans) { if (spans.isEmpty()) return this; - List linkSpans = new LinkedList<>(); + List linkSpans = new LinkedList<>(); for (Span s : MergeById.apply(spans)) { - linkSpans.add(DependencyLinkSpan.from(s)); + linkSpans.addAll(Span2Converter.fromSpan(s)); } return putTrace(linkSpans.iterator()); } + static final Node.MergeFunction MERGE_RPC = new Node.MergeFunction() { + @Override public Span2 merge(Span2 existing, Span2 update) { + if (existing == null) return update; + if (update == null) return existing; + if (existing.kind() == null) return update; + if (update.kind() == null) return existing; + Span2 server = existing.kind() == Kind.SERVER ? existing : update; + Span2 client = existing == server ? update : existing; + if (server.remoteEndpoint() != null && !"".equals(server.remoteEndpoint().serviceName)) { + return server; + } + return server.toBuilder().remoteEndpoint(client.localEndpoint()).build(); + } + + @Override public String toString() { + return "MergeRpc"; + } + }; + /** * @param spans spans where all spans have the same trace id */ - public DependencyLinker putTrace(Iterator spans) { + public DependencyLinker putTrace(Iterator spans) { if (!spans.hasNext()) return this; - DependencyLinkSpan first = spans.next(); - Node.TreeBuilder builder = - new Node.TreeBuilder<>(logger, first.traceId.toString()); - builder.addNode(first.parentId, first.id, first); - + Span2 first = spans.next(); + Node.TreeBuilder builder = + new Node.TreeBuilder<>(logger, MERGE_RPC, first.traceIdString()); + builder.addNode(first.parentId(), first.id(), first); while (spans.hasNext()) { - DependencyLinkSpan next = spans.next(); - builder.addNode(next.parentId, next.id, next); + Span2 next = spans.next(); + builder.addNode(next.parentId(), next.id(), next); } - Node tree = builder.build(); + Node tree = builder.build(); if (logger.isLoggable(FINE)) logger.fine("traversing trace tree, breadth-first"); - for (Iterator> i = tree.traverse(); i.hasNext(); ) { - Node current = i.next(); - DependencyLinkSpan currentSpan = current.value(); + for (Iterator> i = tree.traverse(); i.hasNext(); ) { + Node current = i.next(); + Span2 currentSpan = current.value(); if (logger.isLoggable(FINE)) { logger.fine("processing " + currentSpan); } @@ -88,12 +107,31 @@ public DependencyLinker putTrace(Iterator spans) { logger.fine("skipping synthetic node for broken span tree"); continue; } + + Kind kind = currentSpan.kind(); + if (Kind.CLIENT.equals(kind) && !current.children().isEmpty()) { + logger.fine("deferring link to rpc child span"); + continue; + } + + String serviceName = serviceName(currentSpan); + String remoteServiceName = remoteServiceName(currentSpan); + 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-rpc span; skipping"); + continue; + } + } + String child; String parent; - switch (currentSpan.kind) { + switch (kind) { case SERVER: - child = currentSpan.service; - parent = currentSpan.peerService; + child = serviceName; + parent = remoteServiceName; if (current == tree) { // we are the root-most span. if (parent == null) { logger.fine("root's peer is unknown; skipping"); @@ -102,11 +140,11 @@ public DependencyLinker putTrace(Iterator spans) { } break; case CLIENT: - child = currentSpan.peerService; - parent = currentSpan.service; + parent = serviceName; + child = remoteServiceName; break; default: - logger.fine("non-rpc span; skipping"); + logger.fine("unknown kind; skipping"); continue; } @@ -114,39 +152,60 @@ public DependencyLinker putTrace(Iterator spans) { 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 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; + String rpcAncestor = findRpcAncestor(current); + if (rpcAncestor != null) { + + // Local spans may be between the current node and its remote parent + if (parent == null) parent = rpcAncestor; + + // 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.CLIENT.equals(kind) && serviceName != null && !rpcAncestor.equals(serviceName)) { + logger.fine("detected missing link to client span"); + addLink(rpcAncestor, serviceName); + continue; } - 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 key = Pair.create(parent, child); - if (linkMap.containsKey(key)) { - linkMap.put(key, linkMap.get(key) + 1); - } else { - linkMap.put(key, 1L); - } + addLink(parent, child); } return this; } + String findRpcAncestor(Node current) { + Node ancestor = current.parent(); + while (ancestor != null) { + if (logger.isLoggable(FINE)) { + logger.fine("processing ancestor " + ancestor.value()); + } + if (!ancestor.isSyntheticRootForPartialTree()) { + Span2 maybeRemote = ancestor.value(); + if (maybeRemote.kind() != null) { + return serviceName(maybeRemote); + } + } + ancestor = ancestor.parent(); + } + return null; + } + + void addLink(String parent, String child) { + if (logger.isLoggable(FINE)) { + logger.fine("incrementing link " + parent + " -> " + child); + } + Pair key = Pair.create(parent, child); + if (linkMap.containsKey(key)) { + linkMap.put(key, linkMap.get(key) + 1); + } else { + linkMap.put(key, 1L); + } + } + public List link() { // links are merged by mapping to parent/child and summing corresponding links List result = new ArrayList<>(linkMap.size()); @@ -173,4 +232,16 @@ public static List merge(Iterable in) { } return result; } + + static String serviceName(Span2 span) { + return span.localEndpoint() != null && !"".equals(span.localEndpoint().serviceName) + ? span.localEndpoint().serviceName + : null; + } + + static String remoteServiceName(Span2 span) { + return span.remoteEndpoint() != null && !"".equals(span.remoteEndpoint().serviceName) + ? span.remoteEndpoint().serviceName + : null; + } } diff --git a/zipkin/src/main/java/zipkin/internal/Node.java b/zipkin/src/main/java/zipkin/internal/Node.java index 5b8b4b97eb0..48ef740e5a6 100644 --- a/zipkin/src/main/java/zipkin/internal/Node.java +++ b/zipkin/src/main/java/zipkin/internal/Node.java @@ -109,6 +109,16 @@ public void remove() { } } + interface MergeFunction { + V merge(@Nullable V existing, @Nullable V update); + } + + static final MergeFunction FIRST_NOT_NULL = new MergeFunction() { + @Override public Object merge(Object existing, Object update) { + return existing != null ? existing : update; + } + }; + /** * Some operations do not require the entire span object. This creates a tree given (parent id, * id) pairs. @@ -117,16 +127,21 @@ public void remove() { */ static final class TreeBuilder { final Logger logger; + final MergeFunction mergeFunction; final String traceId; TreeBuilder(Logger logger, String traceId) { + this(logger, FIRST_NOT_NULL, traceId); + } + + TreeBuilder(Logger logger, MergeFunction mergeFunction, String traceId) { this.logger = logger; + this.mergeFunction = mergeFunction; this.traceId = traceId; } - Node rootNode = null; Long rootId = null; - + Node rootNode = null; // Nodes representing the trace tree Map> idToNode = new LinkedHashMap<>(); // Collect the parent-child relationships between all spans. @@ -158,8 +173,11 @@ public boolean addNode(@Nullable Long parentId, long id, V value) { if (parentId == null && rootNode == null) { rootNode = node; rootId = id; + } else if (parentId == null && rootId == id) { + rootNode.value(mergeFunction.merge(rootNode.value, node.value)); } else { - idToNode.put(id, node); + Node previous = idToNode.put(id, node); + if (previous != null) node.value(mergeFunction.merge(previous.value, node.value)); idToParent.put(id, parentId); } return true; diff --git a/zipkin/src/test/java/zipkin/internal/DependencyLinkSpanTest.java b/zipkin/src/test/java/zipkin/internal/DependencyLinkSpanTest.java deleted file mode 100644 index d8a1f702055..00000000000 --- a/zipkin/src/test/java/zipkin/internal/DependencyLinkSpanTest.java +++ /dev/null @@ -1,221 +0,0 @@ -/** - * 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 org.junit.Test; -import zipkin.Constants; -import zipkin.internal.DependencyLinkSpan.Kind; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DependencyLinkSpanTest { - - @Test - public void testToString() { - assertThat(DependencyLinkSpan.builder(0L, 1L, null, 1L).build()) - .hasToString( - "{\"traceId\": \"0000000000000001\", \"id\": \"0000000000000001\", \"kind\": \"UNKNOWN\"}"); - - assertThat(DependencyLinkSpan.builder(0L, 1L, 1L, 2L).build()) - .hasToString( - "{\"traceId\": \"0000000000000001\", \"parentId\": \"0000000000000001\", \"id\": \"0000000000000002\", \"kind\": \"UNKNOWN\"}"); - - assertThat(DependencyLinkSpan.builder(0L, 1L, 1L, 2L) - .srService("processor") - .caService("kinesis").build()) - .hasToString( - "{\"traceId\": \"0000000000000001\", \"parentId\": \"0000000000000001\", \"id\": \"0000000000000002\", \"kind\": \"SERVER\", \"service\": \"processor\", \"peerService\": \"kinesis\"}"); - - // It is invalid to log "ca" without "sr", so marked as unknown - assertThat(DependencyLinkSpan.builder(0L, 1L, 1L, 2L) - .caService("kinesis").build()) - .hasToString( - "{\"traceId\": \"0000000000000001\", \"parentId\": \"0000000000000001\", \"id\": \"0000000000000002\", \"kind\": \"UNKNOWN\"}"); - - assertThat(DependencyLinkSpan.builder(0L, 1L, 1L, 2L) - .saService("mysql").build()) - .hasToString( - "{\"traceId\": \"0000000000000001\", \"parentId\": \"0000000000000001\", \"id\": \"0000000000000002\", \"kind\": \"CLIENT\", \"peerService\": \"mysql\"}"); - - // arbitrary 2-sided span - assertThat(DependencyLinkSpan.builder(0L, 1L, 1L, 2L) - .caService("shell-script") - .saService("mysql").build()) - .hasToString( - "{\"traceId\": \"0000000000000001\", \"parentId\": \"0000000000000001\", \"id\": \"0000000000000002\", \"kind\": \"CLIENT\", \"service\": \"shell-script\", \"peerService\": \"mysql\"}"); - - // 128-bit trace ID - assertThat(DependencyLinkSpan.builder(3L, 1L, null, 1L).build()) - .hasToString( - "{\"traceId\": \"00000000000000030000000000000001\", \"id\": \"0000000000000001\", \"kind\": \"UNKNOWN\"}"); - } - - @Test - public void parentAndChildApply() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L).build(); - assertThat(span.parentId).isNull(); - assertThat(span.id).isEqualTo(1L); - - span = DependencyLinkSpan.builder(0L, 1L, 1L, 2L).build(); - assertThat(span.parentId).isEqualTo(1L); - assertThat(span.id).isEqualTo(2L); - } - - /** You cannot make a dependency link unless you know the the local or peer service. */ - @Test - public void whenNoServiceLabelsExist_kindIsUnknown() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L).build(); - - assertThat(span.kind).isEqualTo(Kind.UNKNOWN); - assertThat(span.peerService).isNull(); - assertThat(span.service).isNull(); - } - - @Test - public void whenOnlyAddressLabelsExist_kindIsClient() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .caService("service1") - .saService("service2") - .build(); - - assertThat(span.kind).isEqualTo(Kind.CLIENT); - assertThat(span.service).isEqualTo("service1"); - assertThat(span.peerService).isEqualTo("service2"); - } - - /** The linker is biased towards server spans, or client spans that know the peer service. */ - @Test - public void whenServerLabelsAreMissing_kindIsUnknownAndLabelsAreCleared() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .caService("service1") - .build(); - - assertThat(span.kind).isEqualTo(Kind.UNKNOWN); - assertThat(span.service).isNull(); - assertThat(span.peerService).isNull(); - } - - /** {@link Constants#SERVER_RECV} is only applied when the local span is acting as a server */ - @Test - public void whenSrServiceExists_kindIsServer() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .srService("service") - .build(); - - assertThat(span.kind).isEqualTo(Kind.SERVER); - assertThat(span.service).isEqualTo("service"); - assertThat(span.peerService).isNull(); - } - - /** - * {@link Constants#CLIENT_ADDR} indicates the peer, which is a client in the case of a server - * span - */ - @Test - public void whenSrAndCaServiceExists_caIsThePeer() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .caService("service1") - .srService("service2") - .build(); - - assertThat(span.kind).isEqualTo(Kind.SERVER); - assertThat(span.service).isEqualTo("service2"); - assertThat(span.peerService).isEqualTo("service1"); - } - - /** - * {@link Constants#CLIENT_SEND} indicates the peer, which is a client in the case of a server - * span - */ - @Test - public void whenSrAndCsServiceExists_caIsThePeer() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .csService("service1") - .srService("service2") - .build(); - - assertThat(span.kind).isEqualTo(Kind.SERVER); - assertThat(span.service).isEqualTo("service2"); - assertThat(span.peerService).isEqualTo("service1"); - } - - /** {@link Constants#CLIENT_ADDR} is more authoritative than {@link Constants#CLIENT_SEND} */ - @Test - public void whenCrAndCaServiceExists_caIsThePeer() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .csService("foo") - .caService("service1") - .srService("service2") - .build(); - - assertThat(span.kind).isEqualTo(Kind.SERVER); - assertThat(span.service).isEqualTo("service2"); - assertThat(span.peerService).isEqualTo("service1"); - } - - @Test - public void specialCasesFinagleLocalSocketLabeling() { - // Finagle labels two sides of the same socket ("ca", "sa") with the local service name. - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .caService("service") - .saService("service") - .build(); - - // When there's no "sr" annotation, we assume it is a client. - assertThat(span.kind).isEqualTo(Kind.CLIENT); - assertThat(span.service).isNull(); - assertThat(span.peerService).isEqualTo("service"); - - span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .srService("service") - .caService("service") - .saService("service") - .build(); - - // When there is an "sr" annotation, we know it is a server - assertThat(span.kind).isEqualTo(Kind.SERVER); - assertThat(span.service).isEqualTo("service"); - assertThat(span.peerService).isNull(); - } - - /** - *

Dependency linker works backwards: it is easier to treat a "cs" as a server span lacking its - * caller, than a client span lacking its receiver. - */ - @Test - public void csWithoutSaIsServer() { - DependencyLinkSpan span = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .csService("service1") - .build(); - - assertThat(span.kind).isEqualTo(Kind.SERVER); - assertThat(span.service).isEqualTo("service1"); - assertThat(span.peerService).isNull(); - } - - /** Service links to empty string are confusing and offer no value. */ - @Test - public void emptyToNull() { - DependencyLinkSpan.Builder builder = DependencyLinkSpan.builder(0L, 1L, null, 1L) - .caService("") - .csService("") - .saService("") - .srService(""); - - assertThat(builder.caService).isNull(); - assertThat(builder.csService).isNull(); - assertThat(builder.saService).isNull(); - assertThat(builder.srService).isNull(); - } -} diff --git a/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java b/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java index 362d6a63bda..c38181aa406 100644 --- a/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java +++ b/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java @@ -20,10 +20,10 @@ import java.util.stream.Collectors; import org.junit.Test; import zipkin.DependencyLink; +import zipkin.Endpoint; import zipkin.Span; import zipkin.TestObjects; -import zipkin.internal.DependencyLinkSpan.Kind; -import zipkin.internal.DependencyLinkSpan.TraceId; +import zipkin.internal.Span2.Kind; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -50,8 +50,8 @@ public void baseCase() { @Test public void linksSpans() { assertThat(new DependencyLinker().putTrace(TestObjects.TRACE).link()).containsExactly( - DependencyLink.create("web", "app", 1L), - DependencyLink.create("app", "db", 1L) + DependencyLink.create("web", "app", 1L), + DependencyLink.create("app", "db", 1L) ); } @@ -69,71 +69,48 @@ public void dropsSelfReferencingSpans() { ); } - /** - * The linker links a directed graph, if the span kind is unknown, we don't know the direction to - * link. - */ - @Test - public void doesntLinkUnknownRootSpans() { - List unknownRootSpans = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.UNKNOWN, null, null), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.UNKNOWN, "server", "client"), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.UNKNOWN, "client", "server") - ); - - for (DependencyLinkSpan span : unknownRootSpans) { - assertThat(new DependencyLinker(logger) - .putTrace(asList(span).iterator()).link()) - .isEmpty(); - } - - assertThat(messages).contains( - "non-rpc span; skipping" - ); - } - /** * A root span can be a client-originated trace or a server receipt which knows its peer. In these * cases, the peer is known and kind establishes the direction. */ @Test public void linksSpansDirectedByKind() { - List validRootSpans = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "server", "client"), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.CLIENT, "client", "server") + List validRootSpans = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "server", "client"), + span2(0L, 1L, null, 1L, Kind.CLIENT, "client", "server") ); - for (DependencyLinkSpan span : validRootSpans) { + for (Span2 span : validRootSpans) { assertThat(new DependencyLinker() - .putTrace(asList(span).iterator()).link()) - .containsOnly(DependencyLink.create("client", "server", 1L)); + .putTrace(asList(span).iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 1L)); } } @Test public void callsAgainstTheSameLinkIncreasesCallCount_span() { - List trace = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "client", null), - new DependencyLinkSpan(new TraceId(0L, 1L), 1L, 2L, Kind.CLIENT, null, "server"), - new DependencyLinkSpan(new TraceId(0L, 1L), 1L, 3L, Kind.CLIENT, null, "server") + List trace = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "client", null), + span2(0L, 1L, 1L, 2L, Kind.CLIENT, null, "server"), + span2(0L, 1L, 1L, 3L, Kind.CLIENT, null, "server") ); assertThat(new DependencyLinker() - .putTrace(trace.iterator()).link()) - .containsOnly(DependencyLink.create("client", "server", 2L)); + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 2L)); } @Test public void callsAgainstTheSameLinkIncreasesCallCount_trace() { - List trace = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "client", null), - new DependencyLinkSpan(new TraceId(0L, 1L), 1L, 2L, Kind.CLIENT, null, "server") + List trace = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "client", null), + span2(0L, 1L, 1L, 2L, Kind.CLIENT, null, "server") ); assertThat(new DependencyLinker() - .putTrace(trace.iterator()) - .putTrace(trace.iterator()).link()) - .containsOnly(DependencyLink.create("client", "server", 2L)); + .putTrace(trace.iterator()) + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 2L)); } /** @@ -142,22 +119,57 @@ public void callsAgainstTheSameLinkIncreasesCallCount_trace() { */ @Test public void singleHostSpansResultInASingleCallCount() { - List> singleLinks = asList( - asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.CLIENT, "client", "server"), - new DependencyLinkSpan(new TraceId(0L, 1L), 1L, 2L, Kind.SERVER, "server", null) - ), - asList( - new DependencyLinkSpan(new TraceId(0L, 3L), null, 3L, Kind.SERVER, "client", null), - new DependencyLinkSpan(new TraceId(0L, 3L), 3L, 4L, Kind.CLIENT, "client", "server") - ) + List trace = asList( + span2(0L, 3L, null, 3L, Kind.CLIENT, "client", null), + span2(0L, 3L, 3L, 4L, Kind.SERVER, "server", "client") ); - for (List trace : singleLinks) { - assertThat(new DependencyLinker() - .putTrace(trace.iterator()).link()) - .containsOnly(DependencyLink.create("client", "server", 1L)); - } + assertThat(new DependencyLinker() + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 1L)); + } + + @Test + public void singleHostSpansResultInASingleCallCount_defersNameToServer() { + List trace = asList( + span2(0L, 1L, null, 1L, Kind.CLIENT, "client", "server"), + span2(0L, 1L, 1L, 2L, Kind.SERVER, "server", null) + ); + + assertThat(new DependencyLinker(logger) + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 1L)); + + assertThat(messages).contains("deferring link to rpc child span"); + messages.clear(); + } + + @Test + public void singleHostSpans_multipleChildren() { + List trace = asList( + span2(0L, 4L, null, 4L, Kind.CLIENT, "client", null), + span2(0L, 4L, 4L, 5L, Kind.SERVER, "server", "client"), + span2(0L, 4L, 4L, 6L, Kind.SERVER, "server", "client") + ); + + assertThat(new DependencyLinker() + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 2L)); + } + + @Test + public void singleHostSpans_multipleChildren_defersNameToServer() { + List trace = asList( + span2(0L, 1L, null, 1L, Kind.CLIENT, "client", "server"), + span2(0L, 1L, 1L, 2L, Kind.SERVER, "server", null), + span2(0L, 1L, 1L, 3L, Kind.SERVER, "server", null) + ); + + assertThat(new DependencyLinker(logger) + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 2L)); + + assertThat(messages).contains("deferring link to rpc child span"); } /** @@ -166,67 +178,82 @@ public void singleHostSpansResultInASingleCallCount() { */ @Test public void intermediatedClientSpansMissingLocalServiceNameLinkToNearestServer() { - List trace = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "client", null), - new DependencyLinkSpan(new TraceId(0L, 1L), 1L, 2L, Kind.UNKNOWN, null, null), - // possibly a local fan-out span - new DependencyLinkSpan(new TraceId(0L, 1L), 2L, 3L, Kind.CLIENT, null, "server"), - new DependencyLinkSpan(new TraceId(0L, 1L), 2L, 4L, Kind.CLIENT, null, "server") + List trace = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "client", null), + span2(0L, 1L, 1L, 2L, null, null, null), + // possibly a local fan-out span + span2(0L, 1L, 2L, 3L, Kind.CLIENT, null, "server"), + span2(0L, 1L, 2L, 4L, Kind.CLIENT, null, "server") ); assertThat(new DependencyLinker() - .putTrace(trace.iterator()).link()) - .containsOnly(DependencyLink.create("client", "server", 2L)); + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("client", "server", 2L)); } /** A loopback span is direction-agnostic, so can be linked properly regardless of kind. */ @Test public void linksLoopbackSpans() { - List validRootSpans = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "service", "service"), - new DependencyLinkSpan(new TraceId(0L, 2L), null, 2L, Kind.CLIENT, "service", "service") + List validRootSpans = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "service", "service"), + span2(0L, 2L, null, 2L, Kind.CLIENT, "service", "service") ); - for (DependencyLinkSpan span : validRootSpans) { + for (Span2 span : validRootSpans) { assertThat(new DependencyLinker() - .putTrace(asList(span).iterator()).link()) - .containsOnly(DependencyLink.create("service", "service", 1L)); + .putTrace(asList(span).iterator()).link()) + .containsOnly(DependencyLink.create("service", "service", 1L)); } } + @Test + public void noSpanKindTreatedSameAsClient() { + List trace = asList( + span2(0L, 1L, null, 1L, null, "some-client", "web"), + span2(0L, 1L, 1L, 2L, null, "web", "app"), + span2(0L, 1L, 2L, 3L, null, "app", "db") + ); + + assertThat(new DependencyLinker().putTrace(trace.iterator()).link()).containsOnly( + DependencyLink.create("some-client", "web", 1L), + DependencyLink.create("web", "app", 1L), + DependencyLink.create("app", "db", 1L) + ); + } + /** * A dependency link is between two services. Given only one span, we cannot link if we don't know * both service names. */ @Test public void cannotLinkSingleSpanWithoutBothServiceNames() { - List incompleteRootSpans = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, null, null), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "server", null), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, null, "client"), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.CLIENT, null, null), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.CLIENT, "client", null), - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.CLIENT, null, "server") + List incompleteRootSpans = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, null, null), + span2(0L, 1L, null, 1L, Kind.SERVER, "server", null), + span2(0L, 1L, null, 1L, Kind.SERVER, null, "client"), + span2(0L, 1L, null, 1L, Kind.CLIENT, null, null), + span2(0L, 1L, null, 1L, Kind.CLIENT, "client", null), + span2(0L, 1L, null, 1L, Kind.CLIENT, null, "server") ); - for (DependencyLinkSpan span : incompleteRootSpans) { + for (Span2 span : incompleteRootSpans) { assertThat(new DependencyLinker(logger) - .putTrace(asList(span).iterator()).link()) - .isEmpty(); + .putTrace(asList(span).iterator()).link()) + .isEmpty(); } } @Test public void doesntLinkUnrelatedSpansWhenMissingRootSpan() { long missingParentId = 1; - List trace = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), missingParentId, 2L, Kind.SERVER, "service1", null), - new DependencyLinkSpan(new TraceId(0L, 1L), missingParentId, 3L, Kind.SERVER, "service2", null) + List trace = asList( + span2(0L, 1L, missingParentId, 2L, Kind.SERVER, "service1", null), + span2(0L, 1L, missingParentId, 3L, Kind.SERVER, "service2", null) ); assertThat(new DependencyLinker(logger) - .putTrace(trace.iterator()).link()) - .isEmpty(); + .putTrace(trace.iterator()).link()) + .isEmpty(); assertThat(messages).contains( "skipping synthetic node for broken span tree" @@ -236,14 +263,14 @@ public void doesntLinkUnrelatedSpansWhenMissingRootSpan() { @Test public void linksRelatedSpansWhenMissingRootSpan() { long missingParentId = 1; - List trace = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), missingParentId, 2L, Kind.SERVER, "service1", null), - new DependencyLinkSpan(new TraceId(0L, 1L), 2L, 3L, Kind.SERVER, "service2", null) + List trace = asList( + span2(0L, 1L, missingParentId, 2L, Kind.SERVER, "service1", null), + span2(0L, 1L, 2L, 3L, Kind.SERVER, "service2", null) ); assertThat(new DependencyLinker(logger) - .putTrace(trace.iterator()).link()) - .containsOnly(DependencyLink.create("service1", "service2", 1L)); + .putTrace(trace.iterator()).link()) + .containsOnly(DependencyLink.create("service1", "service2", 1L)); assertThat(messages).contains( "skipping synthetic node for broken span tree" @@ -253,9 +280,9 @@ public void linksRelatedSpansWhenMissingRootSpan() { /** Client+Server spans that don't share IDs are treated as server spans missing their peer */ @Test public void linksSingleHostSpans() { - List singleHostSpans = asList( - new DependencyLinkSpan(new TraceId(0L, 1L), null, 1L, Kind.SERVER, "web", null), - new DependencyLinkSpan(new TraceId(0L, 1L), 1L, 2L, Kind.SERVER, "app", null) + List singleHostSpans = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "web", null), + span2(0L, 1L, 1L, 2L, Kind.SERVER, "app", null) ); assertThat(new DependencyLinker() @@ -263,17 +290,45 @@ public void linksSingleHostSpans() { .containsOnly(DependencyLink.create("web", "app", 1L)); } + /** Creates a link when there's a span missing, in this case 2L which is an RPC from web to app */ + @Test + public void missingSpan() { + List singleHostSpans = asList( + span2(0L, 1L, null, 1L, Kind.SERVER, "web", null), + span2(0L, 1L, 1L, 2L, Kind.CLIENT, "app", null) + ); + + assertThat(new DependencyLinker(logger) + .putTrace(singleHostSpans.iterator()).link()) + .containsOnly(DependencyLink.create("web", "app", 1L)); + + assertThat(messages).contains( + "detected missing link to client span" + ); + } + @Test public void merge() { List links = asList( - DependencyLink.create("client", "server", 2L), - DependencyLink.create("client", "server", 2L), - DependencyLink.create("client", "client", 1L) + DependencyLink.create("client", "server", 2L), + DependencyLink.create("client", "server", 2L), + DependencyLink.create("client", "client", 1L) ); assertThat(DependencyLinker.merge(links)).containsExactly( - DependencyLink.create("client", "server", 4L), - DependencyLink.create("client", "client", 1L) + DependencyLink.create("client", "server", 4L), + DependencyLink.create("client", "client", 1L) ); } + + static Span2 span2(long traceIdHigh, long traceId, @Nullable Long parentId, long id, + @Nullable Kind kind, + @Nullable String local, @Nullable String remote) { + Span2.Builder result = Span2.builder(); + result.traceIdHigh(traceIdHigh).traceId(traceId).parentId(parentId).id(id); + result.kind(kind); + if (local != null) result.localEndpoint(Endpoint.builder().serviceName(local).build()); + if (remote != null) result.remoteEndpoint(Endpoint.builder().serviceName(remote).build()); + return result.build(); + } }