Initial implementation of agent internal API.
ADD
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/instrumentation/api/AgentScope.java
+package datadog.trace.instrumentation.api;
+
+import java.io.Closeable;
+
+public interface AgentScope extends Closeable {
+ AgentSpan span();
+
+ void setAsyncPropagation(boolean value);
+
+ @Override
+ void close();
+}

ADD
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/instrumentation/api/AgentSpan.java
+package datadog.trace.instrumentation.api;
+
+public interface AgentSpan {
+ AgentSpan setTag(String key, boolean value);
+
+ AgentSpan setTag(String key, int value);
+
+ AgentSpan setTag(String key, long value);
+
+ AgentSpan setTag(String key, double value);
+
+ AgentSpan setTag(String key, String value);
+
+ AgentSpan setError(boolean error);
+
+ AgentSpan setErrorMessage(String errorMessage);
+
+ AgentSpan addThrowable(Throwable throwable);
+
+ Context context();
+
+ void finish();
+
+ String getSpanName();
+
+ void setSpanName(String spanName);
+
+ interface Context {}
+}

ADD
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/instrumentation/api/AgentTracer.java
+package datadog.trace.instrumentation.api;
+
+import datadog.trace.context.TraceScope;
+import datadog.trace.context.TraceScope.Continuation;
+import datadog.trace.instrumentation.api.AgentSpan.Context;
+import java.util.concurrent.atomic.AtomicReference;
+
+public abstract class AgentTracer {
+
+ // Implicit parent
+ public static AgentSpan startSpan(final String spanName) {
+ return get().startSpan(spanName);
+ }
+
+ // Implicit parent
+ public static AgentSpan startSpan(final String spanName, final long startTimeMicros) {
+ return get().startSpan(spanName, startTimeMicros);
+ }
+
+ // Explicit parent
+ public static AgentSpan startSpan(final String spanName, final AgentSpan.Context parent) {
+ return get().startSpan(spanName, parent);
+ }
+
+ // Explicit parent
+ public static AgentSpan startSpan(
+ final String spanName, final AgentSpan.Context parent, final long startTimeMicros) {
+ return get().startSpan(spanName, parent, startTimeMicros);
+ }
+
+ public static AgentScope activateSpan(final AgentSpan span, final boolean finishSpanOnClose) {
+ return get().activateSpan(span, finishSpanOnClose);
+ }
+
+ public static AgentSpan activeSpan() {
+ return get().activeSpan();
+ }
+
+ public static TraceScope activeScope() {
+ return get().activeScope();
+ }
+
+ public static Propagation propagate() {
+ return get().propagate();
+ }
+
+ public static AgentSpan noopSpan() {
+ return get().noopSpan();
+ }
+
+ private static final TracerAPI DEFAULT = new NoopTracerAPI();
+
+ private static final AtomicReference<TracerAPI> provider = new AtomicReference<>(DEFAULT);
+
+ public static void registerIfAbsent(final TracerAPI trace) {
+ provider.compareAndSet(DEFAULT, trace);
+ }
+
+ public static TracerAPI get() {
+ return provider.get();
+ }
+
+ public interface TracerAPI {
+ AgentSpan startSpan(String spanName);
+
+ AgentSpan startSpan(String spanName, long startTimeMicros);
+
+ AgentSpan startSpan(String spanName, AgentSpan.Context parent);
+
+ AgentSpan startSpan(String spanName, AgentSpan.Context parent, long startTimeMicros);
+
+ AgentScope activateSpan(AgentSpan span, boolean finishSpanOnClose);
+
+ AgentSpan activeSpan();
+
+ TraceScope activeScope();
+
+ Propagation propagate();
+
+ AgentSpan noopSpan();
+ }
+
+ static class NoopTracerAPI implements TracerAPI {
+
+ protected NoopTracerAPI() {}
+
+ @Override
+ public AgentSpan startSpan(final String spanName) {
+ return NoopAgentSpan.INSTANCE;
+ }
+
+ @Override
+ public AgentSpan startSpan(final String spanName, final long startTimeMicros) {
+ return NoopAgentSpan.INSTANCE;
+ }
+
+ @Override
+ public AgentSpan startSpan(final String spanName, final Context parent) {
+ return NoopAgentSpan.INSTANCE;
+ }
+
+ @Override
+ public AgentSpan startSpan(
+ final String spanName, final Context parent, final long startTimeMicros) {
+ return NoopAgentSpan.INSTANCE;
+ }
+
+ @Override
+ public AgentScope activateSpan(final AgentSpan span, final boolean finishSpanOnClose) {
+ return NoopAgentScope.INSTANCE;
+ }
+
+ @Override
+ public AgentSpan activeSpan() {
+ return NoopAgentSpan.INSTANCE;
+ }
+
+ @Override
+ public TraceScope activeScope() {
+ return null;
+ }
+
+ @Override
+ public Propagation propagate() {
+ return NoopPropagation.INSTANCE;
+ }
+
+ @Override
+ public AgentSpan noopSpan() {
+ return NoopAgentSpan.INSTANCE;
+ }
+ }
+
+ static class NoopAgentSpan implements AgentSpan {
+ static final NoopAgentSpan INSTANCE = new NoopAgentSpan();
+
+ @Override
+ public AgentSpan setTag(final String key, final boolean value) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final int value) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final long value) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final double value) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final String value) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan setError(final boolean error) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan setErrorMessage(final String errorMessage) {
+ return this;
+ }
+
+ @Override
+ public AgentSpan addThrowable(final Throwable throwable) {
+ return this;
+ }
+
+ @Override
+ public Context context() {
+ return NoopContext.INSTANCE;
+ }
+
+ @Override
+ public void finish() {}
+
+ @Override
+ public String getSpanName() {
+ return "";
+ }
+
+ @Override
+ public void setSpanName(final String spanName) {}
+ }
+
+ static class NoopAgentScope implements AgentScope {
+ static final NoopAgentScope INSTANCE = new NoopAgentScope();
+
+ @Override
+ public AgentSpan span() {
+ return NoopAgentSpan.INSTANCE;
+ }
+
+ @Override
+ public void setAsyncPropagation(final boolean value) {}
+
+ @Override
+ public void close() {}
+ }
+
+ static class NoopPropagation implements Propagation {
+ static final NoopPropagation INSTANCE = new NoopPropagation();
+
+ @Override
+ public Continuation capture() {
+ return NoopContinuation.INSTANCE;
+ }
+
+ @Override
+ public <C> void inject(final AgentSpan span, final C carrier, final Setter<C> setter) {}
+
+ @Override
+ public <C> Context extract(final C carrier, final Getter<C> getter) {
+ return NoopContext.INSTANCE;
+ }
+ }
+
+ static class NoopContinuation implements Continuation {
+ static final NoopContinuation INSTANCE = new NoopContinuation();
+
+ @Override
+ public TraceScope activate() {
+ return NoopTraceScope.INSTANCE;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public void close(final boolean closeContinuationScope) {}
+ }
+
+ static class NoopTraceScope implements TraceScope {
+ static final NoopTraceScope INSTANCE = new NoopTraceScope();
+
+ @Override
+ public Continuation capture() {
+ return NoopContinuation.INSTANCE;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public boolean isAsyncPropagating() {
+ return false;
+ }
+
+ @Override
+ public void setAsyncPropagation(final boolean value) {}
+ }
+
+ static class NoopContext implements Context {
+ static final NoopContext INSTANCE = new NoopContext();
+ }
+}

ADD
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/instrumentation/api/Propagation.java
+package datadog.trace.instrumentation.api;
+
+import datadog.trace.context.TraceScope;
+
+public interface Propagation {
+
+ TraceScope.Continuation capture();
+
+ <C> void inject(AgentSpan span, C carrier, Setter<C> setter);
+
+ interface Setter<C> {
+ void set(C carrier, String key, String value);
+ }
+
+ <C> AgentSpan.Context extract(C carrier, Getter<C> getter);
+
+ interface Getter<C> {
+ Iterable<String> keys(C carrier);
+
+ String get(C carrier, String key);
+ }
+}

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Constants.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Constants.java
@@ -10,7 +10,8 @@ public final class Constants {
/**
* packages which will be loaded on the bootstrap classloader
*
- * <p>Updates should be mirrored in TestUtils#BOOTSTRAP_PACKAGE_PREFIXES_COPY
+ * <p>Updates should be mirrored in
+ * datadog.trace.agent.test.SpockRunner#BOOTSTRAP_PACKAGE_PREFIXES_COPY
*/
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES = {
"datadog.slf4j",
@@ -18,6 +19,7 @@ public final class Constants {
"datadog.trace.api",
"datadog.trace.bootstrap",
"datadog.trace.context",
+ "datadog.trace.instrumentation.api",
"io.opentracing",
};

ADD
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/OpenTracing31.java
+package datadog.trace.agent.tooling;
+
+import static io.opentracing.log.Fields.ERROR_OBJECT;
+import static io.opentracing.propagation.Format.Builtin.TEXT_MAP;
+import static java.util.Collections.singletonMap;
+
+import datadog.trace.api.interceptor.MutableSpan;
+import datadog.trace.context.TraceScope;
+import datadog.trace.instrumentation.api.AgentScope;
+import datadog.trace.instrumentation.api.AgentSpan;
+import datadog.trace.instrumentation.api.AgentTracer.TracerAPI;
+import datadog.trace.instrumentation.api.Propagation;
+import datadog.trace.instrumentation.api.Propagation.Getter;
+import io.opentracing.Scope;
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.log.Fields;
+import io.opentracing.noop.NoopSpan;
+import io.opentracing.propagation.TextMap;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public final class OpenTracing31 implements TracerAPI {
+
+ private final Tracer tracer = GlobalTracer.get();
+ private final OT31Propagation propagation = new OT31Propagation();
+
+ private final OT31Span NOOP_SPAN = new OT31Span("", NoopSpan.INSTANCE);
+
+ @Override
+ public AgentSpan startSpan(final String spanName) {
+ return new OT31Span(spanName);
+ }
+
+ @Override
+ public AgentSpan startSpan(final String spanName, final long startTimeMicros) {
+ return new OT31Span(spanName, startTimeMicros);
+ }
+
+ @Override
+ public AgentSpan startSpan(final String spanName, final AgentSpan.Context parent) {
+ return new OT31Span(spanName, parent);
+ }
+
+ @Override
+ public AgentSpan startSpan(
+ final String spanName, final AgentSpan.Context parent, final long startTimeMicros) {
+ return new OT31Span(spanName, parent, startTimeMicros);
+ }
+
+ @Override
+ public AgentScope activateSpan(final AgentSpan span, final boolean finishSpanOnClose) {
+ // when span is noopSpan(), the scope returned is not a TracerScope
+ final Scope scope = tracer.scopeManager().activate(((OT31Span) span).span, finishSpanOnClose);
+ return new OT31Scope(span, scope);
+ }
+
+ @Override
+ public AgentSpan activeSpan() {
+ final Span span = tracer.activeSpan();
+ final String spanName;
+ if (span instanceof MutableSpan) {
+ spanName = ((MutableSpan) span).getOperationName();
+ } else {
+ spanName = "";
+ }
+ return span == null ? null : new OT31Span(spanName, span);
+ }
+
+ @Override
+ public TraceScope activeScope() {
+ final Scope scope = tracer.scopeManager().active();
+ if (scope instanceof TraceScope) {
+ return (TraceScope) scope;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Propagation propagate() {
+ return propagation;
+ }
+
+ @Override
+ public AgentSpan noopSpan() {
+ return NOOP_SPAN;
+ }
+
+ private final class OT31Span implements AgentSpan {
+
+ private final Span span;
+ private volatile String spanName;
+
+ public OT31Span(final String spanName) {
+ this(spanName, tracer.buildSpan(spanName).start());
+ }
+
+ public OT31Span(final String spanName, final long startTimeMicros) {
+ this(spanName, tracer.buildSpan(spanName).withStartTimestamp(startTimeMicros).start());
+ }
+
+ public OT31Span(final String spanName, final Context parent) {
+ this(
+ spanName,
+ tracer
+ .buildSpan(spanName)
+ .ignoreActiveSpan()
+ .asChildOf(((OTContext) parent).context)
+ .start());
+ }
+
+ public OT31Span(final String spanName, final Context parent, final long startTimeMicros) {
+ this(
+ spanName,
+ tracer
+ .buildSpan(spanName)
+ .ignoreActiveSpan()
+ .asChildOf(((OTContext) parent).context)
+ .withStartTimestamp(startTimeMicros)
+ .start());
+ }
+
+ public OT31Span(final String spanName, final Span span) {
+ this.spanName = spanName;
+ this.span = span;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final boolean value) {
+ span.setTag(key, value);
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final int value) {
+ span.setTag(key, value);
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final long value) {
+ span.setTag(key, value);
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final double value) {
+ span.setTag(key, value);
+ return this;
+ }
+
+ @Override
+ public AgentSpan setTag(final String key, final String value) {
+ span.setTag(key, value);
+ return this;
+ }
+
+ @Override
+ public AgentSpan setError(final boolean error) {
+ Tags.ERROR.set(span, error);
+ return this;
+ }
+
+ @Override
+ public AgentSpan setErrorMessage(final String errorMessage) {
+ span.log(singletonMap(Fields.MESSAGE, errorMessage));
+ return this;
+ }
+
+ @Override
+ public AgentSpan addThrowable(final Throwable throwable) {
+ span.log(singletonMap(ERROR_OBJECT, throwable));
+ return this;
+ }
+
+ @Override
+ public OTContext context() {
+ final SpanContext context = span.context();
+ return new OTContext(context);
+ }
+
+ @Override
+ public void finish() {
+ span.finish();
+ }
+
+ @Override
+ public String getSpanName() {
+ return spanName;
+ }
+
+ @Override
+ public void setSpanName(final String spanName) {
+ this.spanName = spanName;
+ span.setOperationName(spanName);
+ }
+
+ Span getSpan() {
+ return span;
+ }
+ }
+
+ private final class OT31Scope implements AgentScope {
+
+ private final OT31Span span;
+ private final Scope scope;
+
+ public OT31Scope(final AgentSpan span, final Scope scope) {
+ assert span instanceof OT31Span;
+ this.span = (OT31Span) span;
+ this.scope = scope;
+ }
+
+ @Override
+ public void close() {
+ scope.close();
+ }
+
+ @Override
+ public void setAsyncPropagation(final boolean value) {
+ if (scope instanceof TraceScope) {
+ ((TraceScope) scope).setAsyncPropagation(value);
+ }
+ }
+
+ @Override
+ public AgentSpan span() {
+ return span;
+ }
+ }
+
+ private final class OT31Propagation implements Propagation {
+
+ @Override
+ public TraceScope.Continuation capture() {
+ final Scope active = tracer.scopeManager().active();
+ if (active instanceof TraceScope) {
+ return ((TraceScope) active).capture();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public <C> void inject(final AgentSpan span, final C carrier, final Setter<C> setter) {
+ assert span instanceof OT31Span;
+ tracer.inject(
+ ((OT31Span) span).getSpan().context(),
+ TEXT_MAP,
+ new OT31Propagation.Injector<>(carrier, setter));
+ }
+
+ private final class Injector<C> implements TextMap {
+ private final C carrier;
+ private final Setter<C> setter;
+
+ public Injector(final C carrier, final Setter<C> setter) {
+ this.carrier = carrier;
+ this.setter = setter;
+ }
+
+ @Override
+ public Iterator<Entry<String, String>> iterator() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void put(final String key, final String value) {
+ setter.set(carrier, key, value);
+ }
+ }
+
+ @Override
+ public <C> AgentSpan.Context extract(final C carrier, final Getter<C> getter) {
+ return new OTContext(tracer.extract(TEXT_MAP, new Extractor(carrier, getter)));
+ }
+ }
+
+ private static final class Extractor<C> implements TextMap {
+ private final Map<String, String> extracted;
+
+ public Extractor(final C carrier, final Getter<C> getter) {
+ extracted = new HashMap<>();
+ for (final String key : getter.keys(carrier)) {
+ extracted.put(key, getter.get(carrier, key));
+ }
+ }
+
+ @Override
+ public Iterator<Entry<String, String>> iterator() {
+ return extracted.entrySet().iterator();
+ }
+
+ @Override
+ public void put(final String key, final String value) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static final class OTContext implements AgentSpan.Context, SpanContext {
+ private final SpanContext context;
+
+ public OTContext(final SpanContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public Iterable<Entry<String, String>> baggageItems() {
+ return context.baggageItems();
+ }
+ }
+}

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/TracerInstaller.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/TracerInstaller.java
@@ -2,6 +2,7 @@ package datadog.trace.agent.tooling;
import datadog.opentracing.DDTracer;
import datadog.trace.api.Config;
+import datadog.trace.instrumentation.api.AgentTracer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -14,6 +15,7 @@ public class TracerInstaller {
try {
io.opentracing.util.GlobalTracer.register(tracer);
datadog.trace.api.GlobalTracer.registerIfAbsent(tracer);
+ AgentTracer.registerIfAbsent(new OpenTracing31());
} catch (final RuntimeException re) {
log.warn("Failed to register tracer '" + tracer + "'", re);
}

MODIFY
dd-java-agent/instrumentation/osgi-classloading/src/test/groovy/OSGIClassloadingTest.groovy
dd-java-agent/instrumentation/osgi-classloading/src/test/groovy/OSGIClassloadingTest.groovy
@@ -10,7 +10,7 @@ class OSGIClassloadingTest extends AgentTestRunner {
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()
- static final String BOOT_DELEGATION_ADDITION = "datadog.slf4j.*,datadog.slf4j,datadog.trace.agent.TracingAgent.*,datadog.trace.agent.TracingAgent,datadog.trace.api.*,datadog.trace.api,datadog.trace.bootstrap.*,datadog.trace.bootstrap,datadog.trace.context.*,datadog.trace.context,io.opentracing.*,io.opentracing"
+ static final String BOOT_DELEGATION_ADDITION = "datadog.slf4j.*,datadog.slf4j,datadog.trace.agent.TracingAgent.*,datadog.trace.agent.TracingAgent,datadog.trace.api.*,datadog.trace.api,datadog.trace.bootstrap.*,datadog.trace.bootstrap,datadog.trace.context.*,datadog.trace.context,datadog.trace.instrumentation.api.*,datadog.trace.instrumentation.api,io.opentracing.*,io.opentracing"
def "delegation property set on module load"() {
when:

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java
@@ -36,6 +36,7 @@ public class SpockRunner extends Sputnik {
"datadog.trace.api",
"datadog.trace.bootstrap",
"datadog.trace.context",
+ "datadog.trace.instrumentation.api",
"io.opentracing",
};

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy
@@ -152,7 +152,8 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
}
static <T> T controller(ServerEndpoint endpoint, Closure<T> closure) {
- assert ((TraceScope) GlobalTracer.get().scopeManager().active()).asyncPropagating
+ assert GlobalTracer.get().activeSpan() != null: "Controller should have a parent span."
+ assert ((TraceScope) GlobalTracer.get().scopeManager().active()).asyncPropagating: "Scope should be propagating async."
if (endpoint == NOT_FOUND) {
return closure()
}

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/GlobalTracerUtils.java
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/GlobalTracerUtils.java
package datadog.trace.agent.test.utils;
+import datadog.trace.agent.tooling.OpenTracing31;
+import datadog.trace.instrumentation.api.AgentTracer;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import java.lang.reflect.Field;
@@ -8,6 +10,7 @@ public class GlobalTracerUtils {
public static void registerOrReplaceGlobalTracer(final Tracer tracer) {
try {
GlobalTracer.register(tracer);
+ AgentTracer.registerIfAbsent(new OpenTracing31());
} catch (final Exception e) {
// Force it anyway using reflection
Field field = null;

Initial implementation of agent internal API.