Initial finatra instrumentation
ADD
dd-java-agent/instrumentation/finatra/finatra.gradle
+// Set properties before any plugins get loaded
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+}
+
+apply from: "${rootDir}/gradle/java.gradle"
+apply from: "${rootDir}/gradle/test-with-scala.gradle"
+
+// apply plugin: 'org.unbroken-dome.test-sets'
+//testSets {
+//
+// latestDepTest {
+// dirName = 'test'
+// }
+//}
+
+muzzle {
+
+}
+
+dependencies {
+ compileOnly group: 'com.twitter', name: 'finagle-http_2.11', version: '19.1.0'
+ compileOnly group: 'com.twitter', name: 'finatra-http_2.11', version: '19.1.0'
+
+ testCompile project(':dd-java-agent:instrumentation:netty-4.1')
+ testCompile project(':dd-java-agent:instrumentation:java-concurrent')
+ testCompile project(':dd-java-agent:instrumentation:trace-annotation')
+
+ testCompile group: 'com.twitter', name: 'finagle-http_2.11', version: '19.1.0'
+
+ testCompile(group: 'com.fasterxml.jackson.module', name: 'jackson-module-scala_2.11', version: '2.10.2') {
+ force = true
+ }
+
+// latestDepTestCompile project(':dd-java-agent:instrumentation:java-concurrent')
+ // latestDepTestCompile project(':dd-java-agent:instrumentation:trace-annotation')
+}
+
+//compileLatestDepTestGroovy {
+// classpath = classpath.plus(files(compileLatestDepTestScala.destinationDir))
+// dependsOn compileLatestDepTestScala
+//}

ADD
dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraDecorator.java
+package datadog.trace.instrumentation.finatra;
+
+import com.twitter.finagle.http.Request;
+import com.twitter.finagle.http.Response;
+import datadog.trace.agent.decorator.HttpServerDecorator;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class FinatraDecorator extends HttpServerDecorator<Request, Request, Response> {
+ public static final FinatraDecorator DECORATE = new FinatraDecorator();
+
+ @Override
+ protected String component() {
+ return "finatra";
+ }
+
+ @Override
+ protected String method(final Request request) {
+ return request.method().name();
+ }
+
+ @Override
+ protected URI url(final Request request) throws URISyntaxException {
+ return URI.create(request.uri());
+ }
+
+ @Override
+ protected String peerHostname(final Request request) {
+ return request.remoteHost();
+ }
+
+ @Override
+ protected String peerHostIP(final Request request) {
+ return request.remoteAddress().getHostAddress();
+ }
+
+ @Override
+ protected Integer peerPort(final Request request) {
+ return request.remotePort();
+ }
+
+ @Override
+ protected Integer status(final Response response) {
+ return response.statusCode();
+ }
+
+ @Override
+ protected String[] instrumentationNames() {
+ return new String[] {"finatra"};
+ }
+}

