Refactor Ratpack

Remove the use of the OT project and Execution managed scopes. Updated tests.
This commit is contained in:
Gary 2018-11-09 02:42:49 -05:00 committed by Gary Huang
parent cf033baf7e
commit 6475f20308
No known key found for this signature in database
GPG Key ID: 0CB168EE6C6844B7
7 changed files with 310 additions and 137 deletions

View File

@ -51,7 +51,6 @@ testSets {
dependencies {
main_java8CompileOnly group: 'io.ratpack', name: 'ratpack-core', version: '1.4.0'
main_java8Compile project(':dd-trace-ot')
main_java8Compile project(':dd-java-agent:agent-tooling')
main_java8Compile deps.bytebuddy
@ -65,6 +64,7 @@ dependencies {
compile sourceSets.main_java8.output
testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
testCompile group: 'io.ratpack', name: 'ratpack-groovy-test', version: '1.4.0'
latestDepTestCompile group: 'io.ratpack', name: 'ratpack-groovy-test', version: '+'
}

View File

@ -55,9 +55,6 @@ public final class RatpackHttpClientInstrumentation extends Instrumenter.Default
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$StreamedResponseAction",
"datadog.trace.instrumentation.ratpack.impl.RequestSpecInjectAdapter",
"datadog.trace.instrumentation.ratpack.impl.WrappedRequestSpec",
// core helpers
"datadog.opentracing.scopemanager.ContextualScopeManager",
"datadog.opentracing.scopemanager.ScopeContext"
};
}

View File

