Merge commit 'f2d8c8e6b9967b204803a3744942582191266625' into dd-merge

This commit is contained in:
Trask Stalnaker 2020-02-12 15:08:58 -08:00
commit 852c00193c
17 changed files with 526 additions and 46 deletions

View File

@ -20,8 +20,8 @@ Other source files (Groovy, Scala, etc) should ideally be formatted by Intellij
Suggested plugins and settings:
* Editor > Code Style > Java/Groovy > Imports
* Class count to use import with '*': `50` (some number sufficiently large that is unlikely to matter)
* Names count to use static import with '*': `50`
* Class count to use import with '*': `9999` (some number sufficiently large that is unlikely to matter)
* Names count to use static import with '*': `9999`
* With java use the following import layout (groovy should still use the default) to ensure consistency with google-java-format:
![import layout](https://user-images.githubusercontent.com/734411/43430811-28442636-94ae-11e8-86f1-f270ddcba023.png)
* [Google Java Format](https://plugins.jetbrains.com/plugin/8527-google-java-format)

View File

@ -140,11 +140,9 @@ public class Agent {
private static synchronized void startAgent(final Instrumentation inst, final URL bootstrapURL) {
if (AGENT_CLASSLOADER == null) {
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
final ClassLoader agentClassLoader =
createAgentClassLoader("auto-tooling-and-instrumentation.isolated", bootstrapURL);
Thread.currentThread().setContextClassLoader(agentClassLoader);
final Class<?> agentInstallerClass =
agentClassLoader.loadClass("io.opentelemetry.auto.tooling.AgentInstaller");
final Method agentInstallerMethod =
@ -153,8 +151,6 @@ public class Agent {
AGENT_CLASSLOADER = agentClassLoader;
} catch (final Throwable ex) {
log.error("Throwable thrown while installing the agent", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
}
}
@ -163,11 +159,9 @@ public class Agent {
if (AGENT_CLASSLOADER == null) {
throw new IllegalStateException("Agent should have been started already");
}
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
// TracerInstaller.installAgentTracer can be called multiple times without any problem
// so there is no need to have a 'agentTracerInstalled' flag here.
try {
Thread.currentThread().setContextClassLoader(AGENT_CLASSLOADER);
// install global tracer
final Class<?> tracerInstallerClass =
AGENT_CLASSLOADER.loadClass("io.opentelemetry.auto.tooling.TracerInstaller");
@ -177,8 +171,6 @@ public class Agent {
logVersionInfoMethod.invoke(null);
} catch (final Throwable ex) {
log.error("Throwable thrown while installing the agent tracer", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
}

View File

@ -150,7 +150,8 @@ public class AgentInstaller {
agentBuilder = agentBuilder.with(listener);
}
int numInstrumenters = 0;
for (final Instrumenter instrumenter : ServiceLoader.load(Instrumenter.class)) {
for (final Instrumenter instrumenter :
ServiceLoader.load(Instrumenter.class, AgentInstaller.class.getClassLoader())) {
log.debug("Loading instrumentation {}", instrumenter.getClass().getName());
try {

View File

@ -0,0 +1,55 @@
// 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 {
// There are some weird library issues below 2.9 so can't assert inverse
pass {
group = 'com.twitter'
module = 'finatra-http_2.11'
versions = '[2.9.0,]'
}
pass {
group = 'com.twitter'
module = 'finatra-http_2.12'
versions = '[2.9.0,]'
}
}
dependencies {
compileOnly group: 'com.twitter', name: 'finatra-http_2.11', version: '2.9.0'
testCompile project(':instrumentation:netty-4.1')
testCompile project(':instrumentation:java-concurrent')
testCompile group: 'com.twitter', name: 'finatra-http_2.11', version: '19.12.0'
testCompile(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.10') {
force = true
}
// Required for older versions of finatra on JDKs >= 11
testCompile group: 'com.sun.activation', name: 'javax.activation', version: '1.2.0'
latestDepTestCompile project(':instrumentation:netty-4.1')
latestDepTestCompile project(':instrumentation:java-concurrent')
latestDepTestCompile group: 'com.twitter', name: 'finatra-http_2.11', version: '+'
}
compileLatestDepTestGroovy {
classpath = classpath.plus(files(compileLatestDepTestScala.destinationDir))
dependsOn compileLatestDepTestScala
}

View File

@ -0,0 +1,51 @@
package datadog.trace.instrumentation.finatra;
import com.twitter.finagle.http.Request;
import com.twitter.finagle.http.Response;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.decorator.HttpServerDecorator;
import io.opentelemetry.trace.Tracer;
import java.net.URI;
import java.net.URISyntaxException;
public class FinatraDecorator extends HttpServerDecorator<Request, Request, Response> {
public static final FinatraDecorator DECORATE = new FinatraDecorator();
public static final Tracer TRACER =
OpenTelemetry.getTracerFactory().get("io.opentelemetry.auto.finatra-2.9");
@Override
protected String getComponentName() {
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();
}
}

View File

@ -0,0 +1,142 @@
package datadog.trace.instrumentation.finatra;
import static datadog.trace.instrumentation.finatra.FinatraDecorator.DECORATE;
import static datadog.trace.instrumentation.finatra.FinatraDecorator.TRACER;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static io.opentelemetry.trace.Span.Kind.SERVER;
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 io.opentelemetry.auto.config.Config;
import io.opentelemetry.auto.instrumentation.api.MoreTags;
import io.opentelemetry.auto.instrumentation.api.SpanWithScope;
import io.opentelemetry.auto.instrumentation.api.Tags;
import io.opentelemetry.auto.tooling.Instrumenter;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Status;
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[] {
"io.opentelemetry.auto.decorator.BaseDecorator",
"io.opentelemetry.auto.decorator.ServerDecorator",
"io.opentelemetry.auto.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 SpanWithScope nameSpan(
@Advice.Argument(0) final Request request,
@Advice.FieldValue("path") final String path,
@Advice.FieldValue("clazz") final Class clazz,
@Advice.Origin final Method method) {
// Update the parent "netty.request"
final Span parent = TRACER.getCurrentSpan();
parent.setAttribute(MoreTags.RESOURCE_NAME, request.method().name() + " " + path);
parent.setAttribute(Tags.COMPONENT, "finatra");
parent.updateName("finatra.request");
final Span span = TRACER.spanBuilder("finatra.controller").setSpanKind(SERVER).startSpan();
DECORATE.afterStart(span);
span.setAttribute(MoreTags.RESOURCE_NAME, DECORATE.spanNameForClass(clazz));
return new SpanWithScope(span, TRACER.withSpan(span));
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void setupCallback(
@Advice.Enter final SpanWithScope spanWithScope,
@Advice.Thrown final Throwable throwable,
@Advice.Return final Some<Future<Response>> responseOption) {
if (spanWithScope == null) {
return;
}
final Span span = spanWithScope.getSpan();
if (throwable != null) {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
span.end();
spanWithScope.closeScope();
return;
}
responseOption.get().addEventListener(new Listener(spanWithScope));
}
}
public static class Listener implements FutureEventListener<Response> {
private final SpanWithScope spanWithScope;
public Listener(final SpanWithScope spanWithScope) {
this.spanWithScope = spanWithScope;
}
@Override
public void onSuccess(final Response response) {
final Span span = spanWithScope.getSpan();
// Don't use DECORATE.onResponse because this is the controller span
if (Config.get().getHttpServerErrorStatuses().contains(DECORATE.status(response))) {
span.setStatus(Status.UNKNOWN);
}
DECORATE.beforeFinish(span);
span.end();
spanWithScope.closeScope();
}
@Override
public void onFailure(final Throwable cause) {
final Span span = spanWithScope.getSpan();
DECORATE.onError(span, cause);
DECORATE.beforeFinish(span);
span.end();
spanWithScope.closeScope();
}
}
}

View File

@ -0,0 +1,119 @@
import com.twitter.finatra.http.HttpServer
import com.twitter.util.Await
import com.twitter.util.Closable
import com.twitter.util.Duration
import datadog.trace.instrumentation.finatra.FinatraDecorator
import io.opentelemetry.auto.instrumentation.api.MoreTags
import io.opentelemetry.auto.instrumentation.api.SpanTypes
import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.test.asserts.TraceAssert
import io.opentelemetry.auto.test.base.HttpServerTest
import io.opentelemetry.sdk.trace.SpanData
import java.util.concurrent.TimeoutException
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static io.opentelemetry.trace.Span.Kind.SERVER
class FinatraServerTest extends HttpServerTest<HttpServer, FinatraDecorator> {
private static final Duration TIMEOUT = Duration.fromSeconds(5)
private static final long STARTUP_TIMEOUT = 20 * 1000
static closeAndWait(Closable closable) {
if (closable != null) {
Await.ready(closable.close(), TIMEOUT)
}
}
@Override
HttpServer startServer(int port) {
HttpServer testServer = new FinatraServer()
// Starting the server is blocking so start it in a separate thread
Thread startupThread = new Thread({
testServer.main("-admin.port=:0", "-http.port=:" + port)
})
startupThread.setDaemon(true)
startupThread.start()
long startupDeadline = System.currentTimeMillis() + STARTUP_TIMEOUT
while (!testServer.started()) {
if (System.currentTimeMillis() > startupDeadline) {
throw new TimeoutException("Timed out waiting for server startup")
}
}
return testServer
}
@Override
boolean hasHandlerSpan() {
return true
}
@Override
void stopServer(HttpServer httpServer) {
Await.ready(httpServer.close(), TIMEOUT)
}
@Override
FinatraDecorator decorator() {
return FinatraDecorator.DECORATE
}
@Override
String expectedOperationName() {
return "finatra.request"
}
@Override
void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
def errorEndpoint = endpoint == EXCEPTION || endpoint == ERROR
trace.span(index) {
operationName "finatra.controller"
spanKind SERVER
errored errorEndpoint
childOf(parent as SpanData)
tags {
"$MoreTags.RESOURCE_NAME" "FinatraController"
"$MoreTags.SPAN_TYPE" "web"
"$Tags.COMPONENT" FinatraDecorator.DECORATE.getComponentName()
// Finatra doesn't propagate the stack trace or exception to the instrumentation
// so the normal errorTags() method can't be used
}
}
}
// need to override in order to add RESOURCE_NAME
@Override
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
operationName expectedOperationName()
spanKind SERVER
errored endpoint.errored
if (parentID != null) {
traceId traceID
parentId parentID
} else {
parent()
}
tags {
"$MoreTags.RESOURCE_NAME" "$method ${endpoint.resolve(address).path}"
"$MoreTags.SPAN_TYPE" SpanTypes.HTTP_SERVER
"$Tags.COMPONENT" serverDecorator.getComponentName()
"$Tags.PEER_HOSTNAME" { it == "localhost" || it == "127.0.0.1" }
"$Tags.PEER_PORT" Long
"$Tags.PEER_HOST_IPV4" { it == null || it == "127.0.0.1" } // Optional
"$Tags.HTTP_URL" "${endpoint.resolve(address)}"
"$Tags.HTTP_METHOD" method
"$Tags.HTTP_STATUS" endpoint.status
if (endpoint.query) {
"$MoreTags.HTTP_QUERY" endpoint.query
}
}
}
}
}

View File

@ -0,0 +1,20 @@
import static net.bytebuddy.matcher.ElementMatchers.named;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.test.base.HttpServerTestAdvice;
import io.opentelemetry.auto.tooling.Instrumenter;
import net.bytebuddy.agent.builder.AgentBuilder;
@AutoService(Instrumenter.class)
public class NettyServerTestInstrumentation implements Instrumenter {
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
.type(named("io.netty.handler.codec.ByteToMessageDecoder"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("channelRead"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
}
}

View File

@ -0,0 +1,56 @@
import com.twitter.finagle.http.{Request, Response}
import com.twitter.finatra.http.Controller
import com.twitter.util.Future
import groovy.lang.Closure
import io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint._
import io.opentelemetry.auto.test.base.HttpServerTest.controller
class FinatraController extends Controller {
any(SUCCESS.getPath) { request: Request =>
controller(SUCCESS, new Closure[Response](null) {
override def call(): Response = {
response.ok(SUCCESS.getBody)
}
})
}
any(ERROR.getPath) { request: Request =>
controller(ERROR, new Closure[Response](null) {
override def call(): Response = {
response.internalServerError(ERROR.getBody)
}
})
}
any(NOT_FOUND.getPath) { request: Request =>
controller(NOT_FOUND, new Closure[Response](null) {
override def call(): Response = {
response.notFound(NOT_FOUND.getBody)
}
})
}
any(QUERY_PARAM.getPath) { request: Request =>
controller(QUERY_PARAM, new Closure[Response](null) {
override def call(): Response = {
response.ok(QUERY_PARAM.getBody)
}
})
}
any(EXCEPTION.getPath) { request: Request =>
controller(EXCEPTION, new Closure[Future[Response]](null) {
override def call(): Future[Response] = {
throw new Exception(EXCEPTION.getBody)
}
})
}
any(REDIRECT.getPath) { request: Request =>
controller(REDIRECT, new Closure[Response](null) {
override def call(): Response = {
response.found.location(REDIRECT.getBody)
}
})
}
}

View File

@ -0,0 +1,13 @@
import com.twitter.finagle.http.Request
import com.twitter.finatra.http.HttpServer
import com.twitter.finatra.http.filters.ExceptionMappingFilter
import com.twitter.finatra.http.routing.HttpRouter
class FinatraServer extends HttpServer {
override protected def configureHttp(router: HttpRouter): Unit = {
router
.filter[ExceptionMappingFilter[Request]]
.add[FinatraController]
.exceptionMapper[ResponseSettingExceptionMapper]
}
}

View File

@ -0,0 +1,15 @@
import com.twitter.finagle.http.{Request, Response}
import com.twitter.finatra.http.exceptions.ExceptionMapper
import com.twitter.finatra.http.response.ResponseBuilder
import javax.inject.{Inject, Singleton}
@Singleton
class ResponseSettingExceptionMapper @Inject()(response: ResponseBuilder)
extends ExceptionMapper[Exception] {
override def toResponse(request: Request, exception: Exception): Response = {
response.internalServerError(exception.getMessage)
}
}

View File

@ -26,10 +26,23 @@ public class ExecutorInstrumentationUtils {
* @return true iff given task object should be wrapped
*/
public static boolean shouldAttachStateToTask(final Object task, final Executor executor) {
if (task == null) {
return false;
}
final Span span = TRACER.getCurrentSpan();
return (span.getContext().isValid()
&& task != null
&& !ExecutorInstrumentationUtils.isExecutorDisabledForThisTask(executor, task));
final Class enclosingClass = task.getClass().getEnclosingClass();
return span.getContext().isValid()
&& !ExecutorInstrumentationUtils.isExecutorDisabledForThisTask(executor, task)
// 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
&& (enclosingClass == null
|| !enclosingClass
.getName()
.equals("io.netty.util.concurrent.SingleThreadEventExecutor"));
}
/**

View File

@ -60,6 +60,7 @@ include ':instrumentation:elasticsearch:transport-2.0'
include ':instrumentation:elasticsearch:transport-5.0'
include ':instrumentation:elasticsearch:transport-5.3'
include ':instrumentation:elasticsearch:transport-6.0'
include ':instrumentation:finatra-2.9'
include ':instrumentation:glassfish-3.0'
include ':instrumentation:google-http-client-1.19'
include ':instrumentation:grizzly-2.0'

View File

@ -123,20 +123,15 @@ public abstract class AgentTestRunner extends AgentSpecification {
}
@BeforeClass
public static synchronized void agentSetup() throws Exception {
public static synchronized void agentSetup() {
if (null != activeTransformer) {
throw new IllegalStateException("transformer already in place: " + activeTransformer);
}
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(AgentTestRunner.class.getClassLoader());
assert ServiceLoader.load(Instrumenter.class).iterator().hasNext()
: "No instrumentation found";
activeTransformer = AgentInstaller.installBytebuddyAgent(INSTRUMENTATION, TEST_LISTENER);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
assert ServiceLoader.load(Instrumenter.class, AgentTestRunner.class.getClassLoader())
.iterator()
.hasNext()
: "No instrumentation found";
activeTransformer = AgentInstaller.installBytebuddyAgent(INSTRUMENTATION, TEST_LISTENER);
INSTRUMENTATION_ERROR_COUNT.set(0);
}

View File

@ -1,20 +0,0 @@
package io.opentelemetry.auto.test.utils
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class OkHttpUtils {
static clientBuilder() {
def unit = TimeUnit.MINUTES
new OkHttpClient.Builder()
.connectTimeout(1, unit)
.writeTimeout(1, unit)
.readTimeout(1, unit)
}
static client(boolean followRedirects = false) {
clientBuilder().followRedirects(followRedirects).build()
}
}

View File

@ -0,0 +1,27 @@
package io.opentelemetry.auto.test.utils;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
/**
* This class was moved from groovy to java because groovy kept trying to introspect on the
* OkHttpClient class which contains java 8 only classes, which caused the build to fail for java 7.
*/
public class OkHttpUtils {
static OkHttpClient.Builder clientBuilder() {
final TimeUnit unit = TimeUnit.MINUTES;
return new OkHttpClient.Builder()
.connectTimeout(1, unit)
.writeTimeout(1, unit)
.readTimeout(1, unit);
}
public static OkHttpClient client() {
return client(false);
}
public static OkHttpClient client(final boolean followRedirects) {
return clientBuilder().followRedirects(followRedirects).build();
}
}

View File

@ -229,7 +229,7 @@ class ServerTest extends AgentTestRunner {
def "server redirect"() {
setup:
client = OkHttpUtils.clientBuilder().followRedirects(followRedirects).build()
client = OkHttpUtils.client(followRedirects)
def server = httpServer {
handlers {
get("/redirect") {