ADD
dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraInstrumentation.java
+package datadog.trace.instrumentation.finatra;
+
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
+import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
+import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
+import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
+import static datadog.trace.instrumentation.finatra.FinatraDecorator.DECORATE;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.google.auto.service.AutoService;
+import com.twitter.finagle.http.Request;
+import com.twitter.finagle.http.Response;
+import com.twitter.util.Future;
+import com.twitter.util.FutureEventListener;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.api.DDTags;
+import datadog.trace.instrumentation.api.AgentScope;
+import datadog.trace.instrumentation.api.AgentSpan;
+import datadog.trace.instrumentation.api.Tags;
+import java.lang.reflect.Method;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import scala.Some;
+
+@AutoService(Instrumenter.class)
+public class FinatraInstrumentation extends Instrumenter.Default {
+ public FinatraInstrumentation() {
+ super("finatra");
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "datadog.trace.agent.decorator.BaseDecorator",
+ "datadog.trace.agent.decorator.ServerDecorator",
+ "datadog.trace.agent.decorator.HttpServerDecorator",
+ packageName + ".FinatraDecorator",
+ FinatraInstrumentation.class.getName() + "$Listener"
+ };
+ }
+
+ @Override
+ public ElementMatcher<? super TypeDescription> typeMatcher() {
+ return not(isInterface())
+ .and(safeHasSuperType(named("com.twitter.finatra.http.internal.routing.Route")));
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod()
+ .and(named("handleMatch"))
+ .and(takesArguments(2))
+ .and(takesArgument(0, named("com.twitter.finagle.http.Request"))),
+ FinatraInstrumentation.class.getName() + "$RouteAdvice");
+ }
+
+ public static class RouteAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static AgentScope nameSpan(
+ @Advice.Argument(0) final Request request,
+ @Advice.FieldValue("path") final String path,
+ @Advice.FieldValue("clazz") final Class clazz,
+ @Advice.Origin final Method method) {
+
+ final AgentSpan parent = activeSpan();
+
+ final AgentSpan span = startSpan("finatra.request");
+ DECORATE.afterStart(span);
+ DECORATE.onConnection(span, request);
+ DECORATE.onRequest(span, request);
+
+ if (parent != null && "netty.request".equals(parent.getSpanName())) {
+ parent.setTag(DDTags.RESOURCE_NAME, request.method().name() + " " + path);
+ parent.setTag(Tags.COMPONENT, "finatra");
+ parent.setSpanName("finatra.request");
+
+ span.setSpanName("finatra.controller");
+ span.setTag(DDTags.RESOURCE_NAME, DECORATE.spanNameForClass(clazz));
+ }
+
+ final AgentScope scope = activateSpan(span, false);
+ scope.setAsyncPropagation(true);
+ return scope;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class)
+ public static void setupCallback(
+ @Advice.Enter final AgentScope scope,
+ @Advice.Thrown final Throwable throwable,
+ @Advice.Return final Some<Future<Response>> responseOption) {
+
+ if (scope == null) {
+ return;
+ }
+
+ final AgentSpan span = scope.span();
+ if (throwable != null) {
+ DECORATE.onError(span, throwable);
+ DECORATE.beforeFinish(span);
+ span.finish();
+ scope.close();
+ return;
+ }
+
+ responseOption.get().addEventListener(new Listener(scope));
+ }
+ }
+
+ public static class Listener implements FutureEventListener<Response> {
+ private final AgentScope scope;
+
+ public Listener(final AgentScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ public void onSuccess(final Response response) {
+ DECORATE.onResponse(scope.span(), response);
+ DECORATE.beforeFinish(scope.span());
+ scope.span().finish();
+ scope.close();
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ DECORATE.onError(scope.span(), cause);
+ DECORATE.beforeFinish(scope.span());
+ scope.span().finish();
+ scope.close();
+ }
+ }
+}

MODIFY
dd-java-agent/instrumentation/java-concurrent/src/main/java/datadog/trace/instrumentation/java/concurrent/ExecutorInstrumentationUtils.java
dd-java-agent/instrumentation/java-concurrent/src/main/java/datadog/trace/instrumentation/java/concurrent/ExecutorInstrumentationUtils.java
@@ -44,13 +44,23 @@ public class ExecutorInstrumentationUtils {
*/
public static <T> State setupState(
final ContextStore<T, State> contextStore, final T task, final TraceScope scope) {
+
final State state = contextStore.putIfAbsent(task, State.FACTORY);
+
+ // Don't instrument the executor's own runnables. These runnables may never return until
+ // netty shuts down. Any created continuations will be open until that time preventing traces
+ // from being reported
+ if (!task.getClass()
+ .getName()
+ .startsWith("io.netty.util.concurrent.SingleThreadEventExecutor$")) {
+
final TraceScope.Continuation continuation = scope.capture();
if (state.setContinuation(continuation)) {
log.debug("created continuation {} from scope {}, state: {}", continuation, scope, state);
} else {
continuation.close(false);
}
+ }
return state;
}

MODIFY
settings.gradle
settings.gradle
@@ -60,6 +60,7 @@ include ':dd-java-agent:instrumentation:elasticsearch:transport-2'
include ':dd-java-agent:instrumentation:elasticsearch:transport-5'
include ':dd-java-agent:instrumentation:elasticsearch:transport-5.3'
include ':dd-java-agent:instrumentation:elasticsearch:transport-6'
+include ':dd-java-agent:instrumentation:finatra'
include ':dd-java-agent:instrumentation:glassfish'
include ':dd-java-agent:instrumentation:google-http-client'
include ':dd-java-agent:instrumentation:grizzly-2'

Initial finatra instrumentation