adding ability to report all span in the trace
MODIFY
src/main/java/com/datadoghq/trace/impl/DDSpan.java
src/main/java/com/datadoghq/trace/impl/DDSpan.java
package com.datadoghq.trace.impl;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Optional;
+import java.time.Clock;
+import java.util.*;
import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import io.opentracing.Span;
import io.opentracing.SpanContext;
@@ -16,30 +15,28 @@ public class DDSpan implements io.opentracing.Span {
protected final Tracer tracer;
protected String operationName;
protected Map<String, Object> tags;
- protected long startTime;
- protected long startTimeNano; // Only used to measure nano time durations
+ protected long startTimeNano;
protected long durationNano;
protected final DDSpanContext context;
- protected final LinkedHashSet<Span> traces;
+ protected final ArrayList<Span> trace;
DDSpan(
Tracer tracer,
String operationName,
- LinkedHashSet<Span> traces,
+ ArrayList<Span> trace,
Map<String, Object> tags,
- Long timestamp,
+ Long timestampMilliseconds,
DDSpanContext context) {
this.tracer = tracer;
this.operationName = operationName;
- this.traces = Optional.ofNullable(traces).orElse(new LinkedHashSet<>());
+ this.trace = Optional.ofNullable(trace).orElse(new ArrayList<>());
this.tags = tags;
- this.startTime = System.currentTimeMillis() * 1000000;
- this.startTimeNano = System.nanoTime();
+ this.startTimeNano = Optional.ofNullable(timestampMilliseconds).orElse(Clock.systemUTC().millis()) * 1000000L;
this.context = context;
// track each span of the trace
- this.traces.add(this);
+ this.trace.add(this);
}
@@ -48,15 +45,19 @@ public class DDSpan implements io.opentracing.Span {
}
public void finish() {
- finish(System.nanoTime());
+ finish(Clock.systemUTC().millis());
}
- public void finish(long stopTimeMicro) {
- this.durationNano = stopTimeMicro * 1000L - startTime;
+ public void finish(long stopTimeMillis) {
+ this.durationNano = (stopTimeMillis * 1000000L - startTimeNano);
if (this.isRootSpan()) {
- this.traces.stream()
- .filter(s -> ((DDSpanContext) s.context()).getSpanId() != ((DDSpanContext) this.context()).getSpanId())
- .forEach(s -> s.finish());
+ this.trace.stream()
+ .filter(s -> {
+ boolean isSelf = ((DDSpanContext) s.context()).getSpanId() == ((DDSpanContext) this.context()).getSpanId();
+ boolean isFinished = ((DDSpan) s).getDurationNano() != 0L;
+ return !isSelf && !isFinished;
+ })
+ .forEach(Span::finish);
}
}
@@ -142,7 +143,7 @@ public class DDSpan implements io.opentracing.Span {
@JsonGetter(value = "start")
public long getStartTime() {
- return startTime;
+ return startTimeNano;
}
@JsonGetter(value = "duration")
@@ -182,7 +183,8 @@ public class DDSpan implements io.opentracing.Span {
return context.getErrorFlag() ? 1 : 0;
}
- public LinkedHashSet<Span> getTraces() {
- return traces;
+ @JsonIgnore
+ public ArrayList<Span> getTrace() {
+ return trace;
}
}

MODIFY
src/main/java/com/datadoghq/trace/impl/Tracer.java
src/main/java/com/datadoghq/trace/impl/Tracer.java
@@ -73,8 +73,8 @@ public class Tracer implements io.opentracing.Tracer {
return this;
}
- public Tracer.SpanBuilder withStartTimestamp(long timestamp) {
- this.timestamp = timestamp;
+ public Tracer.SpanBuilder withStartTimestamp(long timestampMillis) {
+ this.timestamp = timestampMillis;
return this;
}
@@ -105,15 +105,15 @@ public class Tracer implements io.opentracing.Tracer {
DDSpanContext context = buildTheSpanContext();
logger.startNewSpan(this.operationName, context.getSpanId());
- LinkedHashSet traces = null;
+ ArrayList<Span> trace = null;
if (this.parent != null) {
- traces = parent.getTraces();
+ trace = parent.getTrace();
}
return new DDSpan(
Tracer.this,
this.operationName,
- traces,
+ trace,
this.tags,
this.timestamp,
context);

MODIFY
src/main/java/com/datadoghq/trace/writer/impl/DDApi.java
src/main/java/com/datadoghq/trace/writer/impl/DDApi.java
@@ -16,7 +16,7 @@ import io.opentracing.Span;
public class DDApi {
- protected static final String TRACES_ENDPOINT = "/v0.3/traces";
+ protected static final String TRACES_ENDPOINT = "/v0.3/trace";
protected static final String SERVICES_ENDPOINT = "/v0.3/services";
protected final String host;
@@ -121,7 +121,7 @@ public class DDApi {
// String service = "{\"service_name\": {\"app\": \"service-name\",\"app_type\": \"web\"}}";
// System.out.println("Pushed service: "+api.callPUT(api.servicesEndpoint, service));
- System.out.println("Pushed traces: "+api.sendTraces(traces));
+ System.out.println("Pushed trace: "+api.sendTraces(traces));
}
}

MODIFY
src/test/java/com/datadoghq/trace/impl/DDSpanBuilderTest.java
src/test/java/com/datadoghq/trace/impl/DDSpanBuilderTest.java
package com.datadoghq.trace.impl;
-import io.opentracing.References;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -101,16 +101,16 @@ public class DDSpanBuilderTest {
.withStartTimestamp(expectedTimestamp)
.start();
- assertThat(span.getStartTime()).isEqualTo(expectedTimestamp);
+ assertThat(span.getStartTime()).isEqualTo(expectedTimestamp * 1000000L);
// auto-timestamp in nanoseconds
- long tick = System.nanoTime();
+ long tick = Clock.systemUTC().millis() * 1000000L;
span = (DDSpan) tracer
.buildSpan(expectedName)
.start();
// between now and now + 100ms
- assertThat(span.getStartTime()).isBetween(tick, tick + 100 * 1000);
+ assertThat(span.getStartTime()).isBetween(tick, tick + 100 * 1000000L);
}
@@ -171,25 +171,37 @@ public class DDSpanBuilderTest {
}
@Test
- public void shouldTrackAllSpanInTrace() {
+ public void shouldTrackAllSpanInTrace() throws InterruptedException {
ArrayList<DDSpan> spans = new ArrayList<>();
final int nbSamples = 10;
+ // root (aka spans[0]) is the parent
+ // spans[1] has a predictable duration
+ // others are just for fun
+
DDSpan root = (DDSpan) tracer.buildSpan("fake_O").start();
spans.add(root);
- for (int i = 1; i <= 10; i++) {
+ long tickStart = Clock.systemUTC().millis();
+ spans.add((DDSpan) tracer.buildSpan("fake_" + 1).asChildOf(spans.get(0)).withStartTimestamp(tickStart).start());
+ for (int i = 2; i <= 10; i++) {
spans.add((DDSpan) tracer.buildSpan("fake_" + i).asChildOf(spans.get(i - 1)).start());
}
- assertThat(root.getTraces()).hasSize(nbSamples + 1);
- assertThat(root.getTraces()).containsAll(spans);
- assertThat(spans.get((int) (Math.random() * nbSamples)).getTraces()).containsAll(spans);
+ Thread.sleep(300);
+ long tickEnd = Clock.systemUTC().millis();
+
+ spans.get(1).finish(tickEnd);
+
+ assertThat(root.getTrace()).hasSize(nbSamples + 1);
+ assertThat(root.getTrace()).containsAll(spans);
+ assertThat(spans.get((int) (Math.random() * nbSamples)).getTrace()).containsAll(spans);
root.finish();
//TODO Check order
- //assertThat(root.getTraces()).containsExactly(spans)
+ //assertThat(root.getTrace()).containsExactly(spans)
+ assertThat(spans.get(1).durationNano).isEqualTo((tickEnd - tickStart) * 1000000L);
spans.forEach(span -> assertThat(span.getDurationNano()).isNotNull());
spans.forEach(span -> assertThat(span.getDurationNano()).isNotZero());

MODIFY
src/test/java/com/datadoghq/trace/impl/DDSpanTest.java
src/test/java/com/datadoghq/trace/impl/DDSpanTest.java
package com.datadoghq.trace.impl;
-import io.opentracing.Span;
import org.junit.Test;
-import java.util.Optional;
-
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.floatThat;
-import static org.mockito.Mockito.mock;
public class DDSpanTest {

adding ability to report all span in the trace