From 759fb4e815a0f5e2f673d219eee37823759e643e Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 1 Mar 2018 14:31:03 +1000 Subject: [PATCH] Configure tags to be added to every span --- .../datadog/opentracing/DDSpanContext.java | 4 +- .../java/datadog/opentracing/DDTracer.java | 30 ++- .../datadog/trace/common/DDTraceConfig.java | 27 ++ .../opentracing/DDSpanBuilderTest.groovy | 241 ++++++++++++++++++ .../datadog/opentracing/DDSpanTest.groovy | 2 +- .../datadog/trace/DDTraceConfigTest.groovy | 31 +++ .../opentracing/DDSpanBuilderTest.java | 230 ----------------- 7 files changed, 328 insertions(+), 237 deletions(-) create mode 100644 dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy delete mode 100644 dd-trace-ot/src/test/java/datadog/opentracing/DDSpanBuilderTest.java diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java index 2ecd232eda0..eb37968aa1e 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDSpanContext.java @@ -148,7 +148,7 @@ public void setSpanType(final String spanType) { this.spanType = spanType; } - public void setSamplingPriority(int newPriority) { + public void setSamplingPriority(final int newPriority) { if (samplingPriorityLocked) { log.warn( "samplingPriority locked at {}. Refusing to set to {}", samplingPriority, newPriority); @@ -272,7 +272,7 @@ public synchronized void setTag(final String tag, final Object value) { public synchronized Map getTags() { if (tags.isEmpty()) { - tags = Maps.newHashMapWithExpectedSize(2); + tags = Maps.newHashMapWithExpectedSize(3); } tags.put(DDTags.THREAD_NAME, threadName); tags.put(DDTags.THREAD_ID, threadId); diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java index ca55d9335ac..4c5292e2eed 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java @@ -1,6 +1,7 @@ package datadog.opentracing; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.collect.Maps; import datadog.opentracing.decorators.AbstractDecorator; import datadog.opentracing.decorators.DDDecoratorsFactory; import datadog.opentracing.propagation.Codec; @@ -44,6 +45,9 @@ public class DDTracer extends ThreadLocalScopeManager implements io.opentracing. /** Sampler defines the sampling policy in order to reduce the number of traces for instance */ final Sampler sampler; + /** A set of tags that are added to every span */ + private final Map spanTags; + /** Span context decorators */ private final Map> spanContextDecorators = new HashMap<>(); @@ -63,7 +67,8 @@ public DDTracer(final Properties config) { this( config.getProperty(DDTraceConfig.SERVICE_NAME), Writer.Builder.forConfig(config), - Sampler.Builder.forConfig(config)); + Sampler.Builder.forConfig(config), + DDTraceConfig.parseMap(config.getProperty(DDTraceConfig.SPAN_TAGS))); log.debug("Using config: {}", config); // Create decorators from resource files @@ -75,10 +80,20 @@ public DDTracer(final Properties config) { } public DDTracer(final String serviceName, final Writer writer, final Sampler sampler) { + this(serviceName, writer, sampler, Collections.emptyMap()); + } + + public DDTracer( + final String serviceName, + final Writer writer, + final Sampler sampler, + final Map spanTags) { this.serviceName = serviceName; this.writer = writer; this.writer.start(); this.sampler = sampler; + this.spanTags = spanTags; + registry = new CodecRegistry(); registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec()); registry.register(Format.Builtin.TEXT_MAP, new HTTPCodec()); @@ -90,7 +105,11 @@ public DDTracer(final String serviceName, final Writer writer, final Sampler sam } public DDTracer(final Writer writer) { - this(UNASSIGNED_DEFAULT_SERVICE_NAME, writer, new AllSampler()); + this( + UNASSIGNED_DEFAULT_SERVICE_NAME, + writer, + new AllSampler(), + DDTraceConfig.parseMap(new DDTraceConfig().getProperty(DDTraceConfig.SPAN_TAGS))); } /** @@ -185,6 +204,8 @@ public String toString() { + writer + ", sampler=" + sampler + + ", tags=" + + spanTags + '}'; } @@ -236,7 +257,8 @@ public class DDSpanBuilder implements SpanBuilder { private final String operationName; // Builder attributes - private Map tags = Collections.emptyMap(); + private Map tags = + spanTags.isEmpty() ? Collections.emptyMap() : Maps.newHashMap(spanTags); private long timestamp; private SpanContext parent; private String serviceName; @@ -361,7 +383,7 @@ public DDSpanBuilder addReference(final String referenceType, final SpanContext // Private methods private DDSpanBuilder withTag(final String tag, final Object value) { if (this.tags.isEmpty()) { - this.tags = new HashMap<>(); + this.tags = Maps.newHashMap(); } this.tags.put(tag, value); return this; diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java b/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java index e24ebb13c4a..053056f24d9 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/DDTraceConfig.java @@ -1,9 +1,13 @@ package datadog.trace.common; +import com.google.common.collect.Maps; import datadog.opentracing.DDTracer; import datadog.trace.common.writer.DDAgentWriter; import datadog.trace.common.writer.Writer; +import java.util.Collections; +import java.util.Map; import java.util.Properties; +import lombok.extern.slf4j.Slf4j; /** * Config gives priority to system properties and falls back to environment variables. It also @@ -14,6 +18,7 @@ *

System properties are {@link DDTraceConfig#PREFIX}'ed. Environment variables are the same as * the system property, but uppercased with '.' -> '_'. */ +@Slf4j public class DDTraceConfig extends Properties { /** Config keys below */ private static final String PREFIX = "dd."; @@ -23,12 +28,14 @@ public class DDTraceConfig extends Properties { public static final String AGENT_HOST = "agent.host"; public static final String AGENT_PORT = "agent.port"; public static final String PRIORITY_SAMPLING = "priority.sampling"; + public static final String SPAN_TAGS = "trace.span.tags"; private final String serviceName = getPropOrEnv(PREFIX + SERVICE_NAME); private final String writerType = getPropOrEnv(PREFIX + WRITER_TYPE); private final String agentHost = getPropOrEnv(PREFIX + AGENT_HOST); private final String agentPort = getPropOrEnv(PREFIX + AGENT_PORT); private final String prioritySampling = getPropOrEnv(PREFIX + PRIORITY_SAMPLING); + private final String spanTags = getPropOrEnv(PREFIX + SPAN_TAGS); public DDTraceConfig() { super(); @@ -45,6 +52,7 @@ public DDTraceConfig() { setIfNotNull(AGENT_HOST, agentHost); setIfNotNull(AGENT_PORT, agentPort); setIfNotNull(PRIORITY_SAMPLING, prioritySampling); + setIfNotNull(SPAN_TAGS, spanTags); } public DDTraceConfig(final String serviceName) { @@ -65,4 +73,23 @@ private String getPropOrEnv(final String name) { static String propToEnvName(final String name) { return name.toUpperCase().replace(".", "_"); } + + public static Map parseMap(final String str) { + if (str == null || str.trim().isEmpty()) { + return Collections.emptyMap(); + } + if (!str.matches("(([^,:]+:[^,:]+,)*([^,:]+:[^,:]+),?)?")) { + log.warn("Invalid config '{}'. Must match 'key1:value1,key2:value2'.", str); + return Collections.emptyMap(); + } + + final String[] tokens = str.split(","); + final Map map = Maps.newHashMapWithExpectedSize(tokens.length); + + for (final String token : tokens) { + final String[] keyValue = token.split(":"); + map.put(keyValue[0].trim(), keyValue[1].trim()); + } + return Collections.unmodifiableMap(map); + } } diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy new file mode 100644 index 00000000000..0c354be7bd1 --- /dev/null +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy @@ -0,0 +1,241 @@ +package datadog.opentracing + +import datadog.trace.api.DDTags +import datadog.trace.common.writer.ListWriter +import spock.lang.Specification + +import static java.util.concurrent.TimeUnit.MILLISECONDS +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.when + +class DDSpanBuilderTest extends Specification { + def writer = new ListWriter() + def tracer = new DDTracer(writer) + + def "build simple span"() { + setup: + final DDSpan span = tracer.buildSpan("op name").withServiceName("foo").start() + + expect: + span.operationName == "op name" + } + + def "build complex span"() { + setup: + def expectedName = "fakeName" + def tags = [ + "1": true, + "2": "fakeString", + "3": 42.0, + ] + + DDTracer.DDSpanBuilder builder = tracer + .buildSpan(expectedName) + .withServiceName("foo") + tags.each { + builder = builder.withTag(it.key, it.value) + } + + when: + DDSpan span = builder.start() + + then: + span.getOperationName() == expectedName + span.tags.subMap(tags.keySet()) == tags + + + when: + span = tracer.buildSpan(expectedName).withServiceName("foo").start() + + then: + span.getTags() == [(DDTags.THREAD_NAME): Thread.currentThread().getName(), + (DDTags.THREAD_ID) : Thread.currentThread().getId(), + (DDTags.SPAN_TYPE) : null] + + when: + // with all custom fields provided + final String expectedResource = "fakeResource" + final String expectedService = "fakeService" + final String expectedType = "fakeType" + + span = + tracer + .buildSpan(expectedName) + .withServiceName("foo") + .withResourceName(expectedResource) + .withServiceName(expectedService) + .withErrorFlag() + .withSpanType(expectedType) + .start() + + final DDSpanContext context = span.context() + + then: + context.getResourceName() == expectedResource + context.getErrorFlag() + context.getServiceName() == expectedService + context.getSpanType() == expectedType + + context.tags[DDTags.THREAD_NAME] == Thread.currentThread().getName() + context.tags[DDTags.THREAD_ID] == Thread.currentThread().getId() + } + + def "should build span timestamp in nano"() { + setup: + // time in micro + final long expectedTimestamp = 487517802L * 1000 * 1000L + final String expectedName = "fakeName" + + DDSpan span = + tracer + .buildSpan(expectedName) + .withServiceName("foo") + .withStartTimestamp(expectedTimestamp) + .start() + + expect: + // get return nano time + span.getStartTime() == expectedTimestamp * 1000L + + when: + // auto-timestamp in nanoseconds + def start = System.currentTimeMillis() + span = tracer.buildSpan(expectedName).withServiceName("foo").start() + def stop = System.currentTimeMillis() + + then: + // Give a range of +/- 5 millis + span.getStartTime() >= MILLISECONDS.toNanos(start - 1) + span.getStartTime() <= MILLISECONDS.toNanos(stop + 1) + } + + def "should link to parent span"() { + setup: + final long spanId = 1L + final long expectedParentId = spanId + + final DDSpanContext mockedContext = mock(DDSpanContext) + + when(mockedContext.getSpanId()).thenReturn(spanId) + when(mockedContext.getServiceName()).thenReturn("foo") + + final String expectedName = "fakeName" + + final DDSpan span = + tracer + .buildSpan(expectedName) + .withServiceName("foo") + .asChildOf(mockedContext) + .start() + + final DDSpanContext actualContext = span.context() + + expect: + actualContext.getParentId() == expectedParentId + } + + def "should inherit the DD parent attributes"() { + setup: + def expectedName = "fakeName" + def expectedParentServiceName = "fakeServiceName" + def expectedParentResourceName = "fakeResourceName" + def expectedParentType = "fakeType" + def expectedChildServiceName = "fakeServiceName-child" + def expectedChildResourceName = "fakeResourceName-child" + def expectedChildType = "fakeType-child" + def expectedBaggageItemKey = "fakeKey" + def expectedBaggageItemValue = "fakeValue" + + final DDSpan parent = + tracer + .buildSpan(expectedName) + .withServiceName("foo") + .withResourceName(expectedParentResourceName) + .withSpanType(expectedParentType) + .start() + + parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue) + + // ServiceName and SpanType are always set by the parent if they are not present in the child + DDSpan span = + tracer + .buildSpan(expectedName) + .withServiceName(expectedParentServiceName) + .asChildOf(parent) + .start() + + expect: + span.getOperationName() == expectedName + span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue + span.context().getServiceName() == expectedParentServiceName + span.context().getResourceName() == expectedName + span.context().getSpanType() == expectedParentType + + when: + // ServiceName and SpanType are always overwritten by the child if they are present + span = + tracer + .buildSpan(expectedName) + .withServiceName(expectedChildServiceName) + .withResourceName(expectedChildResourceName) + .withSpanType(expectedChildType) + .asChildOf(parent) + .start() + + then: + span.getOperationName() == expectedName + span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue + span.context().getServiceName() == expectedChildServiceName + span.context().getResourceName() == expectedChildResourceName + span.context().getSpanType() == expectedChildType + } + + def "should track all spans in trace"() { + setup: + List spans = [] + final int nbSamples = 10 + + // root (aka spans[0]) is the parent + // others are just for fun + + def root = tracer.buildSpan("fake_O").withServiceName("foo").start() + spans.add(root) + + final long tickEnd = System.currentTimeMillis() + + for (int i = 1; i <= 10; i++) { + spans.add(tracer + .buildSpan("fake_" + i) + .withServiceName("foo") + .asChildOf(spans.get(i - 1)) + .start()) + } + spans.get(1).finish(tickEnd) + + expect: + root.context().getTrace().size() == nbSamples + 1 + root.context().getTrace().containsAll(spans) + spans[(int) (Math.random() * nbSamples)].context.trace.containsAll(spans) + } + + def "global span tags populated on each span"() { + setup: + System.setProperty("dd.trace.span.tags", tagString) + tracer = new DDTracer(writer) + def span = tracer.buildSpan("op name").withServiceName("foo").start() + tags.putAll([(DDTags.THREAD_NAME): Thread.currentThread().getName(), + (DDTags.THREAD_ID) : Thread.currentThread().getId(), + (DDTags.SPAN_TYPE) : null]) + + expect: + span.tags == tags + + where: + tagString | tags + "" | [:] + "in:val:id" | [:] + "a:x" | [a: "x"] + "a:a,a:b,a:c" | [a: "c"] + "a:1,b-c:d" | [a: "1", "b-c": "d"] + } +} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy index 2d29df845d3..5035d99614f 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy @@ -120,7 +120,7 @@ class DDSpanTest extends Specification { def between = System.currentTimeMillis() def betweenDur = System.currentTimeMillis() - between span.finish() - def total = System.currentTimeMillis() - start + def total = Math.max(1, System.currentTimeMillis() - start) expect: span.durationNano >= TimeUnit.MILLISECONDS.toNanos(betweenDur) diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy index 76a2e929e42..222f7c674a8 100644 --- a/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/trace/DDTraceConfigTest.groovy @@ -118,4 +118,35 @@ class DDTraceConfigTest extends Specification { "writer" | "agent.host" | "somethingelse" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://somethingelse:8126/v0.3/traces } }" "writer" | "agent.port" | "9999" | "DDAgentWriter { api=DDApi { tracesEndpoint=http://localhost:9999/v0.3/traces } }" } + + def "parsing valid string returns a map"() { + expect: + DDTraceConfig.parseMap(str) == map + + where: + str | map + "a:a;" | [a: "a;"] + "a:1, a:2, a:3" | [a: "3"] + "a:b,c:d" | [a: "b", c: "d"] + "key 1!:va|ue_1," | ["key 1!": "va|ue_1"] + " key1 :value1 ,\t key2: value2" | [key1: "value1", key2: "value2"] + } + + def "parsing an invalid string returns an empty map"() { + expect: + DDTraceConfig.parseMap(str) == map + + where: + str | map + null | [:] + "" | [:] + "1" | [:] + "a" | [:] + "a:" | [:] + "a,1" | [:] + "in:val:id" | [:] + "a:b:c:d" | [:] + "a:b,c,d" | [:] + "!a" | [:] + } } diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanBuilderTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanBuilderTest.java deleted file mode 100644 index 3f7eae776ae..00000000000 --- a/dd-trace-ot/src/test/java/datadog/opentracing/DDSpanBuilderTest.java +++ /dev/null @@ -1,230 +0,0 @@ -package datadog.opentracing; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import datadog.trace.api.DDTags; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class DDSpanBuilderTest { - - private DDTracer tracer; - - @Before - public void setUp() throws Exception { - tracer = new DDTracer(); - } - - @After - public void tearDown() throws Exception {} - - @Test - public void shouldBuildSimpleSpan() { - - final String expectedName = "fakeName"; - final DDSpan span = tracer.buildSpan(expectedName).withServiceName("foo").startManual(); - assertThat(span.getOperationName()).isEqualTo(expectedName); - } - - @Test - public void shouldBuildMoreComplexSpan() { - - final String expectedName = "fakeName"; - final Map tags = - new HashMap() { - { - put("1", true); - put("2", "fakeString"); - put("3", 42.0); - } - }; - - DDSpan span = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .withTag("1", (Boolean) tags.get("1")) - .withTag("2", (String) tags.get("2")) - .withTag("3", (Number) tags.get("3")) - .startManual(); - - assertThat(span.getOperationName()).isEqualTo(expectedName); - assertThat(span.getTags()).containsAllEntriesOf(tags); - - // with no tag provided - - span = tracer.buildSpan(expectedName).withServiceName("foo").startManual(); - - assertThat(span.getTags()).isNotNull(); - assertThat(span.getTags().size()).isEqualTo(3); - - // with all custom fields provided - final String expectedResource = "fakeResource"; - final String expectedService = "fakeService"; - final String expectedType = "fakeType"; - - span = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .withResourceName(expectedResource) - .withServiceName(expectedService) - .withErrorFlag() - .withSpanType(expectedType) - .startManual(); - - final DDSpanContext actualContext = span.context(); - - assertThat(actualContext.getResourceName()).isEqualTo(expectedResource); - assertThat(actualContext.getErrorFlag()).isTrue(); - assertThat(actualContext.getServiceName()).isEqualTo(expectedService); - assertThat(actualContext.getSpanType()).isEqualTo(expectedType); - assertThat(actualContext.getTags().get(DDTags.THREAD_NAME)) - .isEqualTo(Thread.currentThread().getName()); - assertThat(actualContext.getTags().get(DDTags.THREAD_ID)) - .isEqualTo(Thread.currentThread().getId()); - } - - @Test - public void shouldBuildSpanTimestampInNano() { - - // time in micro - final long expectedTimestamp = 487517802L * 1000 * 1000L; - final String expectedName = "fakeName"; - - DDSpan span = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .withStartTimestamp(expectedTimestamp) - .start(); - - // get return nano time - assertThat(span.getStartTime()).isEqualTo(expectedTimestamp * 1000L); - - // auto-timestamp in nanoseconds - final long tick = System.currentTimeMillis(); - span = tracer.buildSpan(expectedName).withServiceName("foo").startManual(); - - // Give a range of +/- 5 millis - assertThat(span.getStartTime()) - .isBetween(MILLISECONDS.toNanos(tick - 5), MILLISECONDS.toNanos(tick + 5)); - } - - @Test - public void shouldLinkToParentSpan() { - - final long spanId = 1L; - final long expectedParentId = spanId; - - final DDSpanContext mockedContext = mock(DDSpanContext.class); - - when(mockedContext.getSpanId()).thenReturn(spanId); - when(mockedContext.getServiceName()).thenReturn("foo"); - - final String expectedName = "fakeName"; - - final DDSpan span = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .asChildOf(mockedContext) - .startManual(); - - final DDSpanContext actualContext = span.context(); - - assertThat(actualContext.getParentId()).isEqualTo(expectedParentId); - } - - @Test - public void shouldInheritOfTheDDParentAttributes() { - - final String expectedName = "fakeName"; - final String expectedParentServiceName = "fakeServiceName"; - final String expectedParentResourceName = "fakeResourceName"; - final String expectedParentType = "fakeType"; - final String expectedChildServiceName = "fakeServiceName-child"; - final String expectedChildResourceName = "fakeResourceName-child"; - final String expectedChildType = "fakeType-child"; - final String expectedBaggageItemKey = "fakeKey"; - final String expectedBaggageItemValue = "fakeValue"; - - final DDSpan parent = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .withResourceName(expectedParentResourceName) - .withSpanType(expectedParentType) - .startManual(); - - parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue); - - // ServiceName and SpanType are always set by the parent if they are not present in the child - DDSpan span = - tracer - .buildSpan(expectedName) - .withServiceName(expectedParentServiceName) - .asChildOf(parent) - .startManual(); - - assertThat(span.getOperationName()).isEqualTo(expectedName); - assertThat(span.getBaggageItem(expectedBaggageItemKey)).isEqualTo(expectedBaggageItemValue); - assertThat(span.context().getServiceName()).isEqualTo(expectedParentServiceName); - assertThat(span.context().getResourceName()).isNotEqualTo(expectedParentResourceName); - assertThat(span.context().getSpanType()).isEqualTo(expectedParentType); - - // ServiceName and SpanType are always overwritten by the child if they are present - span = - tracer - .buildSpan(expectedName) - .withServiceName(expectedChildServiceName) - .withResourceName(expectedChildResourceName) - .withSpanType(expectedChildType) - .asChildOf(parent) - .startManual(); - - assertThat(span.getOperationName()).isEqualTo(expectedName); - assertThat(span.getBaggageItem(expectedBaggageItemKey)).isEqualTo(expectedBaggageItemValue); - assertThat(span.context().getServiceName()).isEqualTo(expectedChildServiceName); - assertThat(span.context().getResourceName()).isEqualTo(expectedChildResourceName); - assertThat(span.context().getSpanType()).isEqualTo(expectedChildType); - } - - @Test - public void shouldTrackAllSpanInTrace() throws InterruptedException { - - final ArrayList spans = new ArrayList<>(); - final int nbSamples = 10; - - // root (aka spans[0]) is the parent - // others are just for fun - - final DDSpan root = tracer.buildSpan("fake_O").withServiceName("foo").startManual(); - spans.add(root); - - Thread.sleep(200); - final long tickEnd = System.currentTimeMillis(); - - for (int i = 1; i <= 10; i++) { - spans.add( - tracer - .buildSpan("fake_" + i) - .withServiceName("foo") - .asChildOf(spans.get(i - 1)) - .startManual()); - } - spans.get(1).finish(tickEnd); - - assertThat(root.context().getTrace()).hasSize(nbSamples + 1); - assertThat(root.context().getTrace()).containsAll(spans); - assertThat(spans.get((int) (Math.random() * nbSamples)).context().getTrace()) - .containsAll(spans); - } -}