From 4e58643bd07105b6fee12ca4afdf4be91fd1567f Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Wed, 22 Jan 2020 18:09:10 -0500 Subject: [PATCH] Initial finatra instrumentation --- .../instrumentation/finatra/finatra.gradle | 42 ++++++ .../finatra/FinatraDecorator.java | 51 +++++++ .../finatra/FinatraInstrumentation.java | 142 ++++++++++++++++++ .../ExecutorInstrumentationUtils.java | 20 ++- settings.gradle | 1 + 5 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 dd-java-agent/instrumentation/finatra/finatra.gradle create mode 100644 dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraDecorator.java create mode 100644 dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraInstrumentation.java diff --git a/dd-java-agent/instrumentation/finatra/finatra.gradle b/dd-java-agent/instrumentation/finatra/finatra.gradle new file mode 100644 index 0000000000..f85086a2c6 --- /dev/null +++ b/dd-java-agent/instrumentation/finatra/finatra.gradle @@ -0,0 +1,42 @@ +// 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 +//} diff --git a/dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraDecorator.java b/dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraDecorator.java new file mode 100644 index 0000000000..3d8cd1d1df --- /dev/null +++ b/dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraDecorator.java @@ -0,0 +1,51 @@ +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 { + 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"}; + } +} diff --git a/dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraInstrumentation.java b/dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraInstrumentation.java new file mode 100644 index 0000000000..2911bd58bf --- /dev/null +++ b/dd-java-agent/instrumentation/finatra/src/main/java/datadog/trace/instrumentation/finatra/FinatraInstrumentation.java @@ -0,0 +1,142 @@ +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 typeMatcher() { + return not(isInterface()) + .and(safeHasSuperType(named("com.twitter.finatra.http.internal.routing.Route"))); + } + + @Override + public Map, 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> 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 { + 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(); + } + } +} diff --git a/dd-java-agent/instrumentation/java-concurrent/src/main/java/datadog/trace/instrumentation/java/concurrent/ExecutorInstrumentationUtils.java b/dd-java-agent/instrumentation/java-concurrent/src/main/java/datadog/trace/instrumentation/java/concurrent/ExecutorInstrumentationUtils.java index a7ca0eaf54..6967415e8d 100644 --- a/dd-java-agent/instrumentation/java-concurrent/src/main/java/datadog/trace/instrumentation/java/concurrent/ExecutorInstrumentationUtils.java +++ b/dd-java-agent/instrumentation/java-concurrent/src/main/java/datadog/trace/instrumentation/java/concurrent/ExecutorInstrumentationUtils.java @@ -44,12 +44,22 @@ public class ExecutorInstrumentationUtils { */ public static State setupState( final ContextStore contextStore, final T task, final TraceScope scope) { + final State state = contextStore.putIfAbsent(task, State.FACTORY); - final TraceScope.Continuation continuation = scope.capture(); - if (state.setContinuation(continuation)) { - log.debug("created continuation {} from scope {}, state: {}", continuation, scope, state); - } else { - continuation.close(false); + + // 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; } diff --git a/settings.gradle b/settings.gradle index 8a982d5967..0914d6bd70 100644 --- a/settings.gradle +++ b/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'