diff --git a/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/Span.java b/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/Span.java index 2c51f48c2..e256f73f3 100644 --- a/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/Span.java +++ b/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/Span.java @@ -83,7 +83,7 @@ public class Span { * means no parent span if this {@link TraceSegment}. * @param operationName {@link #operationName} */ - private Span(int spanId, int parentSpanId, String operationName) { + protected Span(int spanId, int parentSpanId, String operationName) { this(spanId, parentSpanId, operationName, System.currentTimeMillis()); } @@ -97,7 +97,7 @@ public class Span { * @param operationName {@link #operationName} * @param startTime given start timestamp. */ - private Span(int spanId, int parentSpanId, String operationName, long startTime) { + protected Span(int spanId, int parentSpanId, String operationName, long startTime) { this(); this.spanId = spanId; this.parentSpanId = parentSpanId; @@ -200,17 +200,17 @@ public class Span { * * @return this Span instance, for chaining */ - public final Span setTag(String key, String value) { + public Span setTag(String key, String value) { tagsWithStr.put(key, value); return this; } - public final Span setTag(String key, boolean value) { + public Span setTag(String key, boolean value) { tagsWithBool.put(key, value); return this; } - public final Span setTag(String key, Integer value) { + public Span setTag(String key, Integer value) { tagsWithInt.put(key, value); return this; } @@ -359,6 +359,10 @@ public class Span { return Collections.unmodifiableList(logs); } + public boolean isLeaf() { + return false; + } + @Override public String toString() { return "Span{" + diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java index 32166f61a..2371f07de 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/ContextManager.java @@ -55,11 +55,19 @@ public class ContextManager implements TracerContextListener, BootService { } public static Span createSpan(String operationName) { - return get().createSpan(operationName); + return get().createSpan(operationName, false); } public static Span createSpan(String operationName, long startTime) { - return get().createSpan(operationName, startTime); + return get().createSpan(operationName, startTime, false); + } + + public static Span createLeafSpan(String operationName) { + return get().createSpan(operationName, true); + } + + public static Span createLeafSpan(String operationName, long startTime) { + return get().createSpan(operationName, startTime, true); } public static Span activeSpan() { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/LeafSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/LeafSpan.java new file mode 100644 index 000000000..a527b68ab --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/LeafSpan.java @@ -0,0 +1,108 @@ +package org.skywalking.apm.agent.core.context; + +import org.skywalking.apm.trace.Span; +import org.skywalking.apm.trace.TraceSegment; + +/** + * LeafSpan is a special type of {@link Span} + * + * In rpc-client tracing scenario, one component could constitute by many other rpc-client. + * e.g Feign constitutes by okhttp, apache httpclient, etc. + * + * By having leaf concept, no need so many spans for single rpc call. + * + * @author wusheng + */ +public class LeafSpan extends Span { + private int stackDepth = 0; + + /** + * Create a new span, by given span id and give startTime but no parent span id, + * No parent span id means that, this Span is the first span of the {@link TraceSegment} + * + * @param spanId given by the creator, and must be unique id in the {@link TraceSegment} + * @param operationName {@link #operationName} + * @param startTime given start time of span + */ + public LeafSpan(int spanId, String operationName, long startTime) { + super(spanId, -1, operationName, startTime); + } + + /** + * Create a new span, by given span id, parent span, operationName and startTime. + * This span must belong a {@link TraceSegment}, also is a part of Distributed Trace. + * + * @param spanId given by the creator, and must be unique id in the {@link TraceSegment} + * @param parentSpan {@link Span} + * @param operationName {@link #operationName} + * @param startTime given start timestamp + */ + public LeafSpan(int spanId, Span parentSpan, String operationName, long startTime) { + super(spanId, parentSpan.getSpanId(), operationName, startTime); + } + + void push() { + stackDepth++; + } + + void pop() { + stackDepth--; + } + + boolean isFinished() { + return stackDepth == 0; + } + + @Override + public boolean isLeaf() { + return true; + } + + private boolean isInOwnerContext() { + return stackDepth == 1; + } + + /** + * Sets the string name for the logical operation this span represents, + * only when this is in context of the leaf span owner. + * + * @return this Span instance, for chaining + */ + @Override + public Span setOperationName(String operationName) { + if (isInOwnerContext()) { + super.setOperationName(operationName); + } + return this; + } + + /** + * Set a key:value tag on the Span, + * only when this is in context of the leaf span owner. + * + * @return this Span instance, for chaining + */ + @Override + public final Span setTag(String key, String value) { + if (isInOwnerContext()) { + super.setTag(key, value); + } + return this; + } + + @Override + public final Span setTag(String key, boolean value) { + if (isInOwnerContext()) { + super.setTag(key, value); + } + return this; + } + + @Override + public final Span setTag(String key, Integer value) { + if (isInOwnerContext()) { + super.setTag(key, value); + } + return this; + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracerContext.java index d46a3a7bb..687e725d7 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/context/TracerContext.java @@ -46,26 +46,40 @@ public final class TracerContext { * @param operationName {@link Span#operationName} * @return the new active span. */ - public Span createSpan(String operationName) { - return this.createSpan(operationName, System.currentTimeMillis()); + public Span createSpan(String operationName, boolean isLeaf) { + return this.createSpan(operationName, System.currentTimeMillis(), isLeaf); } /** * Create a new span, as an active span, by the given operationName and startTime; * * @param operationName {@link Span#operationName} - * @param startTime {@link Span#startTime} + * @param startTime {@link Span#startTime} + * @param isLeaf is true, if the span is a leaf in trace tree. * @return */ - public Span createSpan(String operationName, long startTime) { + public Span createSpan(String operationName, long startTime, boolean isLeaf) { Span parentSpan = peek(); Span span; if (parentSpan == null) { - span = new Span(spanIdGenerator++, operationName, startTime); + if (isLeaf) { + span = new LeafSpan(spanIdGenerator++, operationName, startTime); + } else { + span = new Span(spanIdGenerator++, operationName, startTime); + } + push(span); + } else if (parentSpan.isLeaf()) { + span = parentSpan; + LeafSpan leafSpan = (LeafSpan)span; + leafSpan.push(); } else { - span = new Span(spanIdGenerator++, parentSpan, operationName, startTime); + if (isLeaf) { + span = new LeafSpan(spanIdGenerator++, parentSpan, operationName, startTime); + } else { + span = new Span(spanIdGenerator++, parentSpan, operationName, startTime); + } + push(span); } - push(span); return span; } @@ -98,6 +112,13 @@ public final class TracerContext { public void stopSpan(Span span, Long endTime) { Span lastSpan = peek(); + if (lastSpan.isLeaf()) { + LeafSpan leafSpan = (LeafSpan)lastSpan; + leafSpan.pop(); + if (!leafSpan.isFinished()) { + return; + } + } if (lastSpan == span) { pop().finish(segment, endTime); } else { @@ -142,7 +163,7 @@ public final class TracerContext { * Ref this {@link ContextCarrier} to this {@link TraceSegment} * * @param carrier holds the snapshot, if get this {@link ContextCarrier} from remote, make sure {@link - * ContextCarrier#deserialize(String)} called. + * ContextCarrier#deserialize(String)} called. */ public void extract(ContextCarrier carrier) { if (carrier.isValid()) { diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/LeafSpanTestCase.java b/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/LeafSpanTestCase.java new file mode 100644 index 000000000..e1ebf5e86 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/LeafSpanTestCase.java @@ -0,0 +1,40 @@ +package org.skywalking.apm.agent.core.context; + +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author wusheng + */ +public class LeafSpanTestCase { + @Test + public void testLeaf() { + LeafSpan span = new LeafSpan(0, "serviceA", System.currentTimeMillis()); + span.push(); + span.setOperationName("serviceA2"); + span.setTag("key", "value-text"); + span.setTag("key2", false); + span.setTag("key3", 1); + + //start 2nd span + span.push(); + + Assert.assertFalse(span.isFinished()); + Assert.assertTrue(span.isLeaf()); + span.setOperationName("service123"); + span.setTag("key", "value-text2"); + span.setTag("key2", true); + span.setTag("key3", 2); + Assert.assertEquals("serviceA2", span.getOperationName()); + Assert.assertEquals("value-text", span.getStrTag("key")); + Assert.assertFalse(span.getBoolTag("key2")); + Assert.assertEquals(1, span.getIntTag("key3").intValue()); + + //end 2nd span + span.pop(); + + span.pop(); + Assert.assertTrue(span.isFinished()); + } +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/TracerContextTestCase.java b/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/TracerContextTestCase.java index f34a838a3..6c608553e 100644 --- a/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/TracerContextTestCase.java +++ b/apm-sniffer/apm-agent-core/src/test/java/org/skywalking/apm/agent/core/context/TracerContextTestCase.java @@ -19,7 +19,7 @@ public class TracerContextTestCase { @Test public void testSpanLifeCycle() { TracerContext context = new TracerContext(); - Span span = context.createSpan("/serviceA"); + Span span = context.createSpan("/serviceA", false); Assert.assertEquals(span, context.activeSpan()); @@ -35,8 +35,8 @@ public class TracerContextTestCase { @Test public void testChildOfSpan() { TracerContext context = new TracerContext(); - Span serviceSpan = context.createSpan("/serviceA"); - Span dbSpan = context.createSpan("db/preparedStatement/execute"); + Span serviceSpan = context.createSpan("/serviceA", false); + Span dbSpan = context.createSpan("db/preparedStatement/execute", false); Assert.assertEquals(dbSpan, context.activeSpan()); @@ -60,8 +60,8 @@ public class TracerContextTestCase { @Test public void testInject() { TracerContext context = new TracerContext(); - Span serviceSpan = context.createSpan("/serviceA"); - Span dbSpan = context.createSpan("db/preparedStatement/execute"); + Span serviceSpan = context.createSpan("/serviceA", false); + Span dbSpan = context.createSpan("db/preparedStatement/execute", false); Tags.PEER_HOST.set(dbSpan, "127.0.0.1"); Tags.PEER_PORT.set(dbSpan, 8080); @@ -87,7 +87,7 @@ public class TracerContextTestCase { TracerContext context = new TracerContext(); context.extract(carrier); - Span span = context.createSpan("/serviceC"); + Span span = context.createSpan("/serviceC", false); TracerContext.ListenerManager.add(TestTracerContextListener.INSTANCE); final TraceSegment[] finishedSegmentCarrier = TestTracerContextListener.INSTANCE.finishedSegmentCarrier;