From f53b14bd0fcf60b3ec9f74bcd28de28ad8b31660 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Mon, 8 Jul 2019 14:44:56 -0500 Subject: [PATCH 01/55] Stub google http client integration --- .../google-http-client.gradle | 32 +++++++++++++ .../GoogleHttpClientDecorator.java | 46 +++++++++++++++++++ .../GoogleHttpClientInstrumentation.java | 25 ++++++++++ settings.gradle | 1 + 4 files changed, 104 insertions(+) create mode 100644 dd-java-agent/instrumentation/google-http-client/google-http-client.gradle create mode 100644 dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java create mode 100644 dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java diff --git a/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle b/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle new file mode 100644 index 0000000000..d5ffe59130 --- /dev/null +++ b/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle @@ -0,0 +1,32 @@ +muzzle { + pass { + group = "com.google.http-client" + module = "google-http-client" + versions = "[1.2.0,)" + } +} + +apply from: "${rootDir}/gradle/java.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + compileOnly group: 'com.google.http-client', name: 'google-http-client', version: '1.20.0' + + compile project(':dd-java-agent:agent-tooling') + + compile deps.bytebuddy + compile deps.opentracing + annotationProcessor deps.autoservice + implementation deps.autoservice + + testCompile project(':dd-java-agent:testing') + testCompile group: 'com.google.http-client', name: 'google-http-client', version: '1.20.0' + + latestDepTestCompile group: 'com.google.http-client', name: 'google-http-client', version: '+' +} diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java new file mode 100644 index 0000000000..d9371e0f42 --- /dev/null +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java @@ -0,0 +1,46 @@ +package datadog.trace.instrumentation.apachehttpclient; + +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import datadog.trace.agent.decorator.HttpClientDecorator; +import java.net.URI; +import java.net.URISyntaxException; + +public class GoogleHttpClientDecorator extends HttpClientDecorator { + public static final GoogleHttpClientDecorator DECORATE = new GoogleHttpClientDecorator(); + + @Override + protected String method(final HttpRequest httpRequest) { + return httpRequest.getRequestMethod(); + } + + @Override + protected URI url(final HttpRequest httpRequest) throws URISyntaxException { + return httpRequest.getUrl().toURI(); + } + + @Override + protected String hostname(final HttpRequest httpRequest) { + return httpRequest.getUrl().getHost(); + } + + @Override + protected Integer port(final HttpRequest httpRequest) { + return httpRequest.getUrl().getPort(); + } + + @Override + protected Integer status(final HttpResponse httpResponse) { + return httpResponse.getStatusCode(); + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"google-http-client"}; + } + + @Override + protected String component() { + return "google-http-client"; + } +} diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java new file mode 100644 index 0000000000..de8781eb80 --- /dev/null +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java @@ -0,0 +1,25 @@ +package datadog.trace.instrumentation.apachehttpclient; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import java.util.Map; + +@AutoService(Instrumenter.class) +public class GoogleHttpClientInstrumentation extends Instrumenter.Default { + public GoogleHttpClientInstrumentation() { + super("google-http-client"); + } + + @Override + public ElementMatcher typeMatcher() { + return null; + } + + @Override + public Map, String> transformers() { + return null; + } +} diff --git a/settings.gradle b/settings.gradle index 23b68dd31f..7fa89c512e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -41,6 +41,7 @@ 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:glassfish' +include ':dd-java-agent:instrumentation:google-http-client' include ':dd-java-agent:instrumentation:grpc-1.5' include ':dd-java-agent:instrumentation:hibernate' include ':dd-java-agent:instrumentation:hibernate:core-3.3' From 30916ac5d7424d45f43b6f5c27c177cebe83f370 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Mon, 8 Jul 2019 17:37:14 -0400 Subject: [PATCH 02/55] Implementation --- .../GoogleHttpClientInstrumentation.java | 102 +++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java index de8781eb80..b54b2147eb 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java @@ -1,10 +1,29 @@ package datadog.trace.instrumentation.apachehttpclient; +import static datadog.trace.instrumentation.apachehttpclient.GoogleHttpClientDecorator.DECORATE; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.log.Fields; +import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMap; +import io.opentracing.tag.Tags; +import io.opentracing.util.GlobalTracer; +import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import java.util.Iterator; import java.util.Map; @AutoService(Instrumenter.class) @@ -15,11 +34,90 @@ public class GoogleHttpClientInstrumentation extends Instrumenter.Default { @Override public ElementMatcher typeMatcher() { - return null; + // HttpRequest is a final class. Only need to instrument it exactly + return named("com.google.api.client.http.HttpRequest"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.HttpClientDecorator", + packageName + ".GoogleHttpClientClientDecorator", + getClass().getName() + "$GoogleHttpClientAdvice", + getClass().getName() + "$HeadersInjectAdapter" + }; } @Override public Map, String> transformers() { - return null; + return singletonMap( + isMethod().and(isPublic()).and(named("execute").and(takesArguments(0))), + GoogleHttpClientAdvice.class.getName()); + } + + public static class GoogleHttpClientAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope methodEnter(@Advice.This final HttpRequest request) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(HttpRequest.class); + if (callDepth > 0) { + return null; + } + + final Span span = GlobalTracer.get().buildSpan("http.request").start(); + final Scope scope = GlobalTracer.get().scopeManager().activate(span, false); + DECORATE.afterStart(span); + DECORATE.onRequest(span, request); + GlobalTracer.get() + .inject(span.context(), Format.Builtin.HTTP_HEADERS, new HeadersInjectAdapter(request)); + return scope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.Enter final Scope scope, + @Advice.Return final HttpResponse response, + @Advice.Thrown final Throwable throwable) { + + if (scope != null) { + try { + final Span span = scope.span(); + DECORATE.onResponse(span, response); + + // If HttpRequest.setThrowExceptionOnExecuteError is set to false, there are no exceptions + // for a failed request. Thus, check the response code + if (!response.isSuccessStatusCode()) { + Tags.ERROR.set(span, true); + span.log(singletonMap(Fields.MESSAGE, response.getStatusMessage())); + } + DECORATE.onError(span, throwable); + + DECORATE.beforeFinish(span); + } finally { + scope.close(); + CallDepthThreadLocalMap.reset(HttpRequest.class); + } + } + } + } + + public static class HeadersInjectAdapter implements TextMap { + private final HttpRequest request; + + public HeadersInjectAdapter(final HttpRequest request) { + this.request = request; + } + + @Override + public Iterator> iterator() { + throw new UnsupportedOperationException( + "This class should be used only with tracer#inject()"); + } + + @Override + public void put(final String key, final String value) { + request.getHeaders().put(key, value); + } } } From d97b1c2d536abe5a96d2819facde506bd700f7ed Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Mon, 8 Jul 2019 17:58:42 -0400 Subject: [PATCH 03/55] Fix package --- .../GoogleHttpClientDecorator.java | 2 +- .../GoogleHttpClientInstrumentation.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/{apachehttpclient => googlehttpclient}/GoogleHttpClientDecorator.java (95%) rename dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/{apachehttpclient => googlehttpclient}/GoogleHttpClientInstrumentation.java (97%) diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java similarity index 95% rename from dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java rename to dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java index d9371e0f42..bebdddcf8c 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.apachehttpclient; +package datadog.trace.instrumentation.googlehttpclient; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java similarity index 97% rename from dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java rename to dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java index b54b2147eb..b0e692d6b4 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/apachehttpclient/GoogleHttpClientInstrumentation.java +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java @@ -1,6 +1,6 @@ -package datadog.trace.instrumentation.apachehttpclient; +package datadog.trace.instrumentation.googlehttpclient; -import static datadog.trace.instrumentation.apachehttpclient.GoogleHttpClientDecorator.DECORATE; +import static datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator.DECORATE; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; From caa7e4426a7a15d6133b01c24f06a55e1be4d7b5 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Tue, 9 Jul 2019 15:08:08 -0400 Subject: [PATCH 04/55] Passing tests. Modify escaping of spaces in urls --- .../GoogleHttpClientDecorator.java | 6 +- .../GoogleHttpClientInstrumentation.java | 9 +-- .../test/groovy/GoogleHttpClientTest.groovy | 67 +++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientTest.groovy diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java index bebdddcf8c..4a6d03c1e5 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientDecorator.java @@ -16,7 +16,11 @@ public class GoogleHttpClientDecorator extends HttpClientDecorator { + + @Shared + def requestFactory = new NetHttpTransport().createRequestFactory(); + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + doRequest(method, uri, headers, callback, false); + } + + int doRequest(String method, URI uri, Map headers, Closure callback, boolean throwExceptionOnError) { + GenericUrl genericUrl = new GenericUrl(uri) + + HttpRequest request = requestFactory.buildRequest(method, genericUrl, null) + request.getHeaders().putAll(headers) + request.setThrowExceptionOnExecuteError(throwExceptionOnError) + + HttpResponse response = request.execute() + callback?.call() + + return response.getStatusCode() + } + + @Override + GoogleHttpClientDecorator decorator() { + return GoogleHttpClientDecorator.DECORATE + } + + @Override + boolean testRedirects() { + // Circular redirects don't throw an exception with Google Http Client + return false + } + + def "error traces when exception is not thrown"() { + given: + def uri = server.address.resolve("/error") + + when: + def status = doRequest(method, uri) + + then: + status == 500 + assertTraces(2) { + server.distributedRequestTrace(it, 0, trace(1).last()) + trace(1, size(1)) { + span(0) { + resourceName "$method $uri.path" + spanType DDSpanTypes.HTTP_CLIENT + errored true + } + } + } + + where: + method = "GET" + } +} From 82ee01cadf61cc8f72022e8a24054ca4904e88cc Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 12 Jul 2019 15:41:14 -0400 Subject: [PATCH 05/55] Implement instrumentation for async requests --- .../GoogleHttpClientInstrumentation.java | 113 ++++++++++++++---- .../googlehttpclient/RequestState.java | 10 ++ .../AbstractGoogleHttpClientTest.groovy | 69 +++++++++++ .../groovy/GoogleHttpClientAsyncTest.groovy | 9 ++ .../test/groovy/GoogleHttpClientTest.groovy | 64 +--------- 5 files changed, 182 insertions(+), 83 deletions(-) create mode 100644 dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/RequestState.java create mode 100644 dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy create mode 100644 dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientAsyncTest.groovy diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java index dba8cae9da..d587ab0aba 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/GoogleHttpClientInstrumentation.java @@ -5,13 +5,15 @@ import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.log.Fields; @@ -19,6 +21,8 @@ import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import net.bytebuddy.asm.Advice; @@ -38,6 +42,12 @@ public class GoogleHttpClientInstrumentation extends Instrumenter.Default { return named("com.google.api.client.http.HttpRequest"); } + @Override + public Map contextStore() { + return Collections.singletonMap( + "com.google.api.client.http.HttpRequest", RequestState.class.getName()); + } + @Override public String[] helperClassNames() { return new String[] { @@ -45,45 +55,71 @@ public class GoogleHttpClientInstrumentation extends Instrumenter.Default { "datadog.trace.agent.decorator.ClientDecorator", "datadog.trace.agent.decorator.HttpClientDecorator", packageName + ".GoogleHttpClientDecorator", + packageName + ".RequestState", getClass().getName() + "$GoogleHttpClientAdvice", + getClass().getName() + "$GoogleHttpClientAsyncAdvice", getClass().getName() + "$HeadersInjectAdapter" }; } @Override public Map, String> transformers() { - return singletonMap( - isMethod().and(isPublic()).and(named("execute").and(takesArguments(0))), + final Map, String> transformers = new HashMap<>(); + transformers.put( + isMethod().and(isPublic()).and(named("execute")).and(takesArguments(0)), GoogleHttpClientAdvice.class.getName()); + + transformers.put( + isMethod() + .and(isPublic()) + .and(named("executeAsync")) + .and(takesArguments(1)) + .and(takesArgument(0, (named("java.util.concurrent.Executor")))), + GoogleHttpClientAsyncAdvice.class.getName()); + + return transformers; } public static class GoogleHttpClientAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static Scope methodEnter(@Advice.This final HttpRequest request) { - final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(HttpRequest.class); - if (callDepth > 0) { - return null; + public static void methodEnter(@Advice.This final HttpRequest request) { + + final ContextStore contextStore = + InstrumentationContext.get(HttpRequest.class, RequestState.class); + + RequestState state = contextStore.get(request); + + if (state == null) { + state = new RequestState(GlobalTracer.get().buildSpan("http.request").start()); + contextStore.put(request, state); } - final Span span = GlobalTracer.get().buildSpan("http.request").start(); - final Scope scope = GlobalTracer.get().scopeManager().activate(span, false); - DECORATE.afterStart(span); - DECORATE.onRequest(span, request); - GlobalTracer.get() - .inject(span.context(), Format.Builtin.HTTP_HEADERS, new HeadersInjectAdapter(request)); - return scope; + final Span span = state.getSpan(); + + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { + DECORATE.afterStart(span); + DECORATE.onRequest(span, request); + GlobalTracer.get() + .inject(span.context(), Format.Builtin.HTTP_HEADERS, new HeadersInjectAdapter(request)); + } } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.Enter final Scope scope, + @Advice.This final HttpRequest request, @Advice.Return final HttpResponse response, @Advice.Thrown final Throwable throwable) { - if (scope != null) { - try { - final Span span = scope.span(); + final ContextStore contextStore = + InstrumentationContext.get(HttpRequest.class, RequestState.class); + final RequestState state = contextStore.get(request); + + if (state != null) { + final Span span = state.getSpan(); + + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { DECORATE.onResponse(span, response); + DECORATE.onError(span, throwable); // If HttpRequest.setThrowExceptionOnExecuteError is set to false, there are no exceptions // for a failed request. Thus, check the response code @@ -91,13 +127,46 @@ public class GoogleHttpClientInstrumentation extends Instrumenter.Default { Tags.ERROR.set(span, true); span.log(singletonMap(Fields.MESSAGE, response.getStatusMessage())); } - DECORATE.onError(span, throwable); DECORATE.beforeFinish(span); span.finish(); - } finally { - scope.close(); - CallDepthThreadLocalMap.reset(HttpRequest.class); + } + } + } + } + + public static class GoogleHttpClientAsyncAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter(@Advice.This final HttpRequest request) { + final Span span = GlobalTracer.get().buildSpan("http.request").start(); + + final ContextStore contextStore = + InstrumentationContext.get(HttpRequest.class, RequestState.class); + + final RequestState state = new RequestState(span); + contextStore.put(request, state); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.This final HttpRequest request, @Advice.Thrown final Throwable throwable) { + + if (throwable != null) { + + final ContextStore contextStore = + InstrumentationContext.get(HttpRequest.class, RequestState.class); + final RequestState state = contextStore.get(request); + + if (state != null) { + final Span span = state.getSpan(); + + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { + DECORATE.onError(span, throwable); + + DECORATE.beforeFinish(span); + span.finish(); + } } } } diff --git a/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/RequestState.java b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/RequestState.java new file mode 100644 index 0000000000..8e52df3b72 --- /dev/null +++ b/dd-java-agent/instrumentation/google-http-client/src/main/java/datadog/trace/instrumentation/googlehttpclient/RequestState.java @@ -0,0 +1,10 @@ +package datadog.trace.instrumentation.googlehttpclient; + +import io.opentracing.Span; +import lombok.Data; +import lombok.NonNull; + +@Data +public class RequestState { + @NonNull public Span span; +} diff --git a/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy new file mode 100644 index 0000000000..2dce2bdb81 --- /dev/null +++ b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy @@ -0,0 +1,69 @@ +import com.google.api.client.http.GenericUrl +import com.google.api.client.http.HttpRequest +import com.google.api.client.http.HttpResponse +import com.google.api.client.http.javanet.NetHttpTransport +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.api.DDSpanTypes +import datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator +import spock.lang.Shared + +abstract class AbstractGoogleHttpClientTest extends HttpClientTest { + + @Shared + def requestFactory = new NetHttpTransport().createRequestFactory(); + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + doRequest(method, uri, headers, callback, false); + } + + int doRequest(String method, URI uri, Map headers, Closure callback, boolean throwExceptionOnError) { + GenericUrl genericUrl = new GenericUrl(uri) + + HttpRequest request = requestFactory.buildRequest(method, genericUrl, null) + request.getHeaders().putAll(headers) + request.setThrowExceptionOnExecuteError(throwExceptionOnError) + + HttpResponse response = executeRequest(request); + callback?.call() + + return response.getStatusCode() + } + + abstract HttpResponse executeRequest(HttpRequest request); + + @Override + GoogleHttpClientDecorator decorator() { + return GoogleHttpClientDecorator.DECORATE + } + + @Override + boolean testRedirects() { + // Circular redirects don't throw an exception with Google Http Client + return false + } + + def "error traces when exception is not thrown"() { + given: + def uri = server.address.resolve("/error") + + when: + def status = doRequest(method, uri) + + then: + status == 500 + assertTraces(2) { + server.distributedRequestTrace(it, 0, trace(1).last()) + trace(1, size(1)) { + span(0) { + resourceName "$method $uri.path" + spanType DDSpanTypes.HTTP_CLIENT + errored true + } + } + } + + where: + method = "GET" + } +} diff --git a/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientAsyncTest.groovy b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientAsyncTest.groovy new file mode 100644 index 0000000000..934e31dc54 --- /dev/null +++ b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientAsyncTest.groovy @@ -0,0 +1,9 @@ +import com.google.api.client.http.HttpRequest +import com.google.api.client.http.HttpResponse + +class GoogleHttpClientAsyncTest extends AbstractGoogleHttpClientTest { + @Override + HttpResponse executeRequest(HttpRequest request) { + return request.executeAsync().get() + } +} diff --git a/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientTest.groovy b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientTest.groovy index 944b72dfaf..70272d34e7 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientTest.groovy +++ b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/GoogleHttpClientTest.groovy @@ -1,67 +1,9 @@ -import com.google.api.client.http.GenericUrl import com.google.api.client.http.HttpRequest import com.google.api.client.http.HttpResponse -import com.google.api.client.http.javanet.NetHttpTransport -import datadog.trace.agent.test.base.HttpClientTest -import datadog.trace.api.DDSpanTypes -import datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator -import spock.lang.Shared - -class GoogleHttpClientTest extends HttpClientTest { - - @Shared - def requestFactory = new NetHttpTransport().createRequestFactory(); +class GoogleHttpClientTest extends AbstractGoogleHttpClientTest { @Override - int doRequest(String method, URI uri, Map headers, Closure callback) { - doRequest(method, uri, headers, callback, false); - } - - int doRequest(String method, URI uri, Map headers, Closure callback, boolean throwExceptionOnError) { - GenericUrl genericUrl = new GenericUrl(uri) - - HttpRequest request = requestFactory.buildRequest(method, genericUrl, null) - request.getHeaders().putAll(headers) - request.setThrowExceptionOnExecuteError(throwExceptionOnError) - - HttpResponse response = request.execute() - callback?.call() - - return response.getStatusCode() - } - - @Override - GoogleHttpClientDecorator decorator() { - return GoogleHttpClientDecorator.DECORATE - } - - @Override - boolean testRedirects() { - // Circular redirects don't throw an exception with Google Http Client - return false - } - - def "error traces when exception is not thrown"() { - given: - def uri = server.address.resolve("/error") - - when: - def status = doRequest(method, uri) - - then: - status == 500 - assertTraces(2) { - server.distributedRequestTrace(it, 0, trace(1).last()) - trace(1, size(1)) { - span(0) { - resourceName "$method $uri.path" - spanType DDSpanTypes.HTTP_CLIENT - errored true - } - } - } - - where: - method = "GET" + HttpResponse executeRequest(HttpRequest request) { + return request.execute(); } } From 8a7336a017d7b44238ed0f6c2233003da9932045 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 12 Jul 2019 16:06:19 -0400 Subject: [PATCH 06/55] Use 1.19.0 as first supported version --- .../google-http-client/google-http-client.gradle | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle b/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle index d5ffe59130..b2631fab38 100644 --- a/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle +++ b/dd-java-agent/instrumentation/google-http-client/google-http-client.gradle @@ -2,7 +2,9 @@ muzzle { pass { group = "com.google.http-client" module = "google-http-client" - versions = "[1.2.0,)" + + // 1.19.0 is the first release. The versions before are betas and RCs + versions = "[1.19.0,)" } } @@ -16,7 +18,7 @@ testSets { } dependencies { - compileOnly group: 'com.google.http-client', name: 'google-http-client', version: '1.20.0' + compileOnly group: 'com.google.http-client', name: 'google-http-client', version: '1.19.0' compile project(':dd-java-agent:agent-tooling') @@ -26,7 +28,7 @@ dependencies { implementation deps.autoservice testCompile project(':dd-java-agent:testing') - testCompile group: 'com.google.http-client', name: 'google-http-client', version: '1.20.0' + testCompile group: 'com.google.http-client', name: 'google-http-client', version: '1.19.0' - latestDepTestCompile group: 'com.google.http-client', name: 'google-http-client', version: '+' + latestDepTestCompile group: 'com.google.http-client', name: 'google-http-client', version: '+' } From 0eab4688cd0aca15c8a634a38a98e8be0a69740c Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 12 Jul 2019 16:36:02 -0400 Subject: [PATCH 07/55] Remove unnecessary semicolons --- .../src/test/groovy/AbstractGoogleHttpClientTest.groovy | 8 ++++---- .../src/test/groovy/GoogleHttpClientTest.groovy | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy index 2dce2bdb81..d255ed2642 100644 --- a/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy +++ b/dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy @@ -10,11 +10,11 @@ import spock.lang.Shared abstract class AbstractGoogleHttpClientTest extends HttpClientTest { @Shared - def requestFactory = new NetHttpTransport().createRequestFactory(); + def requestFactory = new NetHttpTransport().createRequestFactory() @Override int doRequest(String method, URI uri, Map headers, Closure callback) { - doRequest(method, uri, headers, callback, false); + doRequest(method, uri, headers, callback, false) } int doRequest(String method, URI uri, Map headers, Closure callback, boolean throwExceptionOnError) { @@ -24,13 +24,13 @@ abstract class AbstractGoogleHttpClientTest extends HttpClientTest Date: Mon, 15 Jul 2019 12:52:57 -0700 Subject: [PATCH 08/55] Remove default instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not very interesting and breaks the definition of “instance” when we want to see the db name when no instance name is defined. --- .../instrumentation/jdbc/JDBCConnectionUrlParser.java | 4 ---- .../src/test/groovy/JDBCConnectionUrlParserTest.groovy | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCConnectionUrlParser.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCConnectionUrlParser.java index 8f5124ad79..a8000ba357 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCConnectionUrlParser.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCConnectionUrlParser.java @@ -295,7 +295,6 @@ public enum JDBCConnectionUrlParser { MSSQLSERVER("microsoft", "sqlserver") { private static final String DEFAULT_HOST = "localhost"; private static final int DEFAULT_PORT = 1433; - private static final String DEFAULT_INSTANCE = "MSSQLSERVER"; @Override DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) { @@ -307,9 +306,6 @@ public enum JDBCConnectionUrlParser { } builder.type("sqlserver"); final DBInfo dbInfo = builder.build(); - if (dbInfo.getInstance() == null) { - builder.instance(DEFAULT_INSTANCE); - } if (dbInfo.getHost() == null) { builder.host(DEFAULT_HOST); } diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCConnectionUrlParserTest.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCConnectionUrlParserTest.groovy index e0fb31854a..b3a662ef55 100644 --- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCConnectionUrlParserTest.groovy +++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCConnectionUrlParserTest.groovy @@ -88,12 +88,12 @@ class JDBCConnectionUrlParserTest extends Specification { "address=(host=anotherhost)(port=3306)(user=wrong)(password=PW)/mdbdb?user=mdbuser&password=PW" | null | "mysql" | "replication" | "mdbuser" | "mdb.host" | 3306 | null | "mdbdb" //https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url - "jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | "MSSQLSERVER" | null - "jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | "MSSQLSERVER" | "stdDatabaseName" + "jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | null | null + "jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName" "jdbc:sqlserver://ss.host\\ssinstance:44;databaseName=ssdb;user=ssuser;password=pw" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "ssinstance" | "ssdb" "jdbc:sqlserver://;serverName=ss.host\\ssinstance:44;DatabaseName=;" | null | "sqlserver" | null | null | "ss.host" | 44 | "ssinstance" | null - "jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | "MSSQLSERVER" | "ssdb" - "jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "MSSQLSERVER" | "ssdb" + "jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | null | "ssdb" + "jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | null | "ssdb" // https://docs.oracle.com/cd/B28359_01/java.111/b31224/urls.htm // https://docs.oracle.com/cd/B28359_01/java.111/b31224/jdbcthin.htm From e08e3a787ce53ff27f6155b09dcd4ce3796dbe30 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Mon, 15 Jul 2019 19:11:51 -0700 Subject: [PATCH 09/55] Allow null to be set in Scope for Span This is useful to temporarily remove a trace from scope for a defined period. --- .../java/datadog/opentracing/DDTracer.java | 2 +- .../scopemanager/ContinuableScope.java | 2 +- .../opentracing/scopemanager/SimpleScope.java | 4 +-- .../opentracing/DDSpanBuilderTest.groovy | 28 +++++++++++++++++++ .../scopemanager/ScopeManagerTest.groovy | 22 +++++++++------ 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java index 74bf6129e7..487f53be66 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java @@ -619,7 +619,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace if (parentContext == null && !ignoreScope) { // use the Scope as parent unless overridden or ignored. final Scope scope = scopeManager.active(); - if (scope != null) { + if (scope != null && scope.span() != null) { parentContext = scope.span().context(); } } diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java index a40126b343..53a788dd9e 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java @@ -48,7 +48,7 @@ public class ContinuableScope implements Scope, TraceScope { this.openCount = openCount; this.continuation = continuation; this.spanUnderScope = spanUnderScope; - this.finishOnClose = finishOnClose; + this.finishOnClose = finishOnClose && spanUnderScope != null; toRestore = scopeManager.tlsScope.get(); scopeManager.tlsScope.set(this); for (final ScopeListener listener : scopeManager.scopeListeners) { diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java index fc43e92fe8..d11194a9c9 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java @@ -17,8 +17,8 @@ public class SimpleScope implements Scope { final boolean finishOnClose) { this.scopeManager = scopeManager; this.spanUnderScope = spanUnderScope; - this.finishOnClose = finishOnClose; - this.toRestore = scopeManager.tlsScope.get(); + this.finishOnClose = finishOnClose && spanUnderScope != null; + toRestore = scopeManager.tlsScope.get(); scopeManager.tlsScope.set(this); for (final ScopeListener listener : scopeManager.scopeListeners) { listener.afterScopeActivated(); diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy index f158c3845b..e7a6da24e2 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy @@ -6,6 +6,7 @@ import datadog.trace.api.Config import datadog.trace.api.DDTags import datadog.trace.api.sampling.PrioritySampling import datadog.trace.common.writer.ListWriter +import io.opentracing.Scope import spock.lang.Specification import static datadog.opentracing.DDSpanContext.ORIGIN_KEY @@ -173,6 +174,33 @@ class DDSpanBuilderTest extends Specification { actualContext.getTraceId() == spanId } + def "should link to parent span implicitly"() { + setup: + final Scope parent = nullParent ? + tracer.scopeManager().activate(null, false) : + tracer.buildSpan("parent") + .startActive(false) + + final String expectedParentId = nullParent ? "0" : parent.span().context().getSpanId() + + final String expectedName = "fakeName" + + final DDSpan span = tracer + .buildSpan(expectedName) + .start() + + final DDSpanContext actualContext = span.context() + + expect: + actualContext.getParentId() == expectedParentId + + cleanup: + parent.close() + + where: + nullParent << [false, true] + } + def "should inherit the DD parent attributes"() { setup: def expectedName = "fakeName" diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy index 4ff52ea087..54afaade40 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy @@ -96,19 +96,19 @@ class ScopeManagerTest extends Specification { def "sets parent as current upon close"() { setup: def parentScope = tracer.buildSpan("parent").startActive(finishSpan) - def childScope = tracer.buildSpan("parent").startActive(finishSpan) + def childScope = nullChild ? tracer.scopeManager().activate(null, finishSpan) : tracer.buildSpan("parent").startActive(finishSpan) expect: scopeManager.active() == childScope - childScope.span().context().parentId == parentScope.span().context().spanId - childScope.span().context().trace == parentScope.span().context().trace + nullChild || childScope.span().context().parentId == parentScope.span().context().spanId + nullChild || childScope.span().context().trace == parentScope.span().context().trace when: childScope.close() then: scopeManager.active() == parentScope - spanFinished(childScope.span()) == finishSpan + spanFinished(childScope.span()) == (nullChild ? null : finishSpan) !spanFinished(parentScope.span()) writer == [] @@ -116,13 +116,17 @@ class ScopeManagerTest extends Specification { parentScope.close() then: - spanFinished(childScope.span()) == finishSpan + spanFinished(childScope.span()) == (nullChild ? null : finishSpan) spanFinished(parentScope.span()) == finishSpan - writer == [[parentScope.span(), childScope.span()]] || !finishSpan + writer == [[parentScope.span(), childScope.span()]] || !finishSpan || nullChild scopeManager.active() == null where: - finishSpan << [true, false] + finishSpan | nullChild + true | false + false | false + true | true + false | true } def "ContinuableScope only creates continuations when propagation is set"() { @@ -567,8 +571,8 @@ class ScopeManagerTest extends Specification { closedCount.get() == 4 } - boolean spanFinished(Span span) { - return ((DDSpan) span).isFinished() + Boolean spanFinished(Span span) { + return ((DDSpan) span)?.isFinished() } class AtomicReferenceScope extends AtomicReference implements ScopeContext, Scope { From 36487a045654959e115a89973e5a8edab636d551 Mon Sep 17 00:00:00 2001 From: Nikolay Martynov Date: Tue, 16 Jul 2019 10:33:07 -0400 Subject: [PATCH 10/55] Remove version from gradle wrapper definition It doesn't look like it is doing anything useful but instead it seems to set version in resulting build jar which is not expected. --- dd-trace-java.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/dd-trace-java.gradle b/dd-trace-java.gradle index 0de18a9544..16a39866f7 100644 --- a/dd-trace-java.gradle +++ b/dd-trace-java.gradle @@ -52,7 +52,6 @@ buildScan { wrapper { distributionType = Wrapper.DistributionType.ALL - version = '5.5.1' } allprojects { From 369658c2008e19cba5bcf8613ca3796466b17414 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Tue, 16 Jul 2019 10:53:07 -0400 Subject: [PATCH 11/55] Initial cgroups parsing implementation --- .../datadog/opentracing/ContainerInfo.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java new file mode 100644 index 0000000000..09c848edfc --- /dev/null +++ b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java @@ -0,0 +1,102 @@ +package datadog.opentracing; + +import lombok.Data; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Data +public class ContainerInfo { + private static final Path CGROUP_DEFAULT_PROCFILE = Paths.get("/proc/self/cgroup"); + private static final String UUID_REGEX = + "[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}"; + private static final String CONTAINER_REGEX = "[0-9a-f]{64}"; + private static final Pattern LINE_PATTERN = Pattern.compile("(\\d+):([^:]*):(.+)$"); + private static final Pattern POD_PATTERN = Pattern.compile("pod(" + UUID_REGEX + ")(?:.slice)?$"); + private static final Pattern CONTAINER_PATTERN = + Pattern.compile("(" + UUID_REGEX + "|" + CONTAINER_REGEX + ")(?:.scope)?$"); + + public String containerId; + public String podId; + public List cGroups; + + @Data + public static class CGroupInfo { + public int id; + public String path; + public List controllers; + public String containerId; + public String podId; + } + + public static boolean isRunningInContainer() { + return Files.isReadable(CGROUP_DEFAULT_PROCFILE); + } + + public static ContainerInfo fromDefaultProcFile() throws IOException, ParseException { + final String content = new String(Files.readAllBytes(CGROUP_DEFAULT_PROCFILE)); + return parse(content); + } + + public static ContainerInfo parse(final String cgroupsContent) throws ParseException { + final ContainerInfo containerInfo = new ContainerInfo(); + + final String[] lines = cgroupsContent.split("\n"); + final List parsedCGroups = new ArrayList<>(); + for (final String line : lines) { + final CGroupInfo cGroupInfo = parseLine(line); + + parsedCGroups.add(cGroupInfo); + + if (cGroupInfo.getPodId() != null) { + containerInfo.setPodId(cGroupInfo.getPodId()); + } + + if (cGroupInfo.getContainerId() != null) { + containerInfo.setContainerId(cGroupInfo.getContainerId()); + } + } + + containerInfo.setCGroups(parsedCGroups); + + return containerInfo; + } + + static CGroupInfo parseLine(final String line) throws ParseException { + final Matcher matcher = LINE_PATTERN.matcher(line); + + if (!matcher.matches()) { + throw new ParseException("Unable to match cgroup", 0); + } + + final CGroupInfo cGroupInfo = new CGroupInfo(); + cGroupInfo.setId(Integer.parseInt(matcher.group(1))); + cGroupInfo.setControllers(Arrays.asList(matcher.group(2).split(","))); + + final String path = matcher.group(3); + final String[] pathParts = path.split("/"); + + cGroupInfo.setPath(path); + + if (pathParts.length >= 1) { + final Matcher containerIdMatcher = CONTAINER_PATTERN.matcher(pathParts[0]); + final String containerId = containerIdMatcher.matches() ? containerIdMatcher.group(1) : null; + cGroupInfo.setContainerId(containerId); + } + + if (pathParts.length >= 2) { + final Matcher podIdMatcher = POD_PATTERN.matcher(pathParts[1]); + final String podId = podIdMatcher.matches() ? podIdMatcher.group(1) : null; + cGroupInfo.setPodId(podId); + } + + return cGroupInfo; + } +} From d74ffc3db02f83b14e9b1acbc61716ab1ed68335 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Tue, 16 Jul 2019 11:44:35 -0400 Subject: [PATCH 12/55] Parsing tests from RFC and reference impl --- .../opentracing/ContainerInfoTest.groovy | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy new file mode 100644 index 0000000000..e5d014e594 --- /dev/null +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy @@ -0,0 +1,148 @@ +package datadog.opentracing + +import spock.lang.Specification +import spock.lang.Unroll + + +class ContainerInfoTest extends Specification { + + @Unroll + def "CGroupInfo is parsed from individual lines"() { + when: + ContainerInfo.CGroupInfo cGroupInfo = ContainerInfo.parseLine(line) + + then: + cGroupInfo.getId() == id + cGroupInfo.getPath() == path + cGroupInfo.getControllers() == controllers + cGroupInfo.getContainerId() == containerId + cGroupInfo.podId == podId + + // Examples from DataDog/architecture/rfcs/apm/agent/containers-tagging/rfc.md and Qard/container-info + where: + id | controllers | path | containerId | podId | line + + // Docker examples + 13 | ["name=systemd"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 12 | ["pids"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 11 | ["hugetlb"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 10 | ["net_prio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 9 | ["perf_event"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 8 | ["net_cls"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 7 | ["freezer"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 6 | ["devices"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 5 | ["memory"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 4 | ["blkio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 3 | ["cpuacct"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 2 | ["cpu"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + 1 | ["cpuset"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" + + // Kubernates examples + 11 | ["perf_event"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 10 | ["pids"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 9 | ["memory"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 8 | ["cpu", "cpuacct"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 7 | ["blkio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 6 | ["cpuset"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 5 | ["devices"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 4 | ["freezer"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 3 | ["net_cls", "net_prop"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 2 | ["hugetlb"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 1 | ["name=systemd"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + + //ECS examples + 9 | ["perf_event"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 8 | ["memory"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 7 | ["hugetlb"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 6 | ["freezer"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 5 | ["devices"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 4 | ["cpuset"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 3 | ["cpuacct"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 2 | ["cpu"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + 1 | ["blkio"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" + + //Fargate Examples + 11 | ["hugetlb"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 10 | ["pids"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 9 | ["cpuset"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 8 | ["net_cls", "net_prio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 7 | ["cpu", "cpuacct"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 6 | ["perf_event"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 5 | ["freezer"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 4 | ["devices"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 3 | ["blkio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 2 | ["memory"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + 1 | ["name=systemd"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" + + //Reference impl examples + 1 | ["name=systemd"] | "/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope" | "cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411" | null | "1:name=systemd:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope" + 1 | ["name=systemd"] | "/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex" | null | null | "1:name=systemd:/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex" + 1 | ["name=systemd"] | "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" | "2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63" | "90d81341_92de_11e7_8cf2_507b9d4141fa" | "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" + + } + + @Unroll + def "Container info parsed from file content"() { + when: + ContainerInfo containerInfo = ContainerInfo.parse(content) + + then: + containerInfo.getContainerId() == containerId + containerInfo.getPodId() == podId + containerInfo.getCGroups().size() == size + + where: + containerId | podId | size | content + // Docker + "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | 13 | """13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860 +1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860""" + + // Kubernetes + "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | 11 | """11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1 +1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1""" + + // ECS + "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | 9 | """9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce +1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce""" + + // Fargate + "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | 11 | """11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da +1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da""" + } +} From 9dbe3a0a17b19b34301cab28172ddbd89e186641 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Tue, 16 Jul 2019 15:31:15 -0400 Subject: [PATCH 13/55] Fix part of path to match --- .../src/main/java/datadog/opentracing/ContainerInfo.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java index 09c848edfc..2cbb7d2ace 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java @@ -12,6 +12,10 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Parses container information from /proc/self/cgroup. Implementation based largely on + * Qard/container-info + */ @Data public class ContainerInfo { private static final Path CGROUP_DEFAULT_PROCFILE = Paths.get("/proc/self/cgroup"); @@ -86,13 +90,13 @@ public class ContainerInfo { cGroupInfo.setPath(path); if (pathParts.length >= 1) { - final Matcher containerIdMatcher = CONTAINER_PATTERN.matcher(pathParts[0]); + final Matcher containerIdMatcher = CONTAINER_PATTERN.matcher(pathParts[pathParts.length - 1]); final String containerId = containerIdMatcher.matches() ? containerIdMatcher.group(1) : null; cGroupInfo.setContainerId(containerId); } if (pathParts.length >= 2) { - final Matcher podIdMatcher = POD_PATTERN.matcher(pathParts[1]); + final Matcher podIdMatcher = POD_PATTERN.matcher(pathParts[pathParts.length - 2]); final String podId = podIdMatcher.matches() ? podIdMatcher.group(1) : null; cGroupInfo.setPodId(podId); } From 48538018133b675e829b8492219c84493cb49a6f Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Tue, 16 Jul 2019 17:21:59 -0400 Subject: [PATCH 14/55] Add container id headers to request builder --- .../datadog/opentracing/ContainerInfo.java | 7 +++--- .../datadog/opentracing/DDTraceOTInfo.java | 16 ++++++++++++++ .../datadog/trace/common/writer/DDApi.java | 22 +++++++++++++------ .../opentracing/ContainerInfoTest.groovy | 2 +- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java index 2cbb7d2ace..5ec79a0b24 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java @@ -1,6 +1,5 @@ package datadog.opentracing; -import lombok.Data; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -11,6 +10,7 @@ import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.Data; /** * Parses container information from /proc/self/cgroup. Implementation based largely on @@ -23,9 +23,10 @@ public class ContainerInfo { "[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}"; private static final String CONTAINER_REGEX = "[0-9a-f]{64}"; private static final Pattern LINE_PATTERN = Pattern.compile("(\\d+):([^:]*):(.+)$"); - private static final Pattern POD_PATTERN = Pattern.compile("pod(" + UUID_REGEX + ")(?:.slice)?$"); + private static final Pattern POD_PATTERN = + Pattern.compile("(?:.+)?pod(" + UUID_REGEX + ")(?:.slice)?$"); private static final Pattern CONTAINER_PATTERN = - Pattern.compile("(" + UUID_REGEX + "|" + CONTAINER_REGEX + ")(?:.scope)?$"); + Pattern.compile("(?:.+)?(" + UUID_REGEX + "|" + CONTAINER_REGEX + ")(?:.scope)?$"); public String containerId; public String podId; diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java index 28840ad8e1..20ce98c057 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java @@ -1,7 +1,9 @@ package datadog.opentracing; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; +import java.text.ParseException; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -13,6 +15,8 @@ public class DDTraceOTInfo { public static final String VERSION; + public static final String CONTAINER_ID; + static { String v; try { @@ -30,6 +34,18 @@ public class DDTraceOTInfo { } VERSION = v; log.info("dd-trace - version: {}", v); + + ContainerInfo containerInfo = null; + + if (ContainerInfo.isRunningInContainer()) { + try { + containerInfo = ContainerInfo.fromDefaultProcFile(); + } catch (final IOException | ParseException e) { + log.error("Unable to parse proc file"); + } + } + + CONTAINER_ID = containerInfo == null ? null : containerInfo.getContainerId(); } public static void main(final String... args) { diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java index 8fa1bd170a..d81ac79a2a 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java @@ -35,6 +35,7 @@ public class DDApi { private static final String DATADOG_META_LANG_INTERPRETER_VENDOR = "Datadog-Meta-Lang-Interpreter-Vendor"; private static final String DATADOG_META_TRACER_VERSION = "Datadog-Meta-Tracer-Version"; + private static final String DATADOG_CONTAINER_ID = "Datadog-Container-ID"; private static final String X_DATADOG_TRACE_COUNT = "X-Datadog-Trace-Count"; private static final int HTTP_TIMEOUT = 1; // 1 second for conenct/read/write operations @@ -261,13 +262,20 @@ public class DDApi { } private static Request.Builder prepareRequest(final HttpUrl url) { - return new Request.Builder() - .url(url) - .addHeader(DATADOG_META_LANG, "java") - .addHeader(DATADOG_META_LANG_VERSION, DDTraceOTInfo.JAVA_VERSION) - .addHeader(DATADOG_META_LANG_INTERPRETER, DDTraceOTInfo.JAVA_VM_NAME) - .addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR) - .addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION); + final Request.Builder builder = + new Request.Builder() + .url(url) + .addHeader(DATADOG_META_LANG, "java") + .addHeader(DATADOG_META_LANG_VERSION, DDTraceOTInfo.JAVA_VERSION) + .addHeader(DATADOG_META_LANG_INTERPRETER, DDTraceOTInfo.JAVA_VM_NAME) + .addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR) + .addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION); + + if (DDTraceOTInfo.CONTAINER_ID == null) { + return builder; + } else { + return builder.addHeader(DATADOG_CONTAINER_ID, DDTraceOTInfo.CONTAINER_ID); + } } @Override diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy index e5d014e594..61190f5bc3 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy @@ -46,7 +46,7 @@ class ContainerInfoTest extends Specification { 6 | ["cpuset"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" 5 | ["devices"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" 4 | ["freezer"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" - 3 | ["net_cls", "net_prop"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" + 3 | ["net_cls", "net_prio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" 2 | ["hugetlb"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" 1 | ["name=systemd"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" From d03a2a95e2f4ba0a457b022451346b9bea736e7e Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Tue, 16 Jul 2019 19:09:00 -0400 Subject: [PATCH 15/55] Change to @Getter/@Setter to be above coverage threshold Since toString(), equals(), and hashCode() are never called, the code coverage for the classes are too low. In the future, we should ignore code coverage of lombok generated methods --- .../src/main/java/datadog/opentracing/ContainerInfo.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java index 5ec79a0b24..47c0b9894a 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java @@ -10,13 +10,15 @@ import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; /** * Parses container information from /proc/self/cgroup. Implementation based largely on * Qard/container-info */ -@Data +@Getter +@Setter public class ContainerInfo { private static final Path CGROUP_DEFAULT_PROCFILE = Paths.get("/proc/self/cgroup"); private static final String UUID_REGEX = @@ -32,7 +34,8 @@ public class ContainerInfo { public String podId; public List cGroups; - @Data + @Getter + @Setter public static class CGroupInfo { public int id; public String path; From 719bf0ad76b08dd7980d861792629aeb8e6a0637 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Wed, 17 Jul 2019 12:00:31 -0400 Subject: [PATCH 16/55] Remove reference to private repo --- .../test/groovy/datadog/opentracing/ContainerInfoTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy index 61190f5bc3..051ab2c9c1 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/ContainerInfoTest.groovy @@ -18,7 +18,7 @@ class ContainerInfoTest extends Specification { cGroupInfo.getContainerId() == containerId cGroupInfo.podId == podId - // Examples from DataDog/architecture/rfcs/apm/agent/containers-tagging/rfc.md and Qard/container-info + // Examples from container tagging rfc and Qard/container-info where: id | controllers | path | containerId | podId | line From 5956f51e07a2d9d44a174542e46456d361121e6d Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Wed, 17 Jul 2019 13:01:08 -0400 Subject: [PATCH 17/55] Change container info to a singleton ContainerInfo DDTraceOTInfo wasn't the best place for container information. Changed ContainerInfo.java to have a static instance --- .../datadog/opentracing/ContainerInfo.java | 28 +++++++++++++++---- .../datadog/opentracing/DDTraceOTInfo.java | 16 ----------- .../datadog/trace/common/writer/DDApi.java | 6 ++-- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java index 47c0b9894a..455bb34969 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java @@ -12,6 +12,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; /** * Parses container information from /proc/self/cgroup. Implementation based largely on @@ -19,6 +20,7 @@ import lombok.Setter; */ @Getter @Setter +@Slf4j public class ContainerInfo { private static final Path CGROUP_DEFAULT_PROCFILE = Paths.get("/proc/self/cgroup"); private static final String UUID_REGEX = @@ -30,9 +32,24 @@ public class ContainerInfo { private static final Pattern CONTAINER_PATTERN = Pattern.compile("(?:.+)?(" + UUID_REGEX + "|" + CONTAINER_REGEX + ")(?:.scope)?$"); + private static final ContainerInfo INSTANCE; + public String containerId; public String podId; - public List cGroups; + public List cGroups = new ArrayList<>(); + + static { + ContainerInfo containerInfo = new ContainerInfo(); + if (ContainerInfo.isRunningInContainer()) { + try { + containerInfo = ContainerInfo.fromDefaultProcFile(); + } catch (final IOException | ParseException e) { + log.error("Unable to parse proc file"); + } + } + + INSTANCE = containerInfo; + } @Getter @Setter @@ -44,6 +61,10 @@ public class ContainerInfo { public String podId; } + public static ContainerInfo get() { + return INSTANCE; + } + public static boolean isRunningInContainer() { return Files.isReadable(CGROUP_DEFAULT_PROCFILE); } @@ -57,11 +78,10 @@ public class ContainerInfo { final ContainerInfo containerInfo = new ContainerInfo(); final String[] lines = cgroupsContent.split("\n"); - final List parsedCGroups = new ArrayList<>(); for (final String line : lines) { final CGroupInfo cGroupInfo = parseLine(line); - parsedCGroups.add(cGroupInfo); + containerInfo.getCGroups().add(cGroupInfo); if (cGroupInfo.getPodId() != null) { containerInfo.setPodId(cGroupInfo.getPodId()); @@ -72,8 +92,6 @@ public class ContainerInfo { } } - containerInfo.setCGroups(parsedCGroups); - return containerInfo; } diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java index 20ce98c057..28840ad8e1 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTraceOTInfo.java @@ -1,9 +1,7 @@ package datadog.opentracing; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; -import java.text.ParseException; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -15,8 +13,6 @@ public class DDTraceOTInfo { public static final String VERSION; - public static final String CONTAINER_ID; - static { String v; try { @@ -34,18 +30,6 @@ public class DDTraceOTInfo { } VERSION = v; log.info("dd-trace - version: {}", v); - - ContainerInfo containerInfo = null; - - if (ContainerInfo.isRunningInContainer()) { - try { - containerInfo = ContainerInfo.fromDefaultProcFile(); - } catch (final IOException | ParseException e) { - log.error("Unable to parse proc file"); - } - } - - CONTAINER_ID = containerInfo == null ? null : containerInfo.getContainerId(); } public static void main(final String... args) { diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java index d81ac79a2a..e3acc6ed5b 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import datadog.opentracing.ContainerInfo; import datadog.opentracing.DDSpan; import datadog.opentracing.DDTraceOTInfo; import datadog.trace.common.writer.unixdomainsockets.UnixDomainSocketFactory; @@ -271,10 +272,11 @@ public class DDApi { .addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR) .addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION); - if (DDTraceOTInfo.CONTAINER_ID == null) { + final String containerId = ContainerInfo.get().getContainerId(); + if (containerId == null) { return builder; } else { - return builder.addHeader(DATADOG_CONTAINER_ID, DDTraceOTInfo.CONTAINER_ID); + return builder.addHeader(DATADOG_CONTAINER_ID, containerId); } } From ab7786d352b6bee1df49981bcf84934559a35f95 Mon Sep 17 00:00:00 2001 From: Nikolay Martynov Date: Wed, 17 Jul 2019 16:58:29 -0400 Subject: [PATCH 18/55] Minor javadoc fix --- .../java/datadog/trace/agent/tooling/ClassLoaderMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java index 59c05548f9..d8275b233a 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java @@ -95,7 +95,7 @@ public class ClassLoaderMatcher { /** * TODO: this turns out to be useless with OSGi: {@code - * }org.eclipse.osgi.internal.loader.BundleLoader#isRequestFromVM} returns {@code true} when + * org.eclipse.osgi.internal.loader.BundleLoader#isRequestFromVM} returns {@code true} when * class loading is issued from this check and {@code false} for 'real' class loads. We should * come up with some sort of hack to avoid this problem. */ From 41a2e7b51ad1139613abb9ce9b8a1ab23c5782de Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Thu, 18 Jul 2019 11:14:59 -0400 Subject: [PATCH 19/55] Cli Application smoke test simple cli application and test --- dd-smoke-tests/README.md | 6 ++-- dd-smoke-tests/cli/cli.gradle | 23 +++++++++++++ .../datadog/smoketest/cli/CliApplication.java | 16 +++++++++ .../smoketest/CliApplicationSmokeTest.groovy | 34 +++++++++++++++++++ settings.gradle | 1 + 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 dd-smoke-tests/cli/cli.gradle create mode 100644 dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java create mode 100644 dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy diff --git a/dd-smoke-tests/README.md b/dd-smoke-tests/README.md index 242579d894..7e8212d37a 100644 --- a/dd-smoke-tests/README.md +++ b/dd-smoke-tests/README.md @@ -1,8 +1,8 @@ # Datadog Smoke Tests -Assert that various application servers will start up with the Datadog JavaAgent without any obvious ill effects. +Assert that various applications will start up with the Datadog JavaAgent without any obvious ill effects. Each subproject underneath `dd-smoke-tests` is a single smoke test. Each test does the following -* Launch the app server with stdout and stderr logged to `$buildDir/reports/server.log` -* Run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response. +* Launch the application with stdout and stderr logged to `$buildDir/reports/server.log` +* For web servers, run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response. Note that there is nothing special about doing 200 requests. 200 is simply an arbitrarily large number to exercise the server. diff --git a/dd-smoke-tests/cli/cli.gradle b/dd-smoke-tests/cli/cli.gradle new file mode 100644 index 0000000000..84b0676780 --- /dev/null +++ b/dd-smoke-tests/cli/cli.gradle @@ -0,0 +1,23 @@ +plugins { + id "com.github.johnrengelman.shadow" version "4.0.4" +} +apply from: "${rootDir}/gradle/java.gradle" +description = 'Command Line Application Smoke Tests.' + +jar { + manifest { + attributes( + 'Main-Class': 'datadog.smoketest.cli.CliApplication' + ) + } +} + +dependencies { + testCompile project(':dd-smoke-tests') +} + +tasks.withType(Test).configureEach { + dependsOn shadowJar + + jvmArgs "-Ddatadog.smoketest.cli.shadowJar.path=${tasks.shadowJar.archivePath}" +} diff --git a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java new file mode 100644 index 0000000000..65e8428d9e --- /dev/null +++ b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java @@ -0,0 +1,16 @@ +package datadog.smoketest.cli; + +/** Simple application that sleeps for a bit than quits. */ +public class CliApplication { + + /** @param args Only argument is the sleep delay (in seconds) */ + public static void main(final String[] args) throws InterruptedException { + final int delay = Integer.parseInt(args[0]); + + System.out.println("Going to shut down after " + delay + "seconds"); + + Thread.sleep(delay * 1000); + + System.out.println("Shutting down"); + } +} diff --git a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy new file mode 100644 index 0000000000..3c6e0b9aac --- /dev/null +++ b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy @@ -0,0 +1,34 @@ +package datadog.smoketest + +import spock.util.concurrent.PollingConditions + +class CliApplicationSmokeTest extends AbstractSmokeTest { + // Estimate for the amount of time instrumentation takes plus a little extra + private static final int INSTRUMENTATION_DELAY = 6 + 5 + + private static final int SHUTDOWN_DELAY = 2 + + @Override + ProcessBuilder createProcessBuilder() { + String cliShadowJar = System.getProperty("datadog.smoketest.cli.shadowJar.path") + + List command = new ArrayList<>() + command.add(javaPath()) + command.addAll(defaultJavaProperties) + command.addAll((String[]) ["-jar", cliShadowJar, String.valueOf(SHUTDOWN_DELAY)]) + ProcessBuilder processBuilder = new ProcessBuilder(command) + processBuilder.directory(new File(buildDirectory)) + } + + def "Cli application process ends before timeout"() { + setup: + def conditions = new PollingConditions(timeout: INSTRUMENTATION_DELAY, initialDelay: SHUTDOWN_DELAY) + + expect: + serverProcess.isAlive() + + conditions.eventually { + assert !serverProcess.isAlive() + } + } +} diff --git a/settings.gradle b/settings.gradle index dcd99258c7..6441d5e934 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ include ':utils:gc-utils' include ':dd-smoke-tests:play' include ':dd-smoke-tests:springboot' include ':dd-smoke-tests:wildfly' +include ':dd-smoke-tests:cli' // instrumentation: include ':dd-java-agent:instrumentation:akka-http-10.0' From e99b974cb31010dbd2e49044c4f3e01ea4cf33fc Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Thu, 18 Jul 2019 15:19:09 -0400 Subject: [PATCH 20/55] Don't use isAlive() in tests and do more in the CLI app --- dd-smoke-tests/cli/cli.gradle | 2 ++ .../datadog/smoketest/cli/CliApplication.java | 29 +++++++++++++++---- .../smoketest/CliApplicationSmokeTest.groovy | 20 +++++-------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/dd-smoke-tests/cli/cli.gradle b/dd-smoke-tests/cli/cli.gradle index 84b0676780..ebc4e66ede 100644 --- a/dd-smoke-tests/cli/cli.gradle +++ b/dd-smoke-tests/cli/cli.gradle @@ -13,6 +13,8 @@ jar { } dependencies { + compile project(':dd-trace-api') + testCompile project(':dd-smoke-tests') } diff --git a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java index 65e8428d9e..7bed7169d4 100644 --- a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java +++ b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java @@ -1,16 +1,33 @@ package datadog.smoketest.cli; -/** Simple application that sleeps for a bit than quits. */ +import datadog.trace.api.Trace; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; + +/** Simple application that makes a request to example.com then quits. */ public class CliApplication { - /** @param args Only argument is the sleep delay (in seconds) */ public static void main(final String[] args) throws InterruptedException { - final int delay = Integer.parseInt(args[0]); + final CliApplication app = new CliApplication(); - System.out.println("Going to shut down after " + delay + "seconds"); + System.out.println("Making request"); - Thread.sleep(delay * 1000); + app.makeRequest(); - System.out.println("Shutting down"); + System.out.println("Finished making request"); + } + + @Trace(operationName = "example") + public void makeRequest() { + try { + final URL url = new URL("http://www.example.com/"); + final URLConnection connection = url.openConnection(); + connection.setConnectTimeout(1000); + connection.connect(); + connection.getInputStream(); + } catch (final IOException e) { + // Ignore. The goal of this app is to attempt the connection not necessarily to succeed + } } } diff --git a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy index 3c6e0b9aac..daf2efa871 100644 --- a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy +++ b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy @@ -1,13 +1,12 @@ package datadog.smoketest -import spock.util.concurrent.PollingConditions + +import java.util.concurrent.TimeUnit class CliApplicationSmokeTest extends AbstractSmokeTest { - // Estimate for the amount of time instrumentation takes plus a little extra - private static final int INSTRUMENTATION_DELAY = 6 + 5 + // Estimate for the amount of time instrumentation, plus request, plus some extra + private static final int TIMEOUT_SECS = 10 - private static final int SHUTDOWN_DELAY = 2 - @Override ProcessBuilder createProcessBuilder() { String cliShadowJar = System.getProperty("datadog.smoketest.cli.shadowJar.path") @@ -15,20 +14,15 @@ class CliApplicationSmokeTest extends AbstractSmokeTest { List command = new ArrayList<>() command.add(javaPath()) command.addAll(defaultJavaProperties) - command.addAll((String[]) ["-jar", cliShadowJar, String.valueOf(SHUTDOWN_DELAY)]) + command.addAll((String[]) ["-jar", cliShadowJar]) ProcessBuilder processBuilder = new ProcessBuilder(command) processBuilder.directory(new File(buildDirectory)) } def "Cli application process ends before timeout"() { - setup: - def conditions = new PollingConditions(timeout: INSTRUMENTATION_DELAY, initialDelay: SHUTDOWN_DELAY) - expect: - serverProcess.isAlive() + assert serverProcess.waitFor(TIMEOUT_SECS, TimeUnit.SECONDS) - conditions.eventually { - assert !serverProcess.isAlive() - } + assert serverProcess.exitValue() == 0 } } From 17d26a49346cd3c64ea0025d681f591e5aead2c5 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 18 Jul 2019 12:38:49 -0700 Subject: [PATCH 21/55] Assert span not null and use NoopSpan instead. --- .../main/java/datadog/opentracing/DDTracer.java | 2 +- .../scopemanager/ContinuableScope.java | 3 ++- .../opentracing/scopemanager/SimpleScope.java | 3 ++- .../datadog/opentracing/DDSpanBuilderTest.groovy | 9 +++++---- .../scopemanager/ScopeManagerTest.groovy | 16 ++++++++-------- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java index 487f53be66..74bf6129e7 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java @@ -619,7 +619,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace if (parentContext == null && !ignoreScope) { // use the Scope as parent unless overridden or ignored. final Scope scope = scopeManager.active(); - if (scope != null && scope.span() != null) { + if (scope != null) { parentContext = scope.span().context(); } } diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java index 53a788dd9e..049a14ad77 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java @@ -44,11 +44,12 @@ public class ContinuableScope implements Scope, TraceScope { final Continuation continuation, final DDSpan spanUnderScope, final boolean finishOnClose) { + assert spanUnderScope != null : "span must not be null"; this.scopeManager = scopeManager; this.openCount = openCount; this.continuation = continuation; this.spanUnderScope = spanUnderScope; - this.finishOnClose = finishOnClose && spanUnderScope != null; + this.finishOnClose = finishOnClose; toRestore = scopeManager.tlsScope.get(); scopeManager.tlsScope.set(this); for (final ScopeListener listener : scopeManager.scopeListeners) { diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java index d11194a9c9..3f97c8a2d7 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java @@ -15,9 +15,10 @@ public class SimpleScope implements Scope { final ContextualScopeManager scopeManager, final Span spanUnderScope, final boolean finishOnClose) { + assert spanUnderScope != null : "span must not be null"; this.scopeManager = scopeManager; this.spanUnderScope = spanUnderScope; - this.finishOnClose = finishOnClose && spanUnderScope != null; + this.finishOnClose = finishOnClose; toRestore = scopeManager.tlsScope.get(); scopeManager.tlsScope.set(this); for (final ScopeListener listener : scopeManager.scopeListeners) { diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy index e7a6da24e2..86452c433d 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy @@ -7,6 +7,7 @@ import datadog.trace.api.DDTags import datadog.trace.api.sampling.PrioritySampling import datadog.trace.common.writer.ListWriter import io.opentracing.Scope +import io.opentracing.noop.NoopSpan import spock.lang.Specification import static datadog.opentracing.DDSpanContext.ORIGIN_KEY @@ -176,12 +177,12 @@ class DDSpanBuilderTest extends Specification { def "should link to parent span implicitly"() { setup: - final Scope parent = nullParent ? - tracer.scopeManager().activate(null, false) : + final Scope parent = noopParent ? + tracer.scopeManager().activate(NoopSpan.INSTANCE, false) : tracer.buildSpan("parent") .startActive(false) - final String expectedParentId = nullParent ? "0" : parent.span().context().getSpanId() + final String expectedParentId = noopParent ? "0" : parent.span().context().getSpanId() final String expectedName = "fakeName" @@ -198,7 +199,7 @@ class DDSpanBuilderTest extends Specification { parent.close() where: - nullParent << [false, true] + noopParent << [false, true] } def "should inherit the DD parent attributes"() { diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy index 54afaade40..80e2eeb879 100644 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy @@ -96,19 +96,19 @@ class ScopeManagerTest extends Specification { def "sets parent as current upon close"() { setup: def parentScope = tracer.buildSpan("parent").startActive(finishSpan) - def childScope = nullChild ? tracer.scopeManager().activate(null, finishSpan) : tracer.buildSpan("parent").startActive(finishSpan) + def childScope = noopChild ? tracer.scopeManager().activate(NoopSpan.INSTANCE, finishSpan) : tracer.buildSpan("parent").startActive(finishSpan) expect: scopeManager.active() == childScope - nullChild || childScope.span().context().parentId == parentScope.span().context().spanId - nullChild || childScope.span().context().trace == parentScope.span().context().trace + noopChild || childScope.span().context().parentId == parentScope.span().context().spanId + noopChild || childScope.span().context().trace == parentScope.span().context().trace when: childScope.close() then: scopeManager.active() == parentScope - spanFinished(childScope.span()) == (nullChild ? null : finishSpan) + noopChild || spanFinished(childScope.span()) == finishSpan !spanFinished(parentScope.span()) writer == [] @@ -116,13 +116,13 @@ class ScopeManagerTest extends Specification { parentScope.close() then: - spanFinished(childScope.span()) == (nullChild ? null : finishSpan) + noopChild || spanFinished(childScope.span()) == finishSpan spanFinished(parentScope.span()) == finishSpan - writer == [[parentScope.span(), childScope.span()]] || !finishSpan || nullChild + writer == [[parentScope.span(), childScope.span()]] || !finishSpan || noopChild scopeManager.active() == null where: - finishSpan | nullChild + finishSpan | noopChild true | false false | false true | true @@ -571,7 +571,7 @@ class ScopeManagerTest extends Specification { closedCount.get() == 4 } - Boolean spanFinished(Span span) { + boolean spanFinished(Span span) { return ((DDSpan) span)?.isFinished() } From 8909e7d4e1846593b70711e29edb907e5b5bfd6d Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Thu, 18 Jul 2019 15:44:22 -0400 Subject: [PATCH 22/55] Java 7 complains about this type --- .../groovy/datadog/smoketest/CliApplicationSmokeTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy index daf2efa871..425fb1095d 100644 --- a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy +++ b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy @@ -5,7 +5,7 @@ import java.util.concurrent.TimeUnit class CliApplicationSmokeTest extends AbstractSmokeTest { // Estimate for the amount of time instrumentation, plus request, plus some extra - private static final int TIMEOUT_SECS = 10 + private static final long TIMEOUT_SECS = 10 @Override ProcessBuilder createProcessBuilder() { From 74099735edee96b09fd0e720dd48787d3b6e9b09 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Thu, 18 Jul 2019 16:28:35 -0400 Subject: [PATCH 23/55] Java 7 doesn't have Process.waitFor(time) so use Spock's timeout --- .../datadog/smoketest/CliApplicationSmokeTest.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy index 425fb1095d..44c44b0e9f 100644 --- a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy +++ b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy @@ -1,11 +1,12 @@ package datadog.smoketest +import spock.lang.Timeout import java.util.concurrent.TimeUnit class CliApplicationSmokeTest extends AbstractSmokeTest { // Estimate for the amount of time instrumentation, plus request, plus some extra - private static final long TIMEOUT_SECS = 10 + private static final int TIMEOUT_SECS = 10 @Override ProcessBuilder createProcessBuilder() { @@ -19,10 +20,9 @@ class CliApplicationSmokeTest extends AbstractSmokeTest { processBuilder.directory(new File(buildDirectory)) } + @Timeout(value = TIMEOUT_SECS, unit = TimeUnit.SECONDS) def "Cli application process ends before timeout"() { expect: - assert serverProcess.waitFor(TIMEOUT_SECS, TimeUnit.SECONDS) - - assert serverProcess.exitValue() == 0 + assert serverProcess.waitFor() == 0 } } From 2627a452c0a43ad0c5f9454151684e9cb27b2cd6 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 18 Jul 2019 23:09:33 +0100 Subject: [PATCH 24/55] unit tests for records(TopicPartition) method of kafka-clients instrumentation. introduce safeguards when instrumenting iterator() so that duplication of traces is not allowed. do not allow subList() to be instrumented --- LICENSE-3rdparty.csv | 1 + .../kafka_clients/TracingIterable.java | 14 ++- .../kafka_clients/TracingList.java | 23 ++--- .../src/test/groovy/KafkaClientTest.groovy | 93 +++++++++++++++++++ 4 files changed, 119 insertions(+), 12 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 2cd62aec64..56bc2c41cd 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -9,3 +9,4 @@ logback.xml,net.logstash.logback,Apache-2.0, import (test),org.junit,EPL-1.0,Copyright © 2002-2017 JUnit. All Rights Reserved. import (test),org.assertj,Apache-2.0,Copyright 2012-2017 the original author or authors. import (test),org.mockito,MIT,Copyright (c) 2007 Mockito contributors +kafka-clients: method records(TopicPartition) partial+test,"Aspect Software, Inc.", Apache-2.0, "Copyright (C) Aspect Software, Inc." diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterable.java b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterable.java index efe80cb1a3..3170472062 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterable.java +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterable.java @@ -7,6 +7,7 @@ public class TracingIterable implements Iterable { private final Iterable delegate; private final String operationName; private final KafkaDecorator decorator; + private boolean firstIterator = true; public TracingIterable( final Iterable delegate, @@ -19,6 +20,17 @@ public class TracingIterable implements Iterable { @Override public Iterator iterator() { - return new TracingIterator(delegate.iterator(), operationName, decorator); + Iterator it; + // We should only return one iterator with tracing. + // However, this is not thread-safe, but usually the first (hopefully only) traversal of + // ConsumerRecords is performed in the same thread that called poll() + if (this.firstIterator) { + it = new TracingIterator(delegate.iterator(), operationName, decorator); + firstIterator = false; + } else { + it = delegate.iterator(); + } + + return it; } } diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java index f7e12cb233..2c6fb8c86f 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java @@ -6,18 +6,15 @@ import java.util.List; import java.util.ListIterator; import org.apache.kafka.clients.consumer.ConsumerRecord; -public class TracingList implements List { +public class TracingList extends TracingIterable implements List { private final List delegate; - private final String operationName; - private final KafkaDecorator decorator; public TracingList( final List delegate, final String operationName, final KafkaDecorator decorator) { + super(delegate, operationName, decorator); this.delegate = delegate; - this.operationName = operationName; - this.decorator = decorator; } @Override @@ -35,11 +32,6 @@ public class TracingList implements List { return delegate.contains(o); } - @Override - public Iterator iterator() { - return new TracingIterator(delegate.iterator(), operationName, decorator); - } - @Override public Object[] toArray() { return delegate.toArray(); @@ -121,6 +113,11 @@ public class TracingList implements List { return delegate.lastIndexOf(o); } + @Override + public Iterator iterator() { + return super.iterator(); + } + @Override public ListIterator listIterator() { // TODO: the API for ListIterator is not really good to instrument it in context of Kafka @@ -137,6 +134,10 @@ public class TracingList implements List { @Override public List subList(final int fromIndex, final int toIndex) { - return new TracingList(delegate.subList(fromIndex, toIndex), operationName, decorator); + // TODO: the API for subList is not really good to instrument it in context of Kafka + // Consumer so we will not do that for now + // Kafka is essentially a sequential commit log. We should only enable tracing when traversing + // sequentially with an iterator + return delegate.subList(fromIndex, toIndex); } } diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy b/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy index b22b6c30e4..094e670b7b 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy @@ -1,5 +1,10 @@ import datadog.trace.agent.test.AgentTestRunner +import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.clients.consumer.ConsumerRecord +import org.apache.kafka.clients.consumer.KafkaConsumer +import org.apache.kafka.clients.producer.KafkaProducer +import org.apache.kafka.clients.producer.ProducerRecord +import org.apache.kafka.common.TopicPartition import org.junit.ClassRule import org.springframework.kafka.core.DefaultKafkaConsumerFactory import org.springframework.kafka.core.DefaultKafkaProducerFactory @@ -119,6 +124,94 @@ class KafkaClientTest extends AgentTestRunner { cleanup: producerFactory.stop() container?.stop() + embeddedKafka.after() + } + + def "test records(TopicPartition) kafka consume"() { + setup: + embeddedKafka.before() + + // set up the Kafka consumer properties + def kafkaPartition = 0 + def consumerProperties = KafkaTestUtils.consumerProps("sender", "false", embeddedKafka) + consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest") + def consumer = new KafkaConsumer(consumerProperties) + + def senderProps = KafkaTestUtils.senderProps(embeddedKafka.getBrokersAsString()) + def producer = new KafkaProducer(senderProps) + + consumer.assign(Arrays.asList(new TopicPartition(SHARED_TOPIC, kafkaPartition))) + + when: + def greeting = "Hello from MockConsumer!" + producer.send(new ProducerRecord(SHARED_TOPIC, kafkaPartition, null, greeting)) + TEST_WRITER.waitForTraces(1) + + then: + + def records = new LinkedBlockingQueue>() + def pollResult = KafkaTestUtils.getRecords(consumer) + + def recs = pollResult.records(new TopicPartition(SHARED_TOPIC, kafkaPartition)).iterator() + + def isFirst = true + while (recs.hasNext()) { + if(!isFirst) { + TEST_WRITER.waitForTraces(1) // ensure consistent ordering of traces + } + records.add(recs.next()) + isFirst = false + } + TEST_WRITER.waitForTraces(1) // ensure consistent ordering of traces + + then: + def first = records.poll(5, TimeUnit.SECONDS) + first.value() == greeting + first.key() == null + + assertTraces(2) { + trace(0, 1) { + // PRODUCER span 0 + span(0) { + serviceName "kafka" + operationName "kafka.produce" + resourceName "Produce Topic $SHARED_TOPIC" + spanType "queue" + errored false + parent() + tags { + "component" "java-kafka" + "span.kind" "producer" + "kafka.partition" { it >= 0 } + defaultTags(true) + } + } + } + trace(1, 1) { + // CONSUMER span 0 + span(0) { + serviceName "kafka" + operationName "kafka.consume" + resourceName "Consume Topic $SHARED_TOPIC" + spanType "queue" + errored false + childOf TEST_WRITER[0][0] + tags { + "component" "java-kafka" + "span.kind" "consumer" + "partition" { it >= 0 } + "offset" 0 + defaultTags(true) + } + } + } + } + + cleanup: + consumer.close() + producer.close() + embeddedKafka.after() + } } From 331b8c8e991ac3eb2ba3cfbf1b7158fc8329e015 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 19 Jul 2019 09:51:37 -0400 Subject: [PATCH 25/55] Add resource name to the @Trace annotation --- .../trace_annotation/TraceAdvice.java | 18 +- .../test/groovy/TraceAnnotationsTest.groovy | 188 ++++++++++++++++++ .../test/trace/annotation/SayTracedHello.java | 34 ++++ .../main/java/datadog/trace/api/Trace.java | 3 + 4 files changed, 240 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java index a070eb9aac..5a8a2d8f55 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java @@ -2,8 +2,10 @@ package datadog.trace.instrumentation.trace_annotation; import static datadog.trace.instrumentation.trace_annotation.TraceDecorator.DECORATE; +import datadog.opentracing.DDTracer; import datadog.trace.api.Trace; import io.opentracing.Scope; +import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; import java.lang.reflect.Method; import net.bytebuddy.asm.Advice; @@ -12,12 +14,22 @@ public class TraceAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static Scope startSpan(@Advice.Origin final Method method) { - final Trace trace = method.getAnnotation(Trace.class); - String operationName = trace == null ? null : trace.operationName(); + final Trace traceAnnotation = method.getAnnotation(Trace.class); + String operationName = traceAnnotation == null ? null : traceAnnotation.operationName(); if (operationName == null || operationName.isEmpty()) { operationName = DECORATE.spanNameForMethod(method); } - return DECORATE.afterStart(GlobalTracer.get().buildSpan(operationName).startActive(true)); + + Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(operationName); + + final String resourceName = traceAnnotation == null ? null : traceAnnotation.resourceName(); + if (resourceName != null + && !resourceName.isEmpty() + && spanBuilder instanceof DDTracer.DDSpanBuilder) { + spanBuilder = ((DDTracer.DDSpanBuilder) spanBuilder).withResourceName(resourceName); + } + + return DECORATE.afterStart(spanBuilder.startActive(true)); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) diff --git a/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceAnnotationsTest.groovy b/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceAnnotationsTest.groovy index 883be47b35..952a99a5bf 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceAnnotationsTest.groovy +++ b/dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceAnnotationsTest.groovy @@ -38,6 +38,77 @@ class TraceAnnotationsTest extends AgentTestRunner { } } + def "test simple case with only operation name set"() { + setup: + // Test single span in new trace + SayTracedHello.sayHA() + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + serviceName "test" + resourceName "SAY_HA" + operationName "SAY_HA" + spanType "DB" + parent() + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + } + } + } + + def "test simple case with only resource name set"() { + setup: + // Test single span in new trace + SayTracedHello.sayHelloOnlyResourceSet() + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + serviceName "test" + resourceName "WORLD" + operationName "SayTracedHello.sayHelloOnlyResourceSet" + parent() + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + } + } + } + + def "test simple case with both resource and operation name set"() { + setup: + // Test single span in new trace + SayTracedHello.sayHAWithResource() + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + serviceName "test" + resourceName "EARTH" + operationName "SAY_HA" + spanType "DB" + parent() + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + } + } + } + def "test complex case annotations"() { when: // Test new trace with 2 children spans @@ -82,6 +153,94 @@ class TraceAnnotationsTest extends AgentTestRunner { } } + def "test complex case with resource name at top level"() { + when: + // Test new trace with 2 children spans + SayTracedHello.sayHELLOsayHAWithResource() + + then: + assertTraces(1) { + trace(0, 3) { + span(0) { + resourceName "WORLD" + operationName "NEW_TRACE" + parent() + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + span(1) { + resourceName "SAY_HA" + operationName "SAY_HA" + spanType "DB" + childOf span(0) + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + span(2) { + serviceName "test" + resourceName "SayTracedHello.sayHello" + operationName "SayTracedHello.sayHello" + childOf span(0) + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + } + } + } + + def "test complex case with resource name at various levels"() { + when: + // Test new trace with 2 children spans + SayTracedHello.sayHELLOsayHAMixedResourceChildren() + + then: + assertTraces(1) { + trace(0, 3) { + span(0) { + resourceName "WORLD" + operationName "NEW_TRACE" + parent() + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + span(1) { + resourceName "EARTH" + operationName "SAY_HA" + spanType "DB" + childOf span(0) + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + span(2) { + serviceName "test" + resourceName "SayTracedHello.sayHello" + operationName "SayTracedHello.sayHello" + childOf span(0) + errored false + tags { + "$Tags.COMPONENT.key" "trace" + defaultTags() + } + } + } + } + } + def "test exception exit"() { setup: @@ -111,6 +270,35 @@ class TraceAnnotationsTest extends AgentTestRunner { } } + def "test exception exit with resource name"() { + setup: + + TEST_TRACER.addDecorator(new ErrorFlag()) + + Throwable error = null + try { + SayTracedHello.sayERRORWithResource() + } catch (final Throwable ex) { + error = ex + } + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + resourceName "WORLD" + operationName "ERROR" + errored true + tags { + "$Tags.COMPONENT.key" "trace" + errorTags(error.class) + defaultTags() + } + } + } + } + } + def "test annonymous class annotations"() { setup: // Test anonymous classes with package. diff --git a/dd-java-agent/instrumentation/trace-annotation/src/test/java/dd/test/trace/annotation/SayTracedHello.java b/dd-java-agent/instrumentation/trace-annotation/src/test/java/dd/test/trace/annotation/SayTracedHello.java index 2163748c7c..087ee25541 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/test/java/dd/test/trace/annotation/SayTracedHello.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/test/java/dd/test/trace/annotation/SayTracedHello.java @@ -15,6 +15,13 @@ public class SayTracedHello { return "hello!"; } + @Trace(resourceName = "WORLD") + public static String sayHelloOnlyResourceSet() { + new StringTag(DDTags.SERVICE_NAME) + .set(GlobalTracer.get().scopeManager().active().span(), "test"); + return "hello!"; + } + @Trace(operationName = "SAY_HA") public static String sayHA() { new StringTag(DDTags.SERVICE_NAME) @@ -23,6 +30,14 @@ public class SayTracedHello { return "HA!!"; } + @Trace(operationName = "SAY_HA", resourceName = "EARTH") + public static String sayHAWithResource() { + new StringTag(DDTags.SERVICE_NAME) + .set(GlobalTracer.get().scopeManager().active().span(), "test"); + new StringTag(DDTags.SPAN_TYPE).set(GlobalTracer.get().scopeManager().active().span(), "DB"); + return "HA EARTH!!"; + } + @Trace(operationName = "NEW_TRACE") public static String sayHELLOsayHA() { new StringTag(DDTags.SERVICE_NAME) @@ -30,11 +45,30 @@ public class SayTracedHello { return sayHello() + sayHA(); } + @Trace(operationName = "NEW_TRACE", resourceName = "WORLD") + public static String sayHELLOsayHAWithResource() { + new StringTag(DDTags.SERVICE_NAME) + .set(GlobalTracer.get().scopeManager().active().span(), "test2"); + return sayHello() + sayHA(); + } + + @Trace(operationName = "NEW_TRACE", resourceName = "WORLD") + public static String sayHELLOsayHAMixedResourceChildren() { + new StringTag(DDTags.SERVICE_NAME) + .set(GlobalTracer.get().scopeManager().active().span(), "test2"); + return sayHello() + sayHAWithResource(); + } + @Trace(operationName = "ERROR") public static String sayERROR() { throw new RuntimeException(); } + @Trace(operationName = "ERROR", resourceName = "WORLD") + public static String sayERRORWithResource() { + throw new RuntimeException(); + } + public static String fromCallable() throws Exception { return new Callable() { @com.newrelic.api.agent.Trace diff --git a/dd-trace-api/src/main/java/datadog/trace/api/Trace.java b/dd-trace-api/src/main/java/datadog/trace/api/Trace.java index 8da196487a..5ae2bd58af 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/Trace.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/Trace.java @@ -13,4 +13,7 @@ public @interface Trace { /** The operation name to set. By default it takes the method's name */ String operationName() default ""; + + /** The resource name. By default it uses the same value as the operation name */ + String resourceName() default ""; } From 1214b90a25421519c1d56602ddd7e7b2ce259efa Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 19 Jul 2019 10:10:24 -0400 Subject: [PATCH 26/55] Add a sleep to ensure everything is started up --- .../src/main/java/datadog/smoketest/cli/CliApplication.java | 3 +++ .../groovy/datadog/smoketest/CliApplicationSmokeTest.groovy | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java index 7bed7169d4..2dc0309d97 100644 --- a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java +++ b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java @@ -11,6 +11,9 @@ public class CliApplication { public static void main(final String[] args) throws InterruptedException { final CliApplication app = new CliApplication(); + // Sleep to ensure all of the processes are running + Thread.sleep(5000); + System.out.println("Making request"); app.makeRequest(); diff --git a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy index 44c44b0e9f..6eed0d4059 100644 --- a/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy +++ b/dd-smoke-tests/cli/src/test/groovy/datadog/smoketest/CliApplicationSmokeTest.groovy @@ -6,7 +6,7 @@ import java.util.concurrent.TimeUnit class CliApplicationSmokeTest extends AbstractSmokeTest { // Estimate for the amount of time instrumentation, plus request, plus some extra - private static final int TIMEOUT_SECS = 10 + private static final int TIMEOUT_SECS = 15 @Override ProcessBuilder createProcessBuilder() { From ef5a006df2ccef508e2455f2e92baa6797977b3c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 19 Jul 2019 16:45:34 +0100 Subject: [PATCH 27/55] changes requested: remove iterator() implementation from TracingList. refactor unit tests to expect only one element to be consumed. Kafka embedded instance as a Rule --- LICENSE-3rdparty.csv | 1 - .../kafka_clients/TracingList.java | 6 ----- .../src/test/groovy/KafkaClientTest.groovy | 25 ++++++------------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 56bc2c41cd..2cd62aec64 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -9,4 +9,3 @@ logback.xml,net.logstash.logback,Apache-2.0, import (test),org.junit,EPL-1.0,Copyright © 2002-2017 JUnit. All Rights Reserved. import (test),org.assertj,Apache-2.0,Copyright 2012-2017 the original author or authors. import (test),org.mockito,MIT,Copyright (c) 2007 Mockito contributors -kafka-clients: method records(TopicPartition) partial+test,"Aspect Software, Inc.", Apache-2.0, "Copyright (C) Aspect Software, Inc." diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java index 2c6fb8c86f..cd8bddbbfd 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingList.java @@ -1,7 +1,6 @@ package datadog.trace.instrumentation.kafka_clients; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.apache.kafka.clients.consumer.ConsumerRecord; @@ -113,11 +112,6 @@ public class TracingList extends TracingIterable implements List return delegate.lastIndexOf(o); } - @Override - public Iterator iterator() { - return super.iterator(); - } - @Override public ListIterator listIterator() { // TODO: the API for ListIterator is not really good to instrument it in context of Kafka diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy b/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy index 094e670b7b..3ea04550aa 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy @@ -5,7 +5,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer import org.apache.kafka.clients.producer.KafkaProducer import org.apache.kafka.clients.producer.ProducerRecord import org.apache.kafka.common.TopicPartition -import org.junit.ClassRule +import org.junit.Rule import org.springframework.kafka.core.DefaultKafkaConsumerFactory import org.springframework.kafka.core.DefaultKafkaProducerFactory import org.springframework.kafka.core.KafkaTemplate @@ -14,7 +14,6 @@ import org.springframework.kafka.listener.MessageListener import org.springframework.kafka.test.rule.KafkaEmbedded import org.springframework.kafka.test.utils.ContainerTestUtils import org.springframework.kafka.test.utils.KafkaTestUtils -import spock.lang.Shared import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit @@ -22,8 +21,7 @@ import java.util.concurrent.TimeUnit class KafkaClientTest extends AgentTestRunner { static final SHARED_TOPIC = "shared.topic" - @Shared - @ClassRule + @Rule KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SHARED_TOPIC) def "test kafka produce and consume"() { @@ -124,12 +122,10 @@ class KafkaClientTest extends AgentTestRunner { cleanup: producerFactory.stop() container?.stop() - embeddedKafka.after() } def "test records(TopicPartition) kafka consume"() { setup: - embeddedKafka.before() // set up the Kafka consumer properties def kafkaPartition = 0 @@ -145,27 +141,21 @@ class KafkaClientTest extends AgentTestRunner { when: def greeting = "Hello from MockConsumer!" producer.send(new ProducerRecord(SHARED_TOPIC, kafkaPartition, null, greeting)) - TEST_WRITER.waitForTraces(1) then: - + TEST_WRITER.waitForTraces(1) def records = new LinkedBlockingQueue>() def pollResult = KafkaTestUtils.getRecords(consumer) def recs = pollResult.records(new TopicPartition(SHARED_TOPIC, kafkaPartition)).iterator() - def isFirst = true - while (recs.hasNext()) { - if(!isFirst) { - TEST_WRITER.waitForTraces(1) // ensure consistent ordering of traces - } - records.add(recs.next()) - isFirst = false + def first = null + if (recs.hasNext()) { + first = recs.next() } - TEST_WRITER.waitForTraces(1) // ensure consistent ordering of traces then: - def first = records.poll(5, TimeUnit.SECONDS) + recs.hasNext() == false first.value() == greeting first.key() == null @@ -210,7 +200,6 @@ class KafkaClientTest extends AgentTestRunner { cleanup: consumer.close() producer.close() - embeddedKafka.after() } From d101bea085bfc1a7ff5beb0d1fdc67fba17d4c49 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 19 Jul 2019 12:11:10 -0400 Subject: [PATCH 28/55] Add DDSpanBuilder to helper classes of both instrumentations --- .../trace_annotation/TraceAnnotationsInstrumentation.java | 4 +++- .../trace_annotation/TraceConfigInstrumentation.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java index f2c1516957..de849c4529 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java @@ -85,7 +85,9 @@ public final class TraceAnnotationsInstrumentation extends Instrumenter.Default @Override public String[] helperClassNames() { return new String[] { - "datadog.trace.agent.decorator.BaseDecorator", packageName + ".TraceDecorator", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.opentracing.DDTracer$DDSpanBuilder", + packageName + ".TraceDecorator", }; } diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java index 2365b54782..9600f54b5f 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java @@ -123,7 +123,9 @@ public class TraceConfigInstrumentation implements Instrumenter { @Override public String[] helperClassNames() { return new String[] { - "datadog.trace.agent.decorator.BaseDecorator", packageName + ".TraceDecorator", + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.opentracing.DDTracer$DDSpanBuilder", + packageName + ".TraceDecorator", }; } From cad2a3d4a410551f2593ddfbd39be6777a9f1c46 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 19 Jul 2019 12:26:17 -0400 Subject: [PATCH 29/55] Set resource name in a way that doesn't need to reference DDSpanBuilder --- .../instrumentation/trace_annotation/TraceAdvice.java | 8 +++----- .../trace_annotation/TraceAnnotationsInstrumentation.java | 4 +--- .../trace_annotation/TraceConfigInstrumentation.java | 4 +--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java index 5a8a2d8f55..ed4d2ffb30 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAdvice.java @@ -2,7 +2,7 @@ package datadog.trace.instrumentation.trace_annotation; import static datadog.trace.instrumentation.trace_annotation.TraceDecorator.DECORATE; -import datadog.opentracing.DDTracer; +import datadog.trace.api.DDTags; import datadog.trace.api.Trace; import io.opentracing.Scope; import io.opentracing.Tracer; @@ -23,10 +23,8 @@ public class TraceAdvice { Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(operationName); final String resourceName = traceAnnotation == null ? null : traceAnnotation.resourceName(); - if (resourceName != null - && !resourceName.isEmpty() - && spanBuilder instanceof DDTracer.DDSpanBuilder) { - spanBuilder = ((DDTracer.DDSpanBuilder) spanBuilder).withResourceName(resourceName); + if (resourceName != null && !resourceName.isEmpty()) { + spanBuilder = spanBuilder.withTag(DDTags.RESOURCE_NAME, resourceName); } return DECORATE.afterStart(spanBuilder.startActive(true)); diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java index de849c4529..f2c1516957 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceAnnotationsInstrumentation.java @@ -85,9 +85,7 @@ public final class TraceAnnotationsInstrumentation extends Instrumenter.Default @Override public String[] helperClassNames() { return new String[] { - "datadog.trace.agent.decorator.BaseDecorator", - "datadog.opentracing.DDTracer$DDSpanBuilder", - packageName + ".TraceDecorator", + "datadog.trace.agent.decorator.BaseDecorator", packageName + ".TraceDecorator", }; } diff --git a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java index 9600f54b5f..2365b54782 100644 --- a/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java +++ b/dd-java-agent/instrumentation/trace-annotation/src/main/java/datadog/trace/instrumentation/trace_annotation/TraceConfigInstrumentation.java @@ -123,9 +123,7 @@ public class TraceConfigInstrumentation implements Instrumenter { @Override public String[] helperClassNames() { return new String[] { - "datadog.trace.agent.decorator.BaseDecorator", - "datadog.opentracing.DDTracer$DDSpanBuilder", - packageName + ".TraceDecorator", + "datadog.trace.agent.decorator.BaseDecorator", packageName + ".TraceDecorator", }; } From 6ccb0d71d8f819a63e66a34042b786c490e9ccdb Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Fri, 14 Jun 2019 17:13:20 -0700 Subject: [PATCH 30/55] Change the scope for the netty client callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the scope was the http client span, which could result in deep nesting. Now it is the parent span. Before [——————Parent—————] [ ^ ———Client—————] [ ^—Child—] Now: [——————Parent—————] [ ^ —Client—] [ ^—Child—] Also improve the tests. --- .../netty40/AttributeKeys.java | 3 + .../HttpClientRequestTracingHandler.java | 15 +- .../HttpClientResponseTracingHandler.java | 40 ++--- .../src/test/groovy/Netty40ClientTest.groovy | 13 +- .../netty41/AttributeKeys.java | 3 + .../HttpClientRequestTracingHandler.java | 15 +- .../HttpClientResponseTracingHandler.java | 40 ++--- .../src/test/groovy/Netty41ClientTest.groovy | 13 +- .../src/test/groovy/RatpackTest.groovy | 2 +- .../test/groovy/VertxHttpClientTest.groovy | 157 ++++-------------- .../src/test/groovy/VertxServerTest.groovy | 48 +++++- .../src/test/java/VertxWebTestServer.java | 54 ++++-- .../instrumentation/vertx/vertx.gradle | 4 +- .../agent/test/base/HttpClientTest.groovy | 12 +- .../trace/agent/test/utils/TraceUtils.groovy | 9 +- 15 files changed, 202 insertions(+), 226 deletions(-) diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java index b1b1ecaafa..7ad60405c9 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java @@ -16,4 +16,7 @@ public class AttributeKeys { public static final AttributeKey CLIENT_ATTRIBUTE_KEY = new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".span"); + + public static final AttributeKey CLIENT_PARENT_ATTRIBUTE_KEY = + new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".parent"); } diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java index 6dbb99246a..1e9ea59338 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java @@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.HttpRequest; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.util.GlobalTracer; import java.net.InetSocketAddress; @@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt final HttpRequest request = (HttpRequest) msg; - final Span span = GlobalTracer.get().buildSpan("netty.client.request").start(); - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { + final Tracer tracer = GlobalTracer.get(); + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan()); + + final Span span = tracer.buildSpan("netty.client.request").start(); + try (final Scope scope = tracer.scopeManager().activate(span, false)) { DECORATE.afterStart(span); DECORATE.onRequest(span, request); DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress()); // AWS calls are often signed, so we can't add headers without breaking the signature. if (!request.headers().contains("amz-sdk-invocation-id")) { - GlobalTracer.get() - .inject( - span.context(), - Format.Builtin.HTTP_HEADERS, - new NettyResponseInjectAdapter(request)); + tracer.inject( + span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request)); } ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span); diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java index cb6979e17d..6dedbb0157 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java @@ -9,42 +9,34 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpResponse; import io.opentracing.Scope; import io.opentracing.Span; -import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { + final Span parent = ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).get(); final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get(); - if (span == null) { - ctx.fireChannelRead(msg); - return; - } - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { - final boolean finishSpan = msg instanceof HttpResponse; + final boolean finishSpan = msg instanceof HttpResponse; - if (scope instanceof TraceScope) { - ((TraceScope) scope).setAsyncPropagation(true); - } - try { - ctx.fireChannelRead(msg); - } catch (final Throwable throwable) { - if (finishSpan) { - DECORATE.onError(span, throwable); - DECORATE.beforeFinish(span); - Tags.HTTP_STATUS.set(span, 500); - span.finish(); // Finish the span manually since finishSpanOnClose was false - throw throwable; - } - } - - if (finishSpan) { + if (span != null && finishSpan) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, true)) { DECORATE.onResponse(span, (HttpResponse) msg); DECORATE.beforeFinish(span); - span.finish(); // Finish the span manually since finishSpanOnClose was false } } + + // We want the callback in the scope of the parent, not the client span + if (parent != null) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + ctx.fireChannelRead(msg); + } + } else { + ctx.fireChannelRead(msg); + } } } diff --git a/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy b/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy index cdf7c74d77..0e08c2244a 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy +++ b/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy @@ -1,8 +1,10 @@ import datadog.trace.agent.test.base.HttpClientTest import datadog.trace.instrumentation.netty40.client.NettyHttpClientDecorator import io.opentracing.tag.Tags +import org.asynchttpclient.AsyncCompletionHandler import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.DefaultAsyncHttpClientConfig +import org.asynchttpclient.Response import spock.lang.Shared import java.util.concurrent.ExecutionException @@ -25,8 +27,13 @@ class Netty40ClientTest extends HttpClientTest { def methodName = "prepare" + method.toLowerCase().capitalize() def requestBuilder = asyncHttpClient."$methodName"(uri.toString()) headers.each { requestBuilder.setHeader(it.key, it.value) } - def response = requestBuilder.execute().get() - callback?.call() + def response = requestBuilder.execute(new AsyncCompletionHandler() { + @Override + Object onCompleted(Response response) throws Exception { + callback?.call() + return response + } + }).get() return response.statusCode } @@ -66,7 +73,7 @@ class Netty40ClientTest extends HttpClientTest { and: assertTraces(1) { trace(0, 2) { - basicSpan(it, 0, "parent", thrownException) + basicSpan(it, 0, "parent", null, thrownException) span(1) { operationName "netty.connect" diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java index 9a8ea9a0a0..e4eb615992 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java @@ -20,4 +20,7 @@ public class AttributeKeys { public static final AttributeKey CLIENT_ATTRIBUTE_KEY = AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".span"); + + public static final AttributeKey CLIENT_PARENT_ATTRIBUTE_KEY = + AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".parent"); } diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java index 5cc082ec8c..d309c96dfa 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java @@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.HttpRequest; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.util.GlobalTracer; import java.net.InetSocketAddress; @@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt final HttpRequest request = (HttpRequest) msg; - final Span span = GlobalTracer.get().buildSpan("netty.client.request").start(); - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { + final Tracer tracer = GlobalTracer.get(); + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan()); + + final Span span = tracer.buildSpan("netty.client.request").start(); + try (final Scope scope = tracer.scopeManager().activate(span, false)) { DECORATE.afterStart(span); DECORATE.onRequest(span, request); DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress()); // AWS calls are often signed, so we can't add headers without breaking the signature. if (!request.headers().contains("amz-sdk-invocation-id")) { - GlobalTracer.get() - .inject( - span.context(), - Format.Builtin.HTTP_HEADERS, - new NettyResponseInjectAdapter(request)); + tracer.inject( + span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request)); } ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span); diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java index a82d8e6eb4..a08926ec0e 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java @@ -9,42 +9,34 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpResponse; import io.opentracing.Scope; import io.opentracing.Span; -import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { + final Span parent = ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).get(); final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get(); - if (span == null) { - ctx.fireChannelRead(msg); - return; - } - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { - final boolean finishSpan = msg instanceof HttpResponse; + final boolean finishSpan = msg instanceof HttpResponse; - if (scope instanceof TraceScope) { - ((TraceScope) scope).setAsyncPropagation(true); - } - try { - ctx.fireChannelRead(msg); - } catch (final Throwable throwable) { - if (finishSpan) { - DECORATE.onError(span, throwable); - DECORATE.beforeFinish(span); - Tags.HTTP_STATUS.set(span, 500); - span.finish(); // Finish the span manually since finishSpanOnClose was false - throw throwable; - } - } - - if (finishSpan) { + if (span != null && finishSpan) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, true)) { DECORATE.onResponse(span, (HttpResponse) msg); DECORATE.beforeFinish(span); - span.finish(); // Finish the span manually since finishSpanOnClose was false } } + + // We want the callback in the scope of the parent, not the client span + if (parent != null) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + ctx.fireChannelRead(msg); + } + } else { + ctx.fireChannelRead(msg); + } } } diff --git a/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy b/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy index 99ccbe3fd5..ea0e0fb2c4 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy +++ b/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy @@ -9,8 +9,10 @@ import io.netty.channel.ChannelInitializer import io.netty.channel.embedded.EmbeddedChannel import io.netty.handler.codec.http.HttpClientCodec import io.opentracing.tag.Tags +import org.asynchttpclient.AsyncCompletionHandler import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.DefaultAsyncHttpClientConfig +import org.asynchttpclient.Response import spock.lang.Shared import java.util.concurrent.ExecutionException @@ -33,8 +35,13 @@ class Netty41ClientTest extends HttpClientTest { def methodName = "prepare" + method.toLowerCase().capitalize() def requestBuilder = asyncHttpClient."$methodName"(uri.toString()) headers.each { requestBuilder.setHeader(it.key, it.value) } - def response = requestBuilder.execute().get() - callback?.call() + def response = requestBuilder.execute(new AsyncCompletionHandler() { + @Override + Object onCompleted(Response response) throws Exception { + callback?.call() + return response + } + }).get() return response.statusCode } @@ -75,7 +82,7 @@ class Netty41ClientTest extends HttpClientTest { and: assertTraces(1) { trace(0, 2) { - basicSpan(it, 0, "parent", thrownException) + basicSpan(it, 0, "parent", null, thrownException) span(1) { operationName "netty.connect" diff --git a/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy b/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy index d97795e5d0..a27f60cff8 100644 --- a/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy +++ b/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy @@ -429,7 +429,7 @@ class RatpackTest extends AgentTestRunner { serviceName "unnamed-java-app" operationName "netty.client.request" spanType DDSpanTypes.HTTP_CLIENT - childOf(span(3)) + childOf(span(1)) errored false tags { "$Tags.COMPONENT.key" "netty-client" diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy index 2875bb8269..0fc2aa4e97 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy @@ -1,151 +1,54 @@ -import datadog.trace.agent.test.AgentTestRunner -import datadog.trace.agent.test.utils.PortUtils -import datadog.trace.api.DDSpanTypes -import io.netty.channel.AbstractChannel -import io.opentracing.tag.Tags +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator import io.vertx.core.Vertx import io.vertx.core.VertxOptions import io.vertx.core.http.HttpClient -import io.vertx.core.http.HttpClientRequest import io.vertx.core.http.HttpClientResponse import io.vertx.core.http.HttpMethod -import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.CompletableFuture -import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -class VertxHttpClientTest extends AgentTestRunner { - - private static final String MESSAGE = "hello world" - - @AutoCleanup - @Shared - def server = httpServer { - handlers { - prefix("success") { - handleDistributedRequest() - - response.status(200).send(MESSAGE) - } - - prefix("error") { - handleDistributedRequest() - - throw new RuntimeException("error") - } - } - } +@Timeout(10) +class VertxHttpClientTest extends HttpClientTest { @Shared Vertx vertx = Vertx.vertx(new VertxOptions()) @Shared HttpClient httpClient = vertx.createHttpClient() - def "#route request trace"() { - setup: - def responseFuture = new CompletableFuture() - def messageFuture = new CompletableFuture() - httpClient.getNow(server.address.port, server.address.host, "/" + route, { response -> - responseFuture.complete(response) - response.bodyHandler({ buffer -> - messageFuture.complete(buffer.toString()) - }) - }) - - when: - HttpClientResponse response = responseFuture.get() - String message = messageFuture.get() - - then: - response.statusCode() == expectedStatus - if (expectedMessage != null) { - message == expectedMessage + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + CompletableFuture future = new CompletableFuture<>() + def request = httpClient.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri") + headers.each { request.putHeader(it.key, it.value) } + request.handler { response -> + callback?.call() + future.complete(response) } + request.end() - assertTraces(2) { - server.distributedRequestTrace(it, 0, TEST_WRITER[1][0]) - trace(1, 1) { - span(0) { - parent() - serviceName "unnamed-java-app" - operationName "netty.client.request" - resourceName "GET /$route" - spanType DDSpanTypes.HTTP_CLIENT - errored expectedError - tags { - defaultTags() - "$Tags.HTTP_STATUS.key" expectedStatus - "$Tags.HTTP_URL.key" "${server.address}/$route" - "$Tags.PEER_HOSTNAME.key" server.address.host - "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" - "$Tags.PEER_PORT.key" server.address.port - "$Tags.HTTP_METHOD.key" "GET" - "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT - "$Tags.COMPONENT.key" "netty-client" - if (expectedError) { - "$Tags.ERROR.key" true - } - } - } - } - } - - where: - route | expectedStatus | expectedError | expectedMessage - "success" | 200 | false | MESSAGE - "error" | 500 | true | null + return future.get().statusCode() } - def "test connection failure"() { - setup: - def invalidPort = PortUtils.randomOpenPort() + @Override + NettyHttpClientDecorator decorator() { + return NettyHttpClientDecorator.DECORATE + } - def errorFuture = new CompletableFuture() + @Override + String expectedOperationName() { + return "netty.client.request" + } - runUnderTrace("parent") { - HttpClientRequest request = httpClient.request( - HttpMethod.GET, - invalidPort, - server.address.host, - "/", - { response -> - // We expect to never get here since our request is expected to fail - errorFuture.complete(null) - }) - request.exceptionHandler({ error -> - errorFuture.complete(error) - }) - request.end() - } + @Override + boolean testRedirects() { + false + } - when: - def throwable = errorFuture.get() - - then: - throwable.cause instanceof ConnectException - - and: - assertTraces(1) { - trace(0, 2) { - span(0) { - operationName "parent" - parent() - } - span(1) { - operationName "netty.connect" - resourceName "netty.connect" - childOf span(0) - errored true - tags { - "$Tags.COMPONENT.key" "netty" - errorTags AbstractChannel.AnnotatedConnectException, "Connection refused: localhost/127.0.0.1:$invalidPort" - defaultTags() - } - } - } - } + @Override + boolean testConnectionFailure() { + false } } diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy index 83a7cf6091..eff0eeab0e 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy @@ -31,7 +31,7 @@ class VertxServerTest extends AgentTestRunner { def "test server request/response"() { setup: def request = new Request.Builder() - .url("http://localhost:$port/test") + .url("http://localhost:$port/proxy") .header("x-datadog-trace-id", "123") .header("x-datadog-parent-id", "456") .get() @@ -43,14 +43,13 @@ class VertxServerTest extends AgentTestRunner { response.body().string() == "Hello World" and: - assertTraces(1) { + assertTraces(2) { trace(0, 2) { span(0) { - traceId "123" - parentId "456" serviceName "unnamed-java-app" operationName "netty.request" resourceName "GET /test" + childOf(trace(1).get(1)) spanType DDSpanTypes.HTTP_SERVER errored false tags { @@ -70,6 +69,47 @@ class VertxServerTest extends AgentTestRunner { assert span(1).operationName.endsWith('.tracedMethod') } } + trace(1, 2) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName "GET /proxy" + traceId "123" + parentId "456" + spanType DDSpanTypes.HTTP_SERVER + errored false + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/proxy" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + defaultTags(true) + } + } + span(1) { + serviceName "unnamed-java-app" + operationName "netty.client.request" + resourceName "GET /test" + childOf(span(0)) + spanType DDSpanTypes.HTTP_CLIENT + errored false + tags { + "$Tags.COMPONENT.key" "netty-client" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/test" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } } } diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java index 7a1d0348af..085dae95ca 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java +++ b/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java @@ -1,25 +1,36 @@ import datadog.trace.api.Trace; import io.vertx.core.AbstractVerticle; +import io.vertx.core.DeploymentOptions; import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpClient; +import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class VertxWebTestServer extends AbstractVerticle { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; public static Vertx start(final int port) throws ExecutionException, InterruptedException { /* This is highly against Vertx ideas, but our tests are synchronous so we have to make sure server is up and running */ final CompletableFuture future = new CompletableFuture<>(); - final Vertx vertx = Vertx.vertx(new VertxOptions()); + final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); + vertx.deployVerticle( - new VertxWebTestServer(port), + VertxWebTestServer.class.getName(), + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3), res -> { if (!res.succeeded()) { - throw new RuntimeException("Cannot deploy server Verticle"); + throw new RuntimeException("Cannot deploy server Verticle", res.cause()); } future.complete(null); }); @@ -29,14 +40,12 @@ public class VertxWebTestServer extends AbstractVerticle { return vertx; } - private final int port; - - public VertxWebTestServer(final int port) { - this.port = port; - } - @Override public void start(final Future startFuture) { + final HttpClient client = vertx.createHttpClient(); + + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); + final Router router = Router.router(vertx); router @@ -51,6 +60,26 @@ public class VertxWebTestServer extends AbstractVerticle { routingContext -> { routingContext.response().setStatusCode(500).end(); }); + router + .route("/proxy") + .handler( + routingContext -> { + client + .get( + port, + "localhost", + "/test", + response -> { + response.bodyHandler( + buffer -> { + routingContext + .response() + .setStatusCode(response.statusCode()) + .end(buffer); + }); + }) + .end(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())); + }); router .route("/test") .handler( @@ -58,10 +87,7 @@ public class VertxWebTestServer extends AbstractVerticle { tracedMethod(); routingContext.next(); }) - .blockingHandler( - routingContext -> { - routingContext.next(); - }) + .blockingHandler(RoutingContext::next) .handler( routingContext -> { routingContext.response().putHeader("content-type", "text/html").end("Hello World"); @@ -74,5 +100,5 @@ public class VertxWebTestServer extends AbstractVerticle { } @Trace - public void tracedMethod() {} + private void tracedMethod() {} } diff --git a/dd-java-agent/instrumentation/vertx/vertx.gradle b/dd-java-agent/instrumentation/vertx/vertx.gradle index 75c0999ba6..df1f0e7ea6 100644 --- a/dd-java-agent/instrumentation/vertx/vertx.gradle +++ b/dd-java-agent/instrumentation/vertx/vertx.gradle @@ -42,9 +42,11 @@ dependencies { implementation deps.autoservice testCompile project(':dd-java-agent:testing') + 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 project(':dd-java-agent:instrumentation:netty-4.1') + + // Tests seem to fail before 3.5... maybe a problem with some of the tests? testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0' latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+' diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy index 23c98b1663..c3b1a440f5 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy @@ -176,10 +176,7 @@ abstract class HttpClientTest extends AgentTestRu assertTraces(1) { trace(0, size(3)) { basicSpan(it, 0, "parent") - span(1) { - operationName "child" - childOf span(0) - } + basicSpan(it, 1, "child", span(0)) clientSpan(it, 2, span(0), method, false) } } @@ -205,10 +202,7 @@ abstract class HttpClientTest extends AgentTestRu clientSpan(it, 0, null, method, false) } trace(1, 1) { - span(0) { - operationName "child" - parent() - } + basicSpan(it, 0, "child") } } @@ -306,7 +300,7 @@ abstract class HttpClientTest extends AgentTestRu and: assertTraces(1) { trace(0, 2) { - basicSpan(it, 0, "parent", thrownException) + basicSpan(it, 0, "parent", null, thrownException) clientSpan(it, 1, span(0), method, false, false, uri, null, thrownException) } } diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy index c349504cb1..a05d97a90c 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy @@ -1,5 +1,6 @@ package datadog.trace.agent.test.utils +import datadog.opentracing.DDSpan import datadog.trace.agent.decorator.BaseDecorator import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.context.TraceScope @@ -42,9 +43,13 @@ class TraceUtils { } } - static basicSpan(TraceAssert trace, int index, String spanName, Throwable exception = null) { + static basicSpan(TraceAssert trace, int index, String spanName, Object parentSpan = null, Throwable exception = null) { trace.span(index) { - parent() + if (parentSpan == null) { + parent() + } else { + childOf((DDSpan) parentSpan) + } serviceName "unnamed-java-app" operationName spanName resourceName spanName From 0e83304a87e085205157ed5fb9e5262d44a84d66 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Sat, 15 Jun 2019 10:05:17 -0700 Subject: [PATCH 31/55] Update Ratpack latest test --- .../latestDepTest/groovy/RatpackTest.groovy | 184 ++++++++---------- 1 file changed, 81 insertions(+), 103 deletions(-) diff --git a/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy b/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy index adf95f13ef..c6276ae636 100644 --- a/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy +++ b/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy @@ -1,7 +1,3 @@ -import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace -import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer -import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT - import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.utils.OkHttpUtils import datadog.trace.api.DDSpanTypes @@ -12,8 +8,6 @@ import io.opentracing.Scope import io.opentracing.Span import io.opentracing.tag.Tags import io.opentracing.util.GlobalTracer -import java.util.concurrent.CountDownLatch -import java.util.regex.Pattern import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.Request @@ -26,21 +20,55 @@ import ratpack.http.client.HttpClient import ratpack.path.PathBinding import ratpack.test.exec.ExecHarness +import java.util.concurrent.CountDownLatch +import java.util.regex.Pattern + +import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace +import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer +import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT + class RatpackTest extends AgentTestRunner { OkHttpClient client = OkHttpUtils.client() - def "test path call"() { + def "test bindings for #path"() { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { - context.render("success") + prefix("a") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("b/::\\d+") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("c/:val?") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("d/:val") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("e/:val?:\\d+") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("f/:val:\\d+") { + all { + context.render(context.get(PathBinding).description) + } } } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(HttpUrl.get(app.address).newBuilder().addPathSegments(path).build()) .get() .build() @@ -49,12 +77,12 @@ class RatpackTest extends AgentTestRunner { then: resp.code() == 200 - resp.body.string() == "success" + resp.body.string() == route assertTraces(1) { trace(0, 2) { span(0) { - resourceName "GET /" + resourceName "GET /$route" serviceName "unnamed-java-app" operationName "netty.request" spanType DDSpanTypes.HTTP_SERVER @@ -65,7 +93,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve(path)}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" "$Tags.PEER_PORT.key" Integer @@ -73,7 +101,7 @@ class RatpackTest extends AgentTestRunner { } } span(1) { - resourceName "GET /" + resourceName "GET /$route" serviceName "unnamed-java-app" operationName "ratpack.handler" spanType DDSpanTypes.HTTP_SERVER @@ -84,7 +112,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve(path)}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_PORT.key" Integer defaultTags() @@ -92,85 +120,35 @@ class RatpackTest extends AgentTestRunner { } } } - } - def "test path with bindings call"() { - setup: - def app = GroovyEmbeddedApp.ratpack { - handlers { - prefix(":foo/:bar?") { - get("baz") {ctx -> - context.render(ctx.get(PathBinding).description) - } - } - } - } - def request = new Request.Builder() - .url(HttpUrl.get(app.address).newBuilder().addPathSegments("a/b/baz").build()) - .get() - .build() - - when: - def resp = client.newCall(request).execute() - - then: - resp.code() == 200 - resp.body.string() == ":foo/:bar?/baz" - - assertTraces(1) { - trace(0, 2) { - span(0) { - resourceName "GET /:foo/:bar?/baz" - serviceName "unnamed-java-app" - operationName "netty.request" - spanType DDSpanTypes.HTTP_SERVER - parent() - errored false - tags { - "$Tags.COMPONENT.key" "netty" - "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER - "$Tags.HTTP_METHOD.key" "GET" - "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "${app.address}a/b/baz" - "$Tags.PEER_HOSTNAME.key" "$app.address.host" - "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" - "$Tags.PEER_PORT.key" Integer - defaultTags() - } - } - span(1) { - resourceName "GET /:foo/:bar?/baz" - serviceName "unnamed-java-app" - operationName "ratpack.handler" - spanType DDSpanTypes.HTTP_SERVER - childOf(span(0)) - errored false - tags { - "$Tags.COMPONENT.key" "ratpack" - "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER - "$Tags.HTTP_METHOD.key" "GET" - "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "${app.address}a/b/baz" - "$Tags.PEER_HOSTNAME.key" "$app.address.host" - "$Tags.PEER_PORT.key" Integer - defaultTags() - } - } - } - } + where: + path | route + "a" | "a" + "b/123" | "b/::\\d+" + "c" | "c/:val?" + "c/123" | "c/:val?" + "c/foo" | "c/:val?" + "d/123" | "d/:val" + "d/foo" | "d/:val" + "e" | "e/:val?:\\d+" + "e/123" | "e/:val?:\\d+" + "e/foo" | "e/:val?:\\d+" + "f/123" | "f/:val:\\d+" } def "test handler error response"() { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { - 0 / 0 + prefix("handler-error") { + all { + 0 / 0 + } } } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(app.address.resolve("/handler-error?query=param").toURL()) .get() .build() when: @@ -181,7 +159,7 @@ class RatpackTest extends AgentTestRunner { assertTraces(1) { trace(0, 2) { span(0) { - resourceName "GET /" + resourceName "GET /handler-error" serviceName "unnamed-java-app" operationName "netty.request" spanType DDSpanTypes.HTTP_SERVER @@ -192,7 +170,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" "$Tags.PEER_PORT.key" Integer @@ -201,7 +179,7 @@ class RatpackTest extends AgentTestRunner { } } span(1) { - resourceName "GET /" + resourceName "GET /handler-error" serviceName "unnamed-java-app" operationName "ratpack.handler" spanType DDSpanTypes.HTTP_SERVER @@ -212,7 +190,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_PORT.key" Integer errorTags(HandlerException, Pattern.compile("java.lang.ArithmeticException: Division( is)? undefined")) @@ -227,7 +205,7 @@ class RatpackTest extends AgentTestRunner { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { + get("promise-error") { Promise.async { 0 / 0 }.then { @@ -237,7 +215,7 @@ class RatpackTest extends AgentTestRunner { } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(app.address.resolve("promise-error?query=param").toURL()) .get() .build() when: @@ -248,7 +226,7 @@ class RatpackTest extends AgentTestRunner { assertTraces(1) { trace(0, 2) { span(0) { - resourceName "GET /" + resourceName "GET /promise-error" serviceName "unnamed-java-app" operationName "netty.request" spanType DDSpanTypes.HTTP_SERVER @@ -259,7 +237,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" "$Tags.PEER_PORT.key" Integer @@ -268,7 +246,7 @@ class RatpackTest extends AgentTestRunner { } } span(1) { - resourceName "GET /" + resourceName "GET /promise-error" serviceName "unnamed-java-app" operationName "ratpack.handler" spanType DDSpanTypes.HTTP_SERVER @@ -279,7 +257,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_PORT.key" Integer "$Tags.ERROR.key" true @@ -294,7 +272,7 @@ class RatpackTest extends AgentTestRunner { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { + all { context.render(Promise.sync { return "fail " + 0 / 0 }) @@ -302,7 +280,7 @@ class RatpackTest extends AgentTestRunner { } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(app.address.resolve("?query=param").toURL()) .get() .build() when: @@ -374,13 +352,13 @@ class RatpackTest extends AgentTestRunner { def app = GroovyEmbeddedApp.ratpack { handlers { - get {HttpClient httpClient -> + get { HttpClient httpClient -> // 1st internal http client call to nested httpClient.get(HttpUrlBuilder.base(external.address).path("nested").build()) - .map {it.body.text} - .flatMap {t -> + .map { it.body.text } + .flatMap { t -> // make a 2nd http request and concatenate the two bodies together - httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map {t + it.body.text} + httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map { t + it.body.text } } .then { context.render(it) @@ -452,7 +430,7 @@ class RatpackTest extends AgentTestRunner { serviceName "unnamed-java-app" operationName "netty.client.request" spanType DDSpanTypes.HTTP_CLIENT - childOf(span(3)) + childOf(span(1)) errored false tags { "$Tags.COMPONENT.key" "netty-client" @@ -496,9 +474,9 @@ class RatpackTest extends AgentTestRunner { def app = GroovyEmbeddedApp.ratpack { handlers { - get {HttpClient httpClient -> + get { HttpClient httpClient -> httpClient.get(badAddress) - .map {it.body.text} + .map { it.body.text } .then { context.render(it) } @@ -681,7 +659,7 @@ class RatpackTest extends AgentTestRunner { scope.span().setBaggageItem("test-baggage", "foo") ParallelBatch.of(testPromise(), testPromise()) .yield() - .map({now -> + .map({ now -> // close the scope now that we got the baggage inside the promises scope.close() return now From 53ef8f020f0d83e44dc5ec6ef0f4feeb62ba8f3f Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Mon, 17 Jun 2019 16:44:07 -0700 Subject: [PATCH 32/55] Additional testing for Vert.x Adds reactive/circuitbreaker tests. --- .../src/test/groovy/VertxRxServerTest.groovy | 157 ++++++++++++++++++ .../test/groovy/VertxRxWebClientTest.groovy | 49 ++++++ .../src/test/java/VertxRxWebTestServer.java | 112 +++++++++++++ .../instrumentation/vertx/vertx.gradle | 6 + 4 files changed, 324 insertions(+) create mode 100644 dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy create mode 100644 dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy create mode 100644 dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy new file mode 100644 index 0000000000..b2d960269a --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy @@ -0,0 +1,157 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.agent.test.utils.OkHttpUtils +import datadog.trace.agent.test.utils.PortUtils +import datadog.trace.api.DDSpanTypes +import io.netty.handler.codec.http.HttpResponseStatus +import io.opentracing.tag.Tags +import io.vertx.core.Vertx +import okhttp3.OkHttpClient +import okhttp3.Request +import spock.lang.Shared + +class VertxRxServerTest extends AgentTestRunner { + + @Shared + OkHttpClient client = OkHttpUtils.client() + + @Shared + int port + @Shared + Vertx server + + def setupSpec() { + port = PortUtils.randomOpenPort() + server = VertxRxWebTestServer.start(port) + } + + def cleanupSpec() { + server.close() + } + + def "test server request/response"() { + setup: + def request = new Request.Builder() + .url("http://localhost:$port/proxy") + .header("x-datadog-trace-id", "123") + .header("x-datadog-parent-id", "456") + .get() + .build() + def response = client.newCall(request).execute() + + expect: + response.code() == 200 + response.body().string() == "Hello World" + + and: + assertTraces(2) { + trace(0, 2) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName "GET /test" + childOf(trace(1).get(1)) + spanType DDSpanTypes.HTTP_SERVER + errored false + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/test" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + defaultTags(true) + } + } + span(1) { + childOf span(0) + assert span(1).operationName.endsWith('.tracedMethod') + } + } + trace(1, 2) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName "GET /proxy" + traceId "123" + parentId "456" + spanType DDSpanTypes.HTTP_SERVER + errored false + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/proxy" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + defaultTags(true) + } + } + span(1) { + serviceName "unnamed-java-app" + operationName "netty.client.request" + resourceName "GET /test" + childOf(span(0)) + spanType DDSpanTypes.HTTP_CLIENT + errored false + tags { + "$Tags.COMPONENT.key" "netty-client" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/test" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } + } + } + + def "test #responseCode response handling"() { + setup: + def request = new Request.Builder().url("http://localhost:$port/$path").get().build() + def response = client.newCall(request).execute() + + expect: + response.code() == responseCode.code() + + and: + assertTraces(1) { + trace(0, 1) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName name + spanType DDSpanTypes.HTTP_SERVER + errored error + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" responseCode.code() + "$Tags.HTTP_URL.key" "http://localhost:$port/$path" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + if (error) { + tag("error", true) + } + defaultTags() + } + } + } + } + + where: + responseCode | name | path | error + HttpResponseStatus.OK | "GET /" | "" | false + HttpResponseStatus.NOT_FOUND | "404" | "doesnt-exit" | false + HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /error" | "error" | true + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy new file mode 100644 index 0000000000..854fb2a4be --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy @@ -0,0 +1,49 @@ +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator +import io.vertx.core.VertxOptions +import io.vertx.core.http.HttpMethod +import io.vertx.reactivex.core.Vertx +import io.vertx.reactivex.ext.web.client.WebClient +import spock.lang.Shared +import spock.lang.Timeout + +@Timeout(10) +class VertxRxWebClientTest extends HttpClientTest { + + @Shared + Vertx vertx = Vertx.vertx(new VertxOptions()) + @Shared + WebClient client = WebClient.create(vertx); + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + def request = client.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri") + headers.each { request.putHeader(it.key, it.value) } + return request + .rxSend() + .doOnSuccess { response -> callback?.call() } + .map { it.statusCode() } + .toObservable() + .blockingFirst() + } + + @Override + NettyHttpClientDecorator decorator() { + return NettyHttpClientDecorator.DECORATE + } + + @Override + String expectedOperationName() { + return "netty.client.request" + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java new file mode 100644 index 0000000000..723a7771cc --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java @@ -0,0 +1,112 @@ +import datadog.trace.api.Trace; +import io.vertx.circuitbreaker.CircuitBreakerOptions; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import io.vertx.core.json.JsonObject; +import io.vertx.reactivex.circuitbreaker.CircuitBreaker; +import io.vertx.reactivex.core.AbstractVerticle; +import io.vertx.reactivex.core.buffer.Buffer; +import io.vertx.reactivex.ext.web.Router; +import io.vertx.reactivex.ext.web.RoutingContext; +import io.vertx.reactivex.ext.web.client.WebClient; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class VertxRxWebTestServer extends AbstractVerticle { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; + + public static Vertx start(final int port) throws ExecutionException, InterruptedException { + /* This is highly against Vertx ideas, but our tests are synchronous + so we have to make sure server is up and running */ + final CompletableFuture future = new CompletableFuture<>(); + + final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); + + vertx.deployVerticle( + VertxRxWebTestServer.class.getName(), + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3), + res -> { + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()); + } + future.complete(null); + }); + + future.get(); + + return vertx; + } + + @Override + public void start(final Future startFuture) { +// final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx); + final WebClient client = WebClient.create(vertx); + + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); + + final Router router = Router.router(vertx); + final CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx, + new CircuitBreakerOptions() + .setMaxFailures(5) // number of failure before opening the circuit + .setTimeout(2000) // consider a failure if the operation does not succeed in time +// .setFallbackOnFailure(true) // do we call the fallback on failure + .setResetTimeout(10000) // time spent in open state before attempting to re-try + ); + + router + .route("/") + .handler( + routingContext -> { + routingContext.response().putHeader("content-type", "text/html").end("Hello World"); + }); + router + .route("/error") + .handler( + routingContext -> { + routingContext.response().setStatusCode(500).end(); + }); + router + .route("/proxy") + .handler( + routingContext -> { + breaker.execute( + ctx -> { + client.get(port, "localhost", "/test") + .rxSendBuffer(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())) + .subscribe( + response -> { + routingContext + .response() + .setStatusCode(response.statusCode()) + .end(response.body()); + }); + }); + }); + router + .route("/test") + .handler( + routingContext -> { + tracedMethod(); + routingContext.next(); + }) + .blockingHandler(RoutingContext::next) + .handler( + routingContext -> { + routingContext.response().putHeader("content-type", "text/html").end("Hello World"); + }); + + vertx + .createHttpServer() + .requestHandler(router::accept) + .listen(port, h -> startFuture.complete()); + } + + @Trace + private void tracedMethod() { + } +} diff --git a/dd-java-agent/instrumentation/vertx/vertx.gradle b/dd-java-agent/instrumentation/vertx/vertx.gradle index df1f0e7ea6..a804990ea9 100644 --- a/dd-java-agent/instrumentation/vertx/vertx.gradle +++ b/dd-java-agent/instrumentation/vertx/vertx.gradle @@ -48,6 +48,12 @@ dependencies { // Tests seem to fail before 3.5... maybe a problem with some of the tests? testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0' + testCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.5.0' + testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.5.0' + testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.5.0' latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '+' } From e692605a3bf35dbf48e6685c752daabd1a892602 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Wed, 10 Jul 2019 14:18:54 -0600 Subject: [PATCH 33/55] Fix formatting --- .../test/groovy/VertxRxWebClientTest.groovy | 2 +- .../src/test/java/VertxRxWebTestServer.java | 120 +++++++++--------- 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy index 854fb2a4be..fc556865c4 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy @@ -13,7 +13,7 @@ class VertxRxWebClientTest extends HttpClientTest { @Shared Vertx vertx = Vertx.vertx(new VertxOptions()) @Shared - WebClient client = WebClient.create(vertx); + WebClient client = WebClient.create(vertx) @Override int doRequest(String method, URI uri, Map headers, Closure callback) { diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java index 723a7771cc..521253c268 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java +++ b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java @@ -26,16 +26,16 @@ public class VertxRxWebTestServer extends AbstractVerticle { final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); vertx.deployVerticle( - VertxRxWebTestServer.class.getName(), - new DeploymentOptions() - .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) - .setInstances(3), - res -> { - if (!res.succeeded()) { - throw new RuntimeException("Cannot deploy server Verticle", res.cause()); - } - future.complete(null); - }); + VertxRxWebTestServer.class.getName(), + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3), + res -> { + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()); + } + future.complete(null); + }); future.get(); @@ -44,69 +44,73 @@ public class VertxRxWebTestServer extends AbstractVerticle { @Override public void start(final Future startFuture) { -// final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx); + // final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx); final WebClient client = WebClient.create(vertx); final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); final Router router = Router.router(vertx); - final CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx, - new CircuitBreakerOptions() - .setMaxFailures(5) // number of failure before opening the circuit - .setTimeout(2000) // consider a failure if the operation does not succeed in time -// .setFallbackOnFailure(true) // do we call the fallback on failure - .setResetTimeout(10000) // time spent in open state before attempting to re-try - ); + final CircuitBreaker breaker = + CircuitBreaker.create( + "my-circuit-breaker", + vertx, + new CircuitBreakerOptions() + .setMaxFailures(5) // number of failure before opening the circuit + .setTimeout(2000) // consider a failure if the operation does not succeed in time + // .setFallbackOnFailure(true) // do we call the fallback on failure + .setResetTimeout(10000) // time spent in open state before attempting to re-try + ); router - .route("/") - .handler( - routingContext -> { - routingContext.response().putHeader("content-type", "text/html").end("Hello World"); - }); + .route("/") + .handler( + routingContext -> { + routingContext.response().putHeader("content-type", "text/html").end("Hello World"); + }); router - .route("/error") - .handler( - routingContext -> { - routingContext.response().setStatusCode(500).end(); - }); + .route("/error") + .handler( + routingContext -> { + routingContext.response().setStatusCode(500).end(); + }); router - .route("/proxy") - .handler( - routingContext -> { - breaker.execute( - ctx -> { - client.get(port, "localhost", "/test") - .rxSendBuffer(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())) - .subscribe( - response -> { - routingContext - .response() - .setStatusCode(response.statusCode()) - .end(response.body()); + .route("/proxy") + .handler( + routingContext -> { + breaker.execute( + ctx -> { + client + .get(port, "localhost", "/test") + .rxSendBuffer( + Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())) + .subscribe( + response -> { + routingContext + .response() + .setStatusCode(response.statusCode()) + .end(response.body()); + }); }); }); - }); router - .route("/test") - .handler( - routingContext -> { - tracedMethod(); - routingContext.next(); - }) - .blockingHandler(RoutingContext::next) - .handler( - routingContext -> { - routingContext.response().putHeader("content-type", "text/html").end("Hello World"); - }); + .route("/test") + .handler( + routingContext -> { + tracedMethod(); + routingContext.next(); + }) + .blockingHandler(RoutingContext::next) + .handler( + routingContext -> { + routingContext.response().putHeader("content-type", "text/html").end("Hello World"); + }); vertx - .createHttpServer() - .requestHandler(router::accept) - .listen(port, h -> startFuture.complete()); + .createHttpServer() + .requestHandler(router::accept) + .listen(port, h -> startFuture.complete()); } @Trace - private void tracedMethod() { - } + private void tracedMethod() {} } From ea4fc4ab285195cfdba71bcefd1d662324fb8d85 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 18 Jul 2019 15:36:25 -0700 Subject: [PATCH 34/55] Use NoopSpan instead of null for null parent. Fix some more tests. --- .../AkkaHttpClientInstrumentationTest.groovy | 10 +++++++++- .../ApacheHttpAsyncClientCallbackTest.groovy | 15 ++------------ .../test/groovy/JaxRsClientAsyncTest.groovy | 13 ++++++++++-- .../HttpClientResponseTracingHandler.java | 20 ++++++++++--------- .../HttpClientResponseTracingHandler.java | 20 ++++++++++--------- 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy index 3ae9770f32..696d153a67 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy @@ -25,8 +25,16 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest + // FIXME: Callback should be here instead. + // callback?.call() + //} + .toCompletableFuture() + .get() } finally { + // FIXME: remove this when callback above works. blockUntilChildSpansFinished(1) } callback?.call() diff --git a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy index fde0e1bfad..44b31ab31c 100644 --- a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy +++ b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy @@ -1,6 +1,5 @@ import datadog.trace.agent.test.base.HttpClientTest import datadog.trace.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientDecorator -import io.opentracing.util.GlobalTracer import org.apache.http.HttpResponse import org.apache.http.concurrent.FutureCallback import org.apache.http.impl.nio.client.HttpAsyncClients @@ -22,8 +21,6 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest headers, Closure callback) { - def hasParent = GlobalTracer.get().activeSpan() != null - def request = new HttpUriRequest(method, uri) headers.entrySet().each { request.addHeader(new BasicHeader(it.key, it.value)) @@ -35,21 +32,13 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest AsyncInvoker request = builder.async() def body = BODY_METHODS.contains(method) ? Entity.text("") : null - Response response = request.method(method, (Entity) body).get() - callback?.call() + Response response = request.method(method, (Entity) body, new InvocationCallback(){ + @Override + void completed(Response s) { + callback?.call() + } + + @Override + void failed(Throwable throwable) { + } + }).get() return response.status } diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java index 6dedbb0157..ac1865b6bf 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java @@ -7,35 +7,37 @@ import datadog.trace.instrumentation.netty40.AttributeKeys; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpResponse; +import io.netty.util.Attribute; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.noop.NoopSpan; import io.opentracing.util.GlobalTracer; public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { - final Span parent = ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).get(); + final Attribute parentAttr = + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY); + parentAttr.setIfAbsent(NoopSpan.INSTANCE); + final Span parent = parentAttr.get(); final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get(); final boolean finishSpan = msg instanceof HttpResponse; if (span != null && finishSpan) { - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, true)) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { DECORATE.onResponse(span, (HttpResponse) msg); DECORATE.beforeFinish(span); + span.finish(); } } // We want the callback in the scope of the parent, not the client span - if (parent != null) { - try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { - if (scope instanceof TraceScope) { - ((TraceScope) scope).setAsyncPropagation(true); - } - ctx.fireChannelRead(msg); + try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); } - } else { ctx.fireChannelRead(msg); } } diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java index a08926ec0e..c13a766361 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java @@ -7,35 +7,37 @@ import datadog.trace.instrumentation.netty41.AttributeKeys; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpResponse; +import io.netty.util.Attribute; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.noop.NoopSpan; import io.opentracing.util.GlobalTracer; public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { - final Span parent = ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).get(); + final Attribute parentAttr = + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY); + parentAttr.setIfAbsent(NoopSpan.INSTANCE); + final Span parent = parentAttr.get(); final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get(); final boolean finishSpan = msg instanceof HttpResponse; if (span != null && finishSpan) { - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, true)) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { DECORATE.onResponse(span, (HttpResponse) msg); DECORATE.beforeFinish(span); + span.finish(); } } // We want the callback in the scope of the parent, not the client span - if (parent != null) { - try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { - if (scope instanceof TraceScope) { - ((TraceScope) scope).setAsyncPropagation(true); - } - ctx.fireChannelRead(msg); + try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); } - } else { ctx.fireChannelRead(msg); } } From f853916dcef58af01c5165eca07fc8c2d8678685 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Fri, 19 Jul 2019 17:35:52 -0400 Subject: [PATCH 35/55] Get hostname in a smarter way Attempt to get hostname through environment variables and command before going to DNS --- .../main/java/datadog/trace/api/Config.java | 39 ++++++++++++++++--- .../datadog/trace/api/ConfigTest.groovy | 2 +- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/Config.java b/dd-trace-api/src/main/java/datadog/trace/api/Config.java index 37ca73aa57..f309b50c6c 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/Config.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/Config.java @@ -1,9 +1,11 @@ package datadog.trace.api; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; @@ -885,7 +887,7 @@ public class Config { * exist or if it is in a wrong format. */ private static Properties loadConfigurationFile() { - Properties properties = new Properties(); + final Properties properties = new Properties(); // Reading from system property first and from env after String configurationFilePath = @@ -922,11 +924,38 @@ public class Config { return properties; } - /** - * Returns the detected hostname. This operation is time consuming so if the usage changes and - * this method will be called several times then we should implement some sort of caching. - */ + /** Returns the detected hostname. First tries locally, then using DNS */ private String getHostName() { + String possibleHostname = null; + + // Try environment variable. This works in almost all environments + if (System.getProperty("os.name").startsWith("Windows")) { + possibleHostname = System.getenv("COMPUTERNAME"); + } else { + possibleHostname = System.getenv("HOSTNAME"); + } + + if (possibleHostname != null && !possibleHostname.isEmpty()) { + log.debug("Determined hostname from environment variable"); + return possibleHostname.trim(); + } + + // Try hostname command + try { + final Process process = Runtime.getRuntime().exec("hostname"); + final BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + possibleHostname = reader.readLine(); + } catch (final Exception e) { + // Ignore. Hostname command is not always available + } + + if (possibleHostname != null && !possibleHostname.isEmpty()) { + log.debug("Determined hostname from hostname command"); + return possibleHostname.trim(); + } + + // From DNS try { return InetAddress.getLocalHost().getHostName(); } catch (final UnknownHostException e) { diff --git a/dd-trace-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy b/dd-trace-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy index f9fd7f3894..1bd60342b9 100644 --- a/dd-trace-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy +++ b/dd-trace-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy @@ -724,7 +724,7 @@ class ConfigTest extends Specification { def config = Config.get(properties) then: - config.localRootSpanTags.get('_dd.hostname') == InetAddress.localHost.hostName + config.localRootSpanTags.containsKey('_dd.hostname') } def "verify fallback to properties file"() { From 797003b5857f8c44deafa1f24c849195c59ae55d Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Fri, 3 May 2019 12:37:30 +0200 Subject: [PATCH 36/55] Moved Spring Webflux instrumentation code to server subpackage --- .../{ => server}/AbstractWebfluxInstrumentation.java | 2 +- .../{ => server}/DispatcherHandlerInstrumentation.java | 2 +- .../{ => server}/HandlerAdapterInstrumentation.java | 2 +- .../{ => server}/RouterFunctionInstrumentation.java | 2 +- .../springwebflux/{ => server}/AdviceUtils.java | 4 ++-- .../{ => server}/DispatcherHandlerAdvice.java | 4 ++-- .../{ => server}/HandlerAdapterAdvice.java | 4 ++-- .../{ => server}/RouteOnSuccessOrError.java | 2 +- .../{ => server}/RouterFunctionAdvice.java | 10 +++++----- .../{ => server}/SpringWebfluxHttpServerDecorator.java | 2 +- .../test/groovy/SingleThreadedSpringWebfluxTest.groovy | 2 +- .../src/test/groovy/SpringWebfluxTest.groovy | 8 ++++---- .../springwebflux/{ => server}/EchoHandler.groovy | 2 +- .../{ => server}/EchoHandlerFunction.groovy | 2 +- .../springwebflux/{ => server}/FooModel.groovy | 2 +- .../{ => server}/SpringWebFluxTestApplication.groovy | 2 +- .../springwebflux/{ => server}/TestController.groovy | 2 +- .../springwebflux/{ => server}/RedirectComponent.java | 2 +- 18 files changed, 28 insertions(+), 28 deletions(-) rename dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/{ => server}/AbstractWebfluxInstrumentation.java (93%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/{ => server}/DispatcherHandlerInstrumentation.java (95%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/{ => server}/HandlerAdapterInstrumentation.java (96%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/{ => server}/RouterFunctionInstrumentation.java (97%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/{ => server}/AdviceUtils.java (89%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/{ => server}/DispatcherHandlerAdvice.java (92%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/{ => server}/HandlerAdapterAdvice.java (93%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/{ => server}/RouteOnSuccessOrError.java (97%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/{ => server}/RouterFunctionAdvice.java (72%) rename dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/{ => server}/SpringWebfluxHttpServerDecorator.java (90%) rename dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/{ => server}/EchoHandler.groovy (92%) rename dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/{ => server}/EchoHandlerFunction.groovy (91%) rename dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/{ => server}/FooModel.groovy (81%) rename dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/{ => server}/SpringWebFluxTestApplication.groovy (98%) rename dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/{ => server}/TestController.groovy (97%) rename dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/{ => server}/RedirectComponent.java (93%) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/AbstractWebfluxInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/AbstractWebfluxInstrumentation.java similarity index 93% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/AbstractWebfluxInstrumentation.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/AbstractWebfluxInstrumentation.java index 58e39d7d39..db7f0e55ba 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/AbstractWebfluxInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/AbstractWebfluxInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import datadog.trace.agent.tooling.Instrumenter; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/DispatcherHandlerInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/DispatcherHandlerInstrumentation.java similarity index 95% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/DispatcherHandlerInstrumentation.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/DispatcherHandlerInstrumentation.java index c6a0c3e436..3857e96ac0 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/DispatcherHandlerInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/DispatcherHandlerInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/HandlerAdapterInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/HandlerAdapterInstrumentation.java similarity index 96% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/HandlerAdapterInstrumentation.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/HandlerAdapterInstrumentation.java index 520d396cda..89d9e946c9 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/HandlerAdapterInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/HandlerAdapterInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static java.util.Collections.singletonMap; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/RouterFunctionInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/RouterFunctionInstrumentation.java similarity index 97% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/RouterFunctionInstrumentation.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/RouterFunctionInstrumentation.java index 133b56ca81..bc1b5a9322 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/RouterFunctionInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/server/RouterFunctionInstrumentation.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static java.util.Collections.singletonMap; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/AdviceUtils.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java similarity index 89% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/AdviceUtils.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java index 108bb223d8..1c1c92ab8b 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/AdviceUtils.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java @@ -1,6 +1,6 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; -import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE; +import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE; import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils; import io.opentracing.Span; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/DispatcherHandlerAdvice.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java similarity index 92% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/DispatcherHandlerAdvice.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java index f7f72e9e3a..7fcd83d6a0 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/DispatcherHandlerAdvice.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java @@ -1,6 +1,6 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; -import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE; +import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE; import datadog.trace.context.TraceScope; import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/HandlerAdapterAdvice.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/HandlerAdapterAdvice.java similarity index 93% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/HandlerAdapterAdvice.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/HandlerAdapterAdvice.java index df050bb9e7..a42ef76f8c 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/HandlerAdapterAdvice.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/HandlerAdapterAdvice.java @@ -1,6 +1,6 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; -import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE; +import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE; import datadog.trace.api.DDTags; import datadog.trace.context.TraceScope; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouteOnSuccessOrError.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/RouteOnSuccessOrError.java similarity index 97% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouteOnSuccessOrError.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/RouteOnSuccessOrError.java index e51a959893..af5eb313aa 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouteOnSuccessOrError.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/RouteOnSuccessOrError.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import datadog.trace.api.DDTags; import io.opentracing.Span; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouterFunctionAdvice.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/RouterFunctionAdvice.java similarity index 72% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouterFunctionAdvice.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/RouterFunctionAdvice.java index 3d79ea2748..c4c441183a 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/RouterFunctionAdvice.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/RouterFunctionAdvice.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import net.bytebuddy.asm.Advice; import org.springframework.web.reactive.function.server.HandlerFunction; @@ -14,10 +14,10 @@ public class RouterFunctionAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.This final RouterFunction thiz, - @Advice.Argument(0) final ServerRequest serverRequest, - @Advice.Return(readOnly = false) Mono> result, - @Advice.Thrown final Throwable throwable) { + @Advice.This final RouterFunction thiz, + @Advice.Argument(0) final ServerRequest serverRequest, + @Advice.Return(readOnly = false) Mono> result, + @Advice.Thrown final Throwable throwable) { if (throwable == null) { result = result.doOnSuccessOrError(new RouteOnSuccessOrError(thiz, serverRequest)); } else { diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/SpringWebfluxHttpServerDecorator.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/SpringWebfluxHttpServerDecorator.java similarity index 90% rename from dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/SpringWebfluxHttpServerDecorator.java rename to dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/SpringWebfluxHttpServerDecorator.java index 1616366e5c..cb5efd263d 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/SpringWebfluxHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/SpringWebfluxHttpServerDecorator.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.springwebflux; +package datadog.trace.instrumentation.springwebflux.server; import datadog.trace.agent.decorator.ServerDecorator; import datadog.trace.api.DDSpanTypes; diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SingleThreadedSpringWebfluxTest.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SingleThreadedSpringWebfluxTest.groovy index 4f3240a3ca..b95bc9d106 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SingleThreadedSpringWebfluxTest.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SingleThreadedSpringWebfluxTest.groovy @@ -1,4 +1,4 @@ -import dd.trace.instrumentation.springwebflux.SpringWebFluxTestApplication +import dd.trace.instrumentation.springwebflux.server.SpringWebFluxTestApplication import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SpringWebfluxTest.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SpringWebfluxTest.groovy index ae6ffbe0dc..4ac26f8f0b 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SpringWebfluxTest.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/SpringWebfluxTest.groovy @@ -1,10 +1,10 @@ import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.utils.OkHttpUtils import datadog.trace.api.DDSpanTypes -import dd.trace.instrumentation.springwebflux.EchoHandlerFunction -import dd.trace.instrumentation.springwebflux.FooModel -import dd.trace.instrumentation.springwebflux.SpringWebFluxTestApplication -import dd.trace.instrumentation.springwebflux.TestController +import dd.trace.instrumentation.springwebflux.server.EchoHandlerFunction +import dd.trace.instrumentation.springwebflux.server.FooModel +import dd.trace.instrumentation.springwebflux.server.SpringWebFluxTestApplication +import dd.trace.instrumentation.springwebflux.server.TestController import io.opentracing.tag.Tags import okhttp3.OkHttpClient import okhttp3.Request diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/EchoHandler.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/EchoHandler.groovy similarity index 92% rename from dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/EchoHandler.groovy rename to dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/EchoHandler.groovy index 8abe3003c0..fd73e5e927 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/EchoHandler.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/EchoHandler.groovy @@ -1,4 +1,4 @@ -package dd.trace.instrumentation.springwebflux +package dd.trace.instrumentation.springwebflux.server import datadog.trace.api.Trace import org.springframework.http.MediaType diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/EchoHandlerFunction.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/EchoHandlerFunction.groovy similarity index 91% rename from dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/EchoHandlerFunction.groovy rename to dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/EchoHandlerFunction.groovy index dd1ba288bf..ca00a79bb5 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/EchoHandlerFunction.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/EchoHandlerFunction.groovy @@ -1,4 +1,4 @@ -package dd.trace.instrumentation.springwebflux +package dd.trace.instrumentation.springwebflux.server import org.springframework.web.reactive.function.server.HandlerFunction diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/FooModel.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/FooModel.groovy similarity index 81% rename from dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/FooModel.groovy rename to dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/FooModel.groovy index 5bfd02f4ee..b119968487 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/FooModel.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/FooModel.groovy @@ -1,4 +1,4 @@ -package dd.trace.instrumentation.springwebflux +package dd.trace.instrumentation.springwebflux.server class FooModel { public long id diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/SpringWebFluxTestApplication.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/SpringWebFluxTestApplication.groovy similarity index 98% rename from dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/SpringWebFluxTestApplication.groovy rename to dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/SpringWebFluxTestApplication.groovy index 77ed2dd688..fd7ac41559 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/SpringWebFluxTestApplication.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/SpringWebFluxTestApplication.groovy @@ -1,4 +1,4 @@ -package dd.trace.instrumentation.springwebflux +package dd.trace.instrumentation.springwebflux.server import datadog.trace.api.Trace import org.springframework.boot.autoconfigure.SpringBootApplication diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/TestController.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/TestController.groovy similarity index 97% rename from dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/TestController.groovy rename to dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/TestController.groovy index 766e6a6d42..0f2747b98b 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/TestController.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/server/TestController.groovy @@ -1,4 +1,4 @@ -package dd.trace.instrumentation.springwebflux +package dd.trace.instrumentation.springwebflux.server import datadog.trace.api.Trace import org.springframework.web.bind.annotation.GetMapping diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/RedirectComponent.java b/dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/server/RedirectComponent.java similarity index 93% rename from dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/RedirectComponent.java rename to dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/server/RedirectComponent.java index 1f7ec4c8b5..8591701439 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/RedirectComponent.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/java/dd/trace/instrumentation/springwebflux/server/RedirectComponent.java @@ -1,4 +1,4 @@ -package dd.trace.instrumentation.springwebflux; +package dd.trace.instrumentation.springwebflux.server; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RouterFunctions.route; From 091648d4c77b0b11124a97a4447c78781a049295 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Thu, 9 May 2019 11:03:48 +0200 Subject: [PATCH 37/55] Enable tracing for Spring Webflux WebClient --- .../DefaultWebClientInstrumentation.java | 55 +++++++++++ .../client/ClientResponseWrapper.java | 90 ++++++++++++++++++ .../client/DefaultWebClientAdvice.java | 24 +++++ .../client/HttpHeadersInjectAdapter.java | 25 +++++ .../SpringWebfluxHttpClientDecorator.java | 59 ++++++++++++ .../client/TracingClientResponseMono.java | 58 ++++++++++++ .../TracingClientResponseSubscriber.java | 93 +++++++++++++++++++ .../springwebflux/server/AdviceUtils.java | 7 ++ 8 files changed, 411 insertions(+) create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/HttpHeadersInjectAdapter.java create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java new file mode 100644 index 0000000000..ef9cfe48dc --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java @@ -0,0 +1,55 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public class DefaultWebClientInstrumentation extends Instrumenter.Default { + + public DefaultWebClientInstrumentation() { + super("spring-webflux"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.agent.decorator.BaseDecorator", + "datadog.trace.agent.decorator.ClientDecorator", + "datadog.trace.agent.decorator.HttpClientDecorator", + packageName + ".SpringWebfluxHttpClientDecorator", + packageName + ".HttpHeadersInjectAdapter", + packageName + ".TracingClientResponseSubscriber", + packageName + ".TracingClientResponseSubscriber$1", + packageName + ".TracingClientResponseMono", + packageName + ".ClientResponseWrapper" + }; + } + + @Override + public ElementMatcher typeMatcher() { + return named( + "org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction"); + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod() + .and(isPublic()) + .and(named("exchange")) + .and( + takesArgument( + 0, named("org.springframework.web.reactive.function.client.ClientRequest"))), + packageName + ".DefaultWebClientAdvice"); + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java new file mode 100644 index 0000000000..0c2de72d87 --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java @@ -0,0 +1,90 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import java.util.List; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.reactive.ClientHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyExtractor; +import org.springframework.web.reactive.function.client.ClientResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +public class ClientResponseWrapper implements ClientResponse { + + private final ClientResponse clientResponse; + private final Context context; + + public ClientResponseWrapper(final ClientResponse clientResponse, final Context context) { + this.clientResponse = clientResponse; + this.context = context; + } + + @Override + public HttpStatus statusCode() { + return clientResponse.statusCode(); + } + + @Override + public Headers headers() { + return clientResponse.headers(); + } + + @Override + public MultiValueMap cookies() { + return clientResponse.cookies(); + } + + @Override + public T body(final BodyExtractor extractor) { + return clientResponse.body(extractor); + } + + @Override + public Mono bodyToMono(final Class elementClass) { + return clientResponse.bodyToMono(elementClass).subscriberContext(context); + } + + @Override + public Mono bodyToMono(final ParameterizedTypeReference typeReference) { + return clientResponse.bodyToMono(typeReference).subscriberContext(context); + } + + @Override + public Flux bodyToFlux(final Class elementClass) { + return clientResponse.bodyToFlux(elementClass).subscriberContext(context); + } + + @Override + public Flux bodyToFlux(final ParameterizedTypeReference typeReference) { + return clientResponse.bodyToFlux(typeReference).subscriberContext(context); + } + + @Override + public Mono> toEntity(final Class bodyType) { + return clientResponse.toEntity(bodyType); + } + + @Override + public Mono> toEntity(final ParameterizedTypeReference typeReference) { + return clientResponse.toEntity(typeReference); + } + + @Override + public Mono>> toEntityList(final Class elementType) { + return clientResponse.toEntityList(elementType); + } + + @Override + public Mono>> toEntityList( + final ParameterizedTypeReference typeReference) { + return clientResponse.toEntityList(typeReference); + } + + public int rawStatusCode() { + return clientResponse.statusCode().value(); + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java new file mode 100644 index 0000000000..b11096450a --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java @@ -0,0 +1,24 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import io.opentracing.util.GlobalTracer; +import net.bytebuddy.asm.Advice; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import reactor.core.publisher.Mono; + +public class DefaultWebClientAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.Thrown final Throwable throwable, + @Advice.This final ExchangeFunction exchangeFunction, + @Advice.Argument(0) final ClientRequest clientRequest, + @Advice.Return(readOnly = false) Mono mono) { + if (throwable == null + && mono != null + && !clientRequest.headers().keySet().contains("x-datadog-trace-id")) { + mono = new TracingClientResponseMono(clientRequest, exchangeFunction, GlobalTracer.get()); + } + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/HttpHeadersInjectAdapter.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/HttpHeadersInjectAdapter.java new file mode 100644 index 0000000000..d0eb1ab3a6 --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/HttpHeadersInjectAdapter.java @@ -0,0 +1,25 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import io.opentracing.propagation.TextMap; +import java.util.Iterator; +import java.util.Map; +import org.springframework.http.HttpHeaders; + +public class HttpHeadersInjectAdapter implements TextMap { + + private final HttpHeaders headers; + + public HttpHeadersInjectAdapter(final HttpHeaders headers) { + this.headers = headers; + } + + @Override + public Iterator> iterator() { + throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!"); + } + + @Override + public void put(final String key, final String value) { + headers.set(key, value); + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java new file mode 100644 index 0000000000..850f99d2d4 --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java @@ -0,0 +1,59 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import datadog.trace.agent.decorator.HttpClientDecorator; +import io.opentracing.Span; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; + +@Slf4j +public class SpringWebfluxHttpClientDecorator + extends HttpClientDecorator { + public static final SpringWebfluxHttpClientDecorator DECORATE = + new SpringWebfluxHttpClientDecorator(); + + public void onCancel(final Span span) { + final Map logs = new HashMap<>(2); + logs.put("event", "cancelled"); + logs.put("message", "The subscription was cancelled"); + span.log(logs); + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"spring-webflux-client"}; + } + + @Override + protected String component() { + return "spring-webflux-client"; + } + + @Override + protected String method(final ClientRequest httpRequest) { + return httpRequest.method().name(); + } + + @Override + protected URI url(final ClientRequest httpRequest) { + return httpRequest.url(); + } + + @Override + protected String hostname(final ClientRequest httpRequest) { + return httpRequest.url().getHost(); + } + + @Override + protected Integer port(final ClientRequest httpRequest) { + return httpRequest.url().getPort(); + } + + @Override + protected Integer status(final ClientResponse httpResponse) { + return httpResponse.statusCode().value(); + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java new file mode 100644 index 0000000000..4feaa717e1 --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java @@ -0,0 +1,58 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format; +import io.opentracing.tag.Tags; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +public class TracingClientResponseMono extends Mono { + + private final ClientRequest clientRequest; + private final ExchangeFunction exchangeFunction; + private final Tracer tracer; + + public TracingClientResponseMono( + final ClientRequest clientRequest, + final ExchangeFunction exchangeFunction, + final Tracer tracer) { + this.clientRequest = clientRequest; + this.exchangeFunction = exchangeFunction; + this.tracer = tracer; + } + + @Override + public void subscribe(final CoreSubscriber subscriber) { + final Context context = subscriber.currentContext(); + final Span parentSpan = context.getOrEmpty(Span.class).orElseGet(tracer::activeSpan); + + final Span span = + tracer + .buildSpan(clientRequest.method().toString()) + .asChildOf(parentSpan) + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) + .start(); + + try (final Scope scope = tracer.scopeManager().activate(span, false)) { + final ClientRequest mutatedRequest = + ClientRequest.from(clientRequest) + .headers( + httpHeaders -> + tracer.inject( + span.context(), + Format.Builtin.HTTP_HEADERS, + new HttpHeadersInjectAdapter(httpHeaders))) + .build(); + exchangeFunction + .exchange(mutatedRequest) + .subscribe( + new TracingClientResponseSubscriber(subscriber, mutatedRequest, context, span)); + } + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java new file mode 100644 index 0000000000..bbccd9a83c --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java @@ -0,0 +1,93 @@ +package datadog.trace.instrumentation.springwebflux.client; + +import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE; + +import io.opentracing.Span; +import java.net.InetAddress; +import java.net.UnknownHostException; +import org.reactivestreams.Subscription; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +public class TracingClientResponseSubscriber implements CoreSubscriber { + + private final CoreSubscriber subscriber; + private final ClientRequest clientRequest; + private final Context context; + private final Span span; + + public TracingClientResponseSubscriber( + final CoreSubscriber subscriber, + final ClientRequest clientRequest, + final Context context, + final Span span) { + this.subscriber = subscriber; + this.clientRequest = clientRequest; + this.context = context.put(Span.class, span); + this.span = span; + } + + @Override + public void onSubscribe(final Subscription subscription) { + DECORATE.onRequest(span, clientRequest); + DECORATE.onPeerConnection(span, remoteAddress()); + + subscriber.onSubscribe( + new Subscription() { + @Override + public void request(final long n) { + subscription.request(n); + } + + @Override + public void cancel() { + DECORATE.onCancel(span); + subscription.cancel(); + span.finish(); + } + }); + } + + private InetAddress remoteAddress() { + try { + return InetAddress.getByName(clientRequest.url().getHost()); + } catch (final UnknownHostException e) { + return null; + } + } + + @Override + public void onNext(final ClientResponse clientResponse) { + try { + subscriber.onNext(new ClientResponseWrapper(clientResponse, context)); + } finally { + DECORATE.onResponse(span, clientResponse); + } + } + + @Override + public void onError(final Throwable throwable) { + try { + subscriber.onError(throwable); + } finally { + DECORATE.onError(span, throwable); + span.finish(); + } + } + + @Override + public void onComplete() { + try { + subscriber.onComplete(); + } finally { + span.finish(); + } + } + + @Override + public Context currentContext() { + return context; + } +} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java index 1c1c92ab8b..ba638a1b6f 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/server/AdviceUtils.java @@ -5,6 +5,7 @@ import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHt import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils; import io.opentracing.Span; import lombok.extern.slf4j.Slf4j; +import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; @@ -39,4 +40,10 @@ public class AdviceUtils { ReactorCoreAdviceUtils.finishSpanIfPresent( (Span) serverRequest.attributes().remove(SPAN_ATTRIBUTE), throwable); } + + public static void finishSpanIfPresent( + final ClientRequest clientRequest, final Throwable throwable) { + ReactorCoreAdviceUtils.finishSpanIfPresent( + (Span) clientRequest.attributes().remove(SPAN_ATTRIBUTE), throwable); + } } From 0e9edb12055ca0e410c24464a3db892b540b77b7 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Wed, 15 May 2019 09:47:23 +0200 Subject: [PATCH 38/55] Aligned instrumentation names between DefaultWebClientInstrumentation and SpringWebfluxHttpClientDecorator --- .../springwebflux/client/DefaultWebClientInstrumentation.java | 2 +- .../springwebflux/client/SpringWebfluxHttpClientDecorator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java index ef9cfe48dc..8220598a1c 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java @@ -17,7 +17,7 @@ import net.bytebuddy.matcher.ElementMatcher; public class DefaultWebClientInstrumentation extends Instrumenter.Default { public DefaultWebClientInstrumentation() { - super("spring-webflux"); + super("spring-webflux", "spring-webflux-client"); } @Override diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java index 850f99d2d4..f164f9b07e 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java @@ -24,7 +24,7 @@ public class SpringWebfluxHttpClientDecorator @Override protected String[] instrumentationNames() { - return new String[] {"spring-webflux-client"}; + return new String[] {"spring-webflux", "spring-webflux-client"}; } @Override From e4560ba50f234e2369e1bbf9b7a7e6a42717adf5 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Wed, 15 May 2019 09:47:57 +0200 Subject: [PATCH 39/55] Instrument all subtypes of ExchangeFunction instead of only ExchangeFunctions$DefaultExchangeFunction --- .../springwebflux/client/DefaultWebClientInstrumentation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java index 8220598a1c..194191b937 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java @@ -1,6 +1,7 @@ package datadog.trace.instrumentation.springwebflux.client; import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -37,8 +38,7 @@ public class DefaultWebClientInstrumentation extends Instrumenter.Default { @Override public ElementMatcher typeMatcher() { - return named( - "org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction"); + return hasSuperType(named("org.springframework.web.reactive.function.client.ExchangeFunction")); } @Override From f5f8fe1c96e25ba84b2179ff992b0ec3142a5e1d Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Wed, 15 May 2019 09:54:34 +0200 Subject: [PATCH 40/55] Add javadoc to clarify usage of this class --- .../springwebflux/client/ClientResponseWrapper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java index 0c2de72d87..463e70921e 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java @@ -13,6 +13,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.context.Context; +/** Wrapper class for ClientResponse that adds Context to the body Publisher */ public class ClientResponseWrapper implements ClientResponse { private final ClientResponse clientResponse; From 35b924b3976a35429ec0fdeb5f0457621eb63395 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Wed, 15 May 2019 09:54:57 +0200 Subject: [PATCH 41/55] Replace span.log with span.tag --- .../client/SpringWebfluxHttpClientDecorator.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java index f164f9b07e..0511e1a460 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientDecorator.java @@ -3,8 +3,6 @@ package datadog.trace.instrumentation.springwebflux.client; import datadog.trace.agent.decorator.HttpClientDecorator; import io.opentracing.Span; import java.net.URI; -import java.util.HashMap; -import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -16,10 +14,8 @@ public class SpringWebfluxHttpClientDecorator new SpringWebfluxHttpClientDecorator(); public void onCancel(final Span span) { - final Map logs = new HashMap<>(2); - logs.put("event", "cancelled"); - logs.put("message", "The subscription was cancelled"); - span.log(logs); + span.setTag("event", "cancelled"); + span.setTag("message", "The subscription was cancelled"); } @Override From ac11bba616974be7f470e2d284a0a28c84a4afbc Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Wed, 15 May 2019 09:57:39 +0200 Subject: [PATCH 42/55] Replace operation name with static value --- .../springwebflux/client/TracingClientResponseMono.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java index 4feaa717e1..b02c2dd73c 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java @@ -34,7 +34,7 @@ public class TracingClientResponseMono extends Mono { final Span span = tracer - .buildSpan(clientRequest.method().toString()) + .buildSpan("webflux.request") .asChildOf(parentSpan) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) .start(); From 23b8caa27c0f820b95bddc6b23f7869d5d551220 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Wed, 15 May 2019 10:09:52 +0200 Subject: [PATCH 43/55] Add decorator hooks during lifecycle of Publisher --- .../springwebflux/client/TracingClientResponseMono.java | 3 +++ .../springwebflux/client/TracingClientResponseSubscriber.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java index b02c2dd73c..8331659176 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java @@ -1,5 +1,7 @@ package datadog.trace.instrumentation.springwebflux.client; +import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE; + import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.Tracer; @@ -38,6 +40,7 @@ public class TracingClientResponseMono extends Mono { .asChildOf(parentSpan) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) .start(); + DECORATE.afterStart(span); try (final Scope scope = tracer.scopeManager().activate(span, false)) { final ClientRequest mutatedRequest = diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java index bbccd9a83c..799e98c6fa 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java @@ -44,6 +44,7 @@ public class TracingClientResponseSubscriber implements CoreSubscriber Date: Wed, 15 May 2019 10:19:32 +0200 Subject: [PATCH 44/55] Remove onPeerConnection decorator call --- .../springwebflux/client/TracingClientResponseSubscriber.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java index 799e98c6fa..0ac4e509d3 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java @@ -32,7 +32,6 @@ public class TracingClientResponseSubscriber implements CoreSubscriber Date: Wed, 15 May 2019 14:13:32 +0200 Subject: [PATCH 45/55] Add test for spring webflux webclient --- .../client/SpringWebfluxHttpClientTest.groovy | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy new file mode 100644 index 0000000000..7a097ed937 --- /dev/null +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy @@ -0,0 +1,31 @@ +package dd.trace.instrumentation.springwebflux.client + +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator +import org.springframework.web.reactive.function.client.ClientResponse +import org.springframework.web.reactive.function.client.WebClient +import spock.lang.Shared + +class SpringWebfluxHttpClientTest extends HttpClientTest { + + @Shared + def client = WebClient.builder().build() + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + assert method == "GET" + ClientResponse response = client.get() + .headers({ h -> headers.forEach({ key, value -> h.add(key, value) }) }) + .uri(uri) + .exchange() + .block() + + callback?.call() + response.statusCode().value() + } + + @Override + SpringWebfluxHttpClientDecorator decorator() { + return SpringWebfluxHttpClientDecorator.DECORATE + } +} From 8f96205579e2f4c5967c7ede56a33a387bb917e1 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Thu, 16 May 2019 14:53:34 +0200 Subject: [PATCH 46/55] Add Javadoc to rawStatusCode method that is not part of ClientResponse in spring-webflux-5.0 --- .../client/ClientResponseWrapper.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java index 463e70921e..20177d11e1 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java @@ -13,7 +13,9 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.context.Context; -/** Wrapper class for ClientResponse that adds Context to the body Publisher */ +/** + * Wrapper class for ClientResponse that adds Context to the body Publisher + */ public class ClientResponseWrapper implements ClientResponse { private final ClientResponse clientResponse; @@ -81,10 +83,19 @@ public class ClientResponseWrapper implements ClientResponse { @Override public Mono>> toEntityList( - final ParameterizedTypeReference typeReference) { + final ParameterizedTypeReference typeReference) { return clientResponse.toEntityList(typeReference); } + /** + * ClientResponseWrapper is based on the ClientResponse from + * spring-webflux-5.0.0.RELEASE. Since spring-webflux 5.1 ClientResponse + * contains extra methods like rewStatusCode and gives methodNotFound + * exceptions at runtime if used in a project with the latest spring-webflux + * 5.1 or higher. + *

+ * See https://docs.spring.io/spring/docs/5.1.x/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html#rawStatusCode-- + */ public int rawStatusCode() { return clientResponse.statusCode().value(); } From bc51f04bd34cd4a4a5f46b84d0f0437438abbf27 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Thu, 16 May 2019 14:54:06 +0200 Subject: [PATCH 47/55] Replace hasSuperType with safeHasSuperType --- .../client/DefaultWebClientInstrumentation.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java index 194191b937..1799aa4781 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java @@ -1,7 +1,7 @@ package datadog.trace.instrumentation.springwebflux.client; +import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType; import static java.util.Collections.singletonMap; -import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -38,7 +38,8 @@ public class DefaultWebClientInstrumentation extends Instrumenter.Default { @Override public ElementMatcher typeMatcher() { - return hasSuperType(named("org.springframework.web.reactive.function.client.ExchangeFunction")); + return safeHasSuperType( + named("org.springframework.web.reactive.function.client.ExchangeFunction")); } @Override From 633db43cb1fc90d57e9ae8cb57edea67ecd4ff40 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Fri, 7 Jun 2019 12:44:19 +0200 Subject: [PATCH 48/55] Fix typo rawStatusCode in javadoc --- .../springwebflux/client/ClientResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java index 20177d11e1..45624d70a4 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java @@ -90,7 +90,7 @@ public class ClientResponseWrapper implements ClientResponse { /** * ClientResponseWrapper is based on the ClientResponse from * spring-webflux-5.0.0.RELEASE. Since spring-webflux 5.1 ClientResponse - * contains extra methods like rewStatusCode and gives methodNotFound + * contains extra methods like rawStatusCode and gives methodNotFound * exceptions at runtime if used in a project with the latest spring-webflux * 5.1 or higher. *

From 0043efa164d8beb7455892f81f366bb50ffe64d0 Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Fri, 7 Jun 2019 12:45:20 +0200 Subject: [PATCH 49/55] Add documentation on conditions when DefaultWebClientAdvice is not applied --- .../client/DefaultWebClientAdvice.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java index b11096450a..30d2a42582 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientAdvice.java @@ -11,13 +11,18 @@ public class DefaultWebClientAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit( - @Advice.Thrown final Throwable throwable, - @Advice.This final ExchangeFunction exchangeFunction, - @Advice.Argument(0) final ClientRequest clientRequest, - @Advice.Return(readOnly = false) Mono mono) { + @Advice.Thrown final Throwable throwable, + @Advice.This final ExchangeFunction exchangeFunction, + @Advice.Argument(0) final ClientRequest clientRequest, + @Advice.Return(readOnly = false) Mono mono) { if (throwable == null - && mono != null - && !clientRequest.headers().keySet().contains("x-datadog-trace-id")) { + && mono != null + // The response of the org.springframework.web.reactive.function.client.ExchangeFunction.exchange method is + // replaced by a decorator that in turn also calls the + // org.springframework.web.reactive.function.client.ExchangeFunction.exchange method. If the original return value + // is already decorated (we detect this if the "x-datadog-trace-id" is added), the result is not decorated again + // to avoid StackOverflowErrors. + && !clientRequest.headers().keySet().contains("x-datadog-trace-id")) { mono = new TracingClientResponseMono(clientRequest, exchangeFunction, GlobalTracer.get()); } } From fff8006e51999fa141eaddc73afb74177a75443b Mon Sep 17 00:00:00 2001 From: Johan Vandeweerd Date: Fri, 7 Jun 2019 12:45:50 +0200 Subject: [PATCH 50/55] Remove unused private method --- .../TracingClientResponseSubscriber.java | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java index 0ac4e509d3..205e625efc 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java @@ -3,8 +3,6 @@ package datadog.trace.instrumentation.springwebflux.client; import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE; import io.opentracing.Span; -import java.net.InetAddress; -import java.net.UnknownHostException; import org.reactivestreams.Subscription; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -19,10 +17,10 @@ public class TracingClientResponseSubscriber implements CoreSubscriber subscriber, - final ClientRequest clientRequest, - final Context context, - final Span span) { + final CoreSubscriber subscriber, + final ClientRequest clientRequest, + final Context context, + final Span span) { this.subscriber = subscriber; this.clientRequest = clientRequest; this.context = context.put(Span.class, span); @@ -34,28 +32,20 @@ public class TracingClientResponseSubscriber implements CoreSubscriber Date: Fri, 14 Jun 2019 11:27:30 -0700 Subject: [PATCH 51/55] Fix trace propagation --- .../DefaultWebClientInstrumentation.java | 1 - .../client/ClientResponseWrapper.java | 102 ----------------- .../client/TracingClientResponseMono.java | 10 +- .../TracingClientResponseSubscriber.java | 107 +++++++++++++----- .../client/SpringWebfluxHttpClientTest.groovy | 71 +++++++++++- .../trace/agent/test/AgentTestRunner.java | 15 ++- .../agent/test/base/HttpClientTest.groovy | 4 +- 7 files changed, 163 insertions(+), 147 deletions(-) delete mode 100644 dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java index 1799aa4781..ca23a46fce 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java/datadog/trace/instrumentation/springwebflux/client/DefaultWebClientInstrumentation.java @@ -32,7 +32,6 @@ public class DefaultWebClientInstrumentation extends Instrumenter.Default { packageName + ".TracingClientResponseSubscriber", packageName + ".TracingClientResponseSubscriber$1", packageName + ".TracingClientResponseMono", - packageName + ".ClientResponseWrapper" }; } diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java deleted file mode 100644 index 45624d70a4..0000000000 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/ClientResponseWrapper.java +++ /dev/null @@ -1,102 +0,0 @@ -package datadog.trace.instrumentation.springwebflux.client; - -import java.util.List; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.reactive.ClientHttpResponse; -import org.springframework.util.MultiValueMap; -import org.springframework.web.reactive.function.BodyExtractor; -import org.springframework.web.reactive.function.client.ClientResponse; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.util.context.Context; - -/** - * Wrapper class for ClientResponse that adds Context to the body Publisher - */ -public class ClientResponseWrapper implements ClientResponse { - - private final ClientResponse clientResponse; - private final Context context; - - public ClientResponseWrapper(final ClientResponse clientResponse, final Context context) { - this.clientResponse = clientResponse; - this.context = context; - } - - @Override - public HttpStatus statusCode() { - return clientResponse.statusCode(); - } - - @Override - public Headers headers() { - return clientResponse.headers(); - } - - @Override - public MultiValueMap cookies() { - return clientResponse.cookies(); - } - - @Override - public T body(final BodyExtractor extractor) { - return clientResponse.body(extractor); - } - - @Override - public Mono bodyToMono(final Class elementClass) { - return clientResponse.bodyToMono(elementClass).subscriberContext(context); - } - - @Override - public Mono bodyToMono(final ParameterizedTypeReference typeReference) { - return clientResponse.bodyToMono(typeReference).subscriberContext(context); - } - - @Override - public Flux bodyToFlux(final Class elementClass) { - return clientResponse.bodyToFlux(elementClass).subscriberContext(context); - } - - @Override - public Flux bodyToFlux(final ParameterizedTypeReference typeReference) { - return clientResponse.bodyToFlux(typeReference).subscriberContext(context); - } - - @Override - public Mono> toEntity(final Class bodyType) { - return clientResponse.toEntity(bodyType); - } - - @Override - public Mono> toEntity(final ParameterizedTypeReference typeReference) { - return clientResponse.toEntity(typeReference); - } - - @Override - public Mono>> toEntityList(final Class elementType) { - return clientResponse.toEntityList(elementType); - } - - @Override - public Mono>> toEntityList( - final ParameterizedTypeReference typeReference) { - return clientResponse.toEntityList(typeReference); - } - - /** - * ClientResponseWrapper is based on the ClientResponse from - * spring-webflux-5.0.0.RELEASE. Since spring-webflux 5.1 ClientResponse - * contains extra methods like rawStatusCode and gives methodNotFound - * exceptions at runtime if used in a project with the latest spring-webflux - * 5.1 or higher. - *

- * See https://docs.spring.io/spring/docs/5.1.x/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html#rawStatusCode-- - */ - public int rawStatusCode() { - return clientResponse.statusCode().value(); - } -} diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java index 8331659176..9170eb8c7b 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseMono.java @@ -2,6 +2,7 @@ package datadog.trace.instrumentation.springwebflux.client; import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE; +import datadog.trace.context.TraceScope; import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.Tracer; @@ -36,13 +37,18 @@ public class TracingClientResponseMono extends Mono { final Span span = tracer - .buildSpan("webflux.request") + .buildSpan("http.request") .asChildOf(parentSpan) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) .start(); DECORATE.afterStart(span); try (final Scope scope = tracer.scopeManager().activate(span, false)) { + + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + final ClientRequest mutatedRequest = ClientRequest.from(clientRequest) .headers( @@ -55,7 +61,7 @@ public class TracingClientResponseMono extends Mono { exchangeFunction .exchange(mutatedRequest) .subscribe( - new TracingClientResponseSubscriber(subscriber, mutatedRequest, context, span)); + new TracingClientResponseSubscriber(subscriber, mutatedRequest, context, span, parentSpan)); } } } diff --git a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java index 205e625efc..b096371dca 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java +++ b/dd-java-agent/instrumentation/spring-webflux/src/main/java8/datadog/trace/instrumentation/springwebflux/client/TracingClientResponseSubscriber.java @@ -2,7 +2,13 @@ package datadog.trace.instrumentation.springwebflux.client; import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE; +import datadog.trace.context.TraceScope; +import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.noop.NoopSpan; +import io.opentracing.util.GlobalTracer; +import java.util.concurrent.atomic.AtomicReference; import org.reactivestreams.Subscription; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -11,71 +17,116 @@ import reactor.util.context.Context; public class TracingClientResponseSubscriber implements CoreSubscriber { + private final Tracer tracer = GlobalTracer.get(); private final CoreSubscriber subscriber; private final ClientRequest clientRequest; private final Context context; - private final Span span; + private final AtomicReference spanRef; + private final Span parentSpan; public TracingClientResponseSubscriber( final CoreSubscriber subscriber, final ClientRequest clientRequest, final Context context, - final Span span) { + final Span span, + final Span parentSpan) { this.subscriber = subscriber; this.clientRequest = clientRequest; - this.context = context.put(Span.class, span); - this.span = span; + this.context = context; + spanRef = new AtomicReference<>(span); + this.parentSpan = parentSpan == null ? NoopSpan.INSTANCE : parentSpan; } @Override public void onSubscribe(final Subscription subscription) { - DECORATE.onRequest(span, clientRequest); + final Span span = spanRef.get(); + if (span == null) { + subscriber.onSubscribe(subscription); + return; + } - subscriber.onSubscribe( - new Subscription() { - @Override - public void request(final long n) { - subscription.request(n); - } + try (final Scope scope = tracer.scopeManager().activate(span, false)) { - @Override - public void cancel() { - DECORATE.onCancel(span); - DECORATE.beforeFinish(span); - subscription.cancel(); - span.finish(); - } - }); + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + + DECORATE.onRequest(span, clientRequest); + + subscriber.onSubscribe( + new Subscription() { + @Override + public void request(final long n) { + try (final Scope scope = tracer.scopeManager().activate(span, false)) { + subscription.request(n); + } + } + + @Override + public void cancel() { + DECORATE.onCancel(span); + DECORATE.beforeFinish(span); + subscription.cancel(); + span.finish(); + } + }); + } } @Override public void onNext(final ClientResponse clientResponse) { - try { - subscriber.onNext(new ClientResponseWrapper(clientResponse, context)); - } finally { + final Span span = spanRef.getAndSet(null); + if (span != null) { DECORATE.onResponse(span, clientResponse); + DECORATE.beforeFinish(span); + span.finish(); + } + + try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) { + + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + + subscriber.onNext(clientResponse); } } @Override public void onError(final Throwable throwable) { - try { - subscriber.onError(throwable); - } finally { + final Span span = spanRef.getAndSet(null); + if (span != null) { DECORATE.onError(span, throwable); DECORATE.beforeFinish(span); span.finish(); } + + try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) { + + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + + subscriber.onError(throwable); + } } @Override public void onComplete() { - try { - subscriber.onComplete(); - } finally { + final Span span = spanRef.getAndSet(null); + if (span != null) { DECORATE.beforeFinish(span); span.finish(); } + + try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) { + + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + + subscriber.onComplete(); + } } @Override diff --git a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy index 7a097ed937..da3b6f36e8 100644 --- a/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy +++ b/dd-java-agent/instrumentation/spring-webflux/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy @@ -1,7 +1,14 @@ package dd.trace.instrumentation.springwebflux.client +import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.DDTags +import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator import datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator +import io.opentracing.tag.Tags +import io.opentracing.util.GlobalTracer +import org.springframework.http.HttpMethod import org.springframework.web.reactive.function.client.ClientResponse import org.springframework.web.reactive.function.client.WebClient import spock.lang.Shared @@ -13,14 +20,20 @@ class SpringWebfluxHttpClientTest extends HttpClientTest headers, Closure callback) { - assert method == "GET" - ClientResponse response = client.get() - .headers({ h -> headers.forEach({ key, value -> h.add(key, value) }) }) + def hasParent = GlobalTracer.get().activeSpan() != null + ClientResponse response = client.method(HttpMethod.resolve(method)) + .headers { h -> headers.forEach({ key, value -> h.add(key, value) }) } .uri(uri) .exchange() + .doOnSuccessOrError { success, error -> + blockUntilChildSpansFinished(1) + callback?.call() + } .block() - callback?.call() + if(hasParent) { + blockUntilChildSpansFinished(callback ? 3 : 2) + } response.statusCode().value() } @@ -28,4 +41,54 @@ class SpringWebfluxHttpClientTest extends HttpClientTest extends AgentTestRu def "trace request with callback and no parent"() { when: def status = doRequest(method, server.address.resolve("/success"), ["is-dd-server": "false"]) { - runUnderTrace("child") { + runUnderTrace("callback") { // Ensure consistent ordering of traces for assertion. TEST_WRITER.waitForTraces(1) } @@ -202,7 +202,7 @@ abstract class HttpClientTest extends AgentTestRu clientSpan(it, 0, null, method, false) } trace(1, 1) { - basicSpan(it, 0, "child") + basicSpan(it, 0, "callback") } } From b6c3d9a200c16dd22e602f6c77b5099390fe4cf9 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Wed, 24 Jul 2019 16:53:57 -0400 Subject: [PATCH 52/55] Remove the request with a short sleep The test still "does something" in that it has a method with a @Trace annotation. Doesn't need to call an external url --- .../datadog/smoketest/cli/CliApplication.java | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java index 2dc0309d97..3d44ab43de 100644 --- a/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java +++ b/dd-smoke-tests/cli/src/main/java/datadog/smoketest/cli/CliApplication.java @@ -1,11 +1,8 @@ package datadog.smoketest.cli; import datadog.trace.api.Trace; -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; -/** Simple application that makes a request to example.com then quits. */ +/** Simple application that sleeps then quits. */ public class CliApplication { public static void main(final String[] args) throws InterruptedException { @@ -14,23 +11,15 @@ public class CliApplication { // Sleep to ensure all of the processes are running Thread.sleep(5000); - System.out.println("Making request"); + System.out.println("Calling example trace"); - app.makeRequest(); + app.exampleTrace(); - System.out.println("Finished making request"); + System.out.println("Finished calling example trace"); } @Trace(operationName = "example") - public void makeRequest() { - try { - final URL url = new URL("http://www.example.com/"); - final URLConnection connection = url.openConnection(); - connection.setConnectTimeout(1000); - connection.connect(); - connection.getInputStream(); - } catch (final IOException e) { - // Ignore. The goal of this app is to attempt the connection not necessarily to succeed - } + public void exampleTrace() throws InterruptedException { + Thread.sleep(500); } } From 0d1bf5edd7f3007984b543e166cd85594ee61e0b Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 25 Jul 2019 15:32:54 -0700 Subject: [PATCH 53/55] Limit Vert.x tests to 3.x 4.0.0-milestone1 was just released and is incompatible. --- dd-java-agent/instrumentation/vertx/vertx.gradle | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx.gradle b/dd-java-agent/instrumentation/vertx/vertx.gradle index a804990ea9..95a0d298c8 100644 --- a/dd-java-agent/instrumentation/vertx/vertx.gradle +++ b/dd-java-agent/instrumentation/vertx/vertx.gradle @@ -52,8 +52,9 @@ dependencies { testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.5.0' testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.5.0' - latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+' - latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '+' - latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '+' - latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '+' + // Vert.x 4.0 is incompatible with our tests. + latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '3.+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.+' } From e98727620f027c7e0e9ada69112d1438db840919 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 25 Jul 2019 15:08:55 -0700 Subject: [PATCH 54/55] Version 0.31.0 --- dd-trace-java.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-trace-java.gradle b/dd-trace-java.gradle index 16a39866f7..788a956df7 100644 --- a/dd-trace-java.gradle +++ b/dd-trace-java.gradle @@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null allprojects { group = 'com.datadoghq' - version = '0.31.0-SNAPSHOT' + version = '0.31.0' if (isCI) { buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/" From a0b5a15e17998fa20f91e56580514150e8981aa2 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 25 Jul 2019 16:15:19 -0700 Subject: [PATCH 55/55] Begin 0.32.0 --- dd-trace-java.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-trace-java.gradle b/dd-trace-java.gradle index 788a956df7..6e68a380b6 100644 --- a/dd-trace-java.gradle +++ b/dd-trace-java.gradle @@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null allprojects { group = 'com.datadoghq' - version = '0.31.0' + version = '0.32.0-SNAPSHOT' if (isCI) { buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"