@ -55,13 +55,8 @@ public final class RatpackInstrumentation extends Instrumenter.Default {
@Override
public String[] helperClassNames() {
return new String[] {
// core helpers
"datadog.opentracing.scopemanager.ContextualScopeManager",
"datadog.opentracing.scopemanager.ScopeContext",
// service registry helpers
"datadog.trace.instrumentation.ratpack.impl.RatpackRequestExtractAdapter",
"datadog.trace.instrumentation.ratpack.impl.RatpackScopeManager",
"datadog.trace.instrumentation.ratpack.impl.RatpackScopeManager$RatpackScope",
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice",
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$RatpackServerRegistryAdvice",
"datadog.trace.instrumentation.ratpack.impl.TracingHandler"
@ -102,9 +97,6 @@ public final class RatpackInstrumentation extends Instrumenter.Default {
@Override
public String[] helperClassNames() {
return new String[] {
// core helpers
"datadog.opentracing.scopemanager.ContextualScopeManager",
"datadog.opentracing.scopemanager.ScopeContext",
// exec helpers
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$ExecStarterAdvice",
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$ExecStarterAction"

View File

@ -1,82 +0,0 @@
package datadog.trace.instrumentation.ratpack.impl;
import datadog.opentracing.scopemanager.ScopeContext;
import io.opentracing.Scope;
import io.opentracing.Span;
import ratpack.exec.Execution;
import ratpack.exec.UnmanagedThreadException;
/**
* This scope manager uses the Ratpack Execution to store the current Scope. This is a ratpack
* registry analogous to a ThreadLocal but for an execution that may transfer between several
* threads
*/
public final class RatpackScopeManager implements ScopeContext {
@Override
public boolean inContext() {
return Execution.isManagedThread();
}
@Override
public Scope activate(Span span, boolean finishSpanOnClose) {
Execution execution = Execution.current();
RatpackScope ratpackScope =
new RatpackScope(
span, finishSpanOnClose, execution.maybeGet(RatpackScope.class).orElse(null));
// remove any existing RatpackScopes before adding it to the registry
execution
.maybeGet(RatpackScope.class)
.ifPresent(ignored -> execution.remove(RatpackScope.class));
execution.add(RatpackScope.class, ratpackScope);
execution.onComplete(
ratpackScope); // ensure that the scope is closed when the execution finishes
return ratpackScope;
}
@Override
public Scope active() {
try {
return Execution.current().maybeGet(RatpackScope.class).orElse(null);
} catch (UnmanagedThreadException ume) {
return null; // should never happen due to inContextCheck
}
}
static class RatpackScope implements Scope {
private final Span wrapped;
private final boolean finishOnClose;
private final RatpackScope toRestore;
RatpackScope(Span wrapped, boolean finishOnClose, RatpackScope toRestore) {
this.wrapped = wrapped;
this.finishOnClose = finishOnClose;
this.toRestore = toRestore;
}
@Override
public Span span() {
return wrapped;
}
@Override
public void close() {
Execution execution = Execution.current();
// only close if this scope is the current scope for this Execution
// As with ThreadLocalScope this shouldn't happen if users call methods in the expected order
execution
.maybeGet(RatpackScope.class)
.filter(s -> this == s)
.ifPresent(
ignore -> {
if (finishOnClose) {
wrapped.finish();
}
// pop the execution "stack"
execution.remove(RatpackScope.class);
if (toRestore != null) {
execution.add(toRestore);
}
});
}
}
}

View File

@ -1,6 +1,5 @@
package datadog.trace.instrumentation.ratpack.impl;
import datadog.opentracing.scopemanager.ContextualScopeManager;
import io.opentracing.Scope;
import io.opentracing.ScopeManager;
import io.opentracing.util.GlobalTracer;
@ -17,25 +16,12 @@ public class RatpackServerAdvice {
public static class RatpackServerRegistryAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void injectTracing(@Advice.Return(readOnly = false) Registry registry) {
RatpackScopeManager ratpackScopeManager = new RatpackScopeManager();
// the value returned from ServerRegistry.buildBaseRegistry needs to be modified to add our
// scope manager and handler decorator to the registry
//noinspection UnusedAssignment
registry =
registry.join(
Registry.builder()
.add(ScopeManager.class, ratpackScopeManager)
.add(ScopeManager.class, GlobalTracer.get().scopeManager())
.add(HandlerDecorator.prepend(new TracingHandler()))
.build());
if (GlobalTracer.isRegistered()) {
if (GlobalTracer.get().scopeManager() instanceof ContextualScopeManager) {
((ContextualScopeManager) GlobalTracer.get().scopeManager())
.addScopeContext(ratpackScopeManager);
}
} else {
log.warn("No GlobalTracer registered");
}
}
}
@ -43,9 +29,8 @@ public class RatpackServerAdvice {
@Advice.OnMethodEnter
public static void addScopeToRegistry(
@Advice.Argument(value = 0, readOnly = false) Action<? super RegistrySpec> action) {
Scope active = GlobalTracer.get().scopeManager().active();
final Scope active = GlobalTracer.get().scopeManager().active();
if (active != null) {
//noinspection UnusedAssignment
action = new ExecStarterAction(active).append(action);
}
}
@ -53,8 +38,8 @@ public class RatpackServerAdvice {
public static class ExecutionAdvice {
@Advice.OnMethodExit
public static void addScopeToRegistry(@Advice.Return ExecStarter starter) {
Scope active = GlobalTracer.get().scopeManager().active();
public static void addScopeToRegistry(@Advice.Return final ExecStarter starter) {
final Scope active = GlobalTracer.get().scopeManager().active();
if (active != null) {
starter.register(new ExecStarterAction(active));
}
@ -64,13 +49,12 @@ public class RatpackServerAdvice {
public static class ExecStarterAction implements Action<RegistrySpec> {
private final Scope active;
@SuppressWarnings("WeakerAccess")
public ExecStarterAction(Scope active) {
public ExecStarterAction(final Scope active) {
this.active = active;
}
@Override
public void execute(RegistrySpec spec) {
public void execute(final RegistrySpec spec) {
if (active != null) {
spec.add(active);
}

View File

@ -2,6 +2,7 @@ package datadog.trace.instrumentation.ratpack.impl;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.SpanContext;
@ -37,9 +38,16 @@ public final class TracingHandler implements Handler {
.withTag(Tags.HTTP_URL.getKey(), request.getUri())
.startActive(true);
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
ctx.getResponse()
.beforeSend(
response -> {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(false);
}
final Span span = scope.span();
span.setTag(DDTags.RESOURCE_NAME, getResourceName(ctx));
final Status status = response.getStatus();
@ -49,6 +57,7 @@ public final class TracingHandler implements Handler {
}
Tags.HTTP_STATUS.set(span, status.getCode());
}
scope.close();
});

View File

@ -1,9 +1,10 @@
import datadog.opentracing.scopemanager.ContextualScopeManager
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.instrumentation.ratpack.impl.RatpackScopeManager
import datadog.trace.api.DDTags
import datadog.trace.context.TraceScope
import io.opentracing.Scope
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
@ -37,12 +38,15 @@ class RatpackTest extends AgentTestRunner {
.url(app.address.toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 200
resp.body.string() == "success"
<<<<<<< HEAD
TEST_WRITER.size() == 1
def trace = TEST_WRITER.firstTrace()
trace.size() == 1
@ -60,6 +64,29 @@ class RatpackTest extends AgentTestRunner {
span.context().tags["http.status_code"] == 200
span.context().tags["thread.name"] != null
span.context().tags["thread.id"] != null
=======
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored false
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "/"
defaultTags()
}
}
}
}
>>>>>>> 1bfa7e47... Refactor Ratpack
}
def "test path with bindings call"() {
@ -77,12 +104,15 @@ class RatpackTest extends AgentTestRunner {
.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"
<<<<<<< HEAD
TEST_WRITER.size() == 1
def trace = TEST_WRITER.firstTrace()
trace.size() == 1
@ -100,6 +130,29 @@ class RatpackTest extends AgentTestRunner {
span.context().tags["http.status_code"] == 200
span.context().tags["thread.name"] != null
span.context().tags["thread.id"] != null
=======
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "GET /:foo/:bar?/baz"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored false
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "/a/b/baz"
defaultTags()
}
}
}
}
>>>>>>> 1bfa7e47... Refactor Ratpack
}
def "test error response"() {
@ -107,7 +160,9 @@ class RatpackTest extends AgentTestRunner {
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
context.clientError(500)
context.render(Promise.sync {
return "fail " + 0 / 0
})
}
}
}
@ -120,6 +175,7 @@ class RatpackTest extends AgentTestRunner {
then:
resp.code() == 500
<<<<<<< HEAD
TEST_WRITER.size() == 1
def trace = TEST_WRITER.firstTrace()
trace.size() == 1
@ -137,6 +193,31 @@ class RatpackTest extends AgentTestRunner {
span.context().tags["http.status_code"] == 500
span.context().tags["thread.name"] != null
span.context().tags["thread.id"] != null
=======
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$Tags.HTTP_URL.key" "/"
"error" true
// errorTags(Exception, String) // TODO: find out how to get throwable in instrumentation
defaultTags()
}
}
}
}
>>>>>>> 1bfa7e47... Refactor Ratpack
}
def "test path call using ratpack http client"() {
@ -173,8 +254,10 @@ class RatpackTest extends AgentTestRunner {
.url(app.address.toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 200
resp.body().string() == "success"
@ -182,6 +265,7 @@ class RatpackTest extends AgentTestRunner {
// 3rd is the three traces, ratpack, http client 2 and http client 1
// 2nd is nested2 from the external server (the result of the 2nd internal http client call)
// 1st is nested from the external server (the result of the 1st internal http client call)
<<<<<<< HEAD
TEST_WRITER.size() == 3
def trace = TEST_WRITER.get(2)
trace.size() == 3
@ -212,9 +296,121 @@ class RatpackTest extends AgentTestRunner {
clientTrace1.context().tags["http.status_code"] == 200
clientTrace1.context().tags["thread.name"] != null
clientTrace1.context().tags["thread.id"] != null
=======
assertTraces(3) {
// simulated external system, first call
trace(0, 1) {
span(0) {
resourceName "GET /nested"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(trace(2).get(2))
errored false
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "/nested"
defaultTags(true)
}
}
}
// simulated external system, second call
trace(1, 1) {
span(0) {
resourceName "GET /nested2"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(trace(2).get(1))
errored false
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "/nested2"
defaultTags(true)
}
}
}
trace(2, 3) {
// main app span that processed the request from OKHTTP request
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored false
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "/"
defaultTags()
}
}
// Second http client call that receives the 'ess' of Success
span(1) {
resourceName "GET /?"
serviceName "unnamed-java-app"
operationName "ratpack.client-request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(0))
errored false
tags {
"$Tags.COMPONENT.key" "httpclient"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${external.address}nested2"
defaultTags()
}
}
// First http client call that receives the 'Succ' of Success
span(2) {
resourceName "GET /nested"
serviceName "unnamed-java-app"
operationName "ratpack.client-request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(0))
errored false
tags {
"$Tags.COMPONENT.key" "httpclient"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${external.address}nested"
defaultTags()
}
}
}
}
}
def clientTrace2 = trace[2] // First http client call that receives the 'Succ' of Success
def "test forked path call and start span in handler (#startSpanInHandler)"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
>>>>>>> 1bfa7e47... Refactor Ratpack
final Scope scope = !startSpanInHandler ? GlobalTracer.get().scopeManager().active() :
GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.startActive(true)
<<<<<<< HEAD
clientTrace2.context().serviceName == "unnamed-java-app"
clientTrace2.context().operationName == "ratpack.client-request"
clientTrace1.context().tags["component"] == "ratpack-httpclient"
@ -225,11 +421,23 @@ class RatpackTest extends AgentTestRunner {
clientTrace2.context().tags["http.status_code"] == 200
clientTrace2.context().tags["thread.name"] != null
clientTrace2.context().tags["thread.id"] != null
=======
((TraceScope) scope).setAsyncPropagation(true)
scope.span().setBaggageItem("test-baggage", "foo")
context.render(testPromise(startSpanInHandler).fork())
}
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.get()
.build()
>>>>>>> 1bfa7e47... Refactor Ratpack
def nestedTrace = TEST_WRITER.get(1)
nestedTrace.size() == 1
def nestedSpan = nestedTrace[0] // simulated external system, second call
when:
def resp = client.newCall(request).execute()
<<<<<<< HEAD
nestedSpan.context().serviceName == "unnamed-java-app"
nestedSpan.context().operationName == "ratpack.handler"
nestedSpan.context().resourceName == "GET /nested2"
@ -242,11 +450,49 @@ class RatpackTest extends AgentTestRunner {
nestedSpan.context().tags["http.status_code"] == 200
nestedSpan.context().tags["thread.name"] != null
nestedSpan.context().tags["thread.id"] != null
=======
then:
resp.code() == 200
resp.body().string() == "foo"
>>>>>>> 1bfa7e47... Refactor Ratpack
def nestedTrace2 = TEST_WRITER.get(0)
nestedTrace2.size() == 1
def nestedSpan2 = nestedTrace2[0] // simulated external system, first call
assertTraces(1) {
trace(0, (startSpanInHandler ? 2 : 1)) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored false
tags {
"$Tags.COMPONENT.key" "handler"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "/"
defaultTags()
}
}
if (startSpanInHandler) {
span(1) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored false
tags {
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
defaultTags()
}
}
}
}
}
<<<<<<< HEAD
nestedSpan2.context().serviceName == "unnamed-java-app"
nestedSpan2.context().operationName == "ratpack.handler"
nestedSpan2.context().resourceName == "GET /nested"
@ -259,33 +505,60 @@ class RatpackTest extends AgentTestRunner {
nestedSpan2.context().tags["http.status_code"] == 200
nestedSpan2.context().tags["thread.name"] != null
nestedSpan2.context().tags["thread.id"] != null
=======
where:
startSpanInHandler << [true, false]
>>>>>>> 1bfa7e47... Refactor Ratpack
}
def "forked executions inherit parent scope"() {
when:
def result = ExecHarness.yieldSingle({ spec ->
// This does the work of the initial instrumentation that occurs on the server registry. Because we are using
// ExecHarness for testing this does not get executed by the instrumentation
def ratpackScopeManager = new RatpackScopeManager()
spec.add(ratpackScopeManager)
((ContextualScopeManager) GlobalTracer.get().scopeManager())
.addScopeContext(ratpackScopeManager)
}, {
def result = ExecHarness.yieldSingle({}, {
final Scope scope =
GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.startActive(true)
((TraceScope) scope).setAsyncPropagation(true)
scope.span().setBaggageItem("test-baggage", "foo")
ParallelBatch.of(testPromise(), testPromise()).yield()
ParallelBatch.of(testPromise(false), testPromise(false))
.yield()
.map({ now ->
// close the scope now that we got the baggage inside the promises
scope.close()
return now
})
})
then:
result.valueOrThrow == ["foo", "foo"]
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
parent()
errored false
tags {
defaultTags()
}
}
}
}
}
Promise<String> testPromise() {
// returns a promise that contains the active scope's "test-baggage" baggage
// will close an active scope if closeSpan is set to true
Promise<String> testPromise(boolean closeSpan = true) {
Promise.sync {
GlobalTracer.get().activeSpan().getBaggageItem("test-baggage")
Scope tracerScope = GlobalTracer.get().scopeManager().active()
String res = tracerScope.span().getBaggageItem("test-baggage")
if (closeSpan) {
tracerScope.close()
}
return res
}
}
}