Skip to content

Commit 9e0c64a

Browse files
johnrengelmanJohn Engelman
authored andcommitted
Add agent instrumentation for Ratpack 1.7
1 parent 2b5c4f5 commit 9e0c64a

File tree

21 files changed

+724
-25
lines changed

21 files changed

+724
-25
lines changed

instrumentation/ratpack/ratpack-1.4/testing/src/main/java/io/opentelemetry/instrumentation/ratpack/client/AbstractRatpackHttpClientTest.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929

3030
public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void> {
3131

32-
private final ExecHarness exec = ExecHarness.harness();
32+
protected final ExecHarness exec = ExecHarness.harness();
3333

34-
private HttpClient client;
35-
private HttpClient singleConnectionClient;
34+
protected HttpClient client;
35+
protected HttpClient singleConnectionClient;
3636

3737
@BeforeAll
38-
void setUpClient() throws Exception {
38+
protected void setUpClient() throws Exception {
3939
exec.run(
4040
unused -> {
4141
client = buildHttpClient();
@@ -66,7 +66,7 @@ public Void buildRequest(String method, URI uri, Map<String, String> headers) {
6666
@Override
6767
public int sendRequest(Void request, String method, URI uri, Map<String, String> headers)
6868
throws Exception {
69-
return exec.yield(unused -> internalSendRequest(client, method, uri, headers))
69+
return exec.yield(execution -> internalSendRequest(client, method, uri, headers))
7070
.getValueOrThrow();
7171
}
7272

@@ -78,13 +78,17 @@ public final void sendRequestWithCallback(
7878
Map<String, String> headers,
7979
HttpClientResult httpClientResult)
8080
throws Exception {
81-
exec.execute(
82-
Operation.of(
83-
() ->
84-
internalSendRequest(client, method, uri, headers)
85-
.result(
86-
result ->
87-
httpClientResult.complete(result::getValue, result.getThrowable()))));
81+
exec.yield(
82+
(e) ->
83+
Operation.of(
84+
() ->
85+
internalSendRequest(client, method, uri, headers)
86+
.result(
87+
result ->
88+
httpClientResult.complete(
89+
result::getValue, result.getThrowable())))
90+
.promise())
91+
.getValueOrThrow();
8892
}
8993

9094
// overridden in RatpackForkedHttpClientTest
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("io.ratpack")
8+
module.set("ratpack-core")
9+
versions.set("[1.7.0,)")
10+
}
11+
}
12+
13+
dependencies {
14+
library("io.ratpack:ratpack-core:1.7.0")
15+
16+
implementation(project(":instrumentation:netty:netty-4.1:library"))
17+
implementation(project(":instrumentation:ratpack:ratpack-1.4:javaagent"))
18+
implementation(project(":instrumentation:ratpack:ratpack-1.7:library"))
19+
20+
testImplementation(project(":instrumentation:ratpack:ratpack-1.4:testing"))
21+
22+
testLibrary("io.ratpack:ratpack-test:1.7.0")
23+
24+
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) {
25+
testImplementation("com.sun.activation:jakarta.activation:1.2.2")
26+
}
27+
}
28+
29+
// to allow all tests to pass we need to choose a specific netty version
30+
if (!(findProperty("testLatestDeps") as Boolean)) {
31+
configurations.configureEach {
32+
if (!name.contains("muzzle")) {
33+
resolutionStrategy {
34+
eachDependency {
35+
// specifying a fixed version for all libraries with io.netty group
36+
if (requested.group == "io.netty" && requested.name != "netty-tcnative") {
37+
useVersion("4.1.37.Final")
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
45+
tasks {
46+
withType<Test>().configureEach {
47+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
48+
}
49+
}
50+
51+
tasks.withType<Test>().configureEach {
52+
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
7+
8+
import io.opentelemetry.context.Context;
9+
import io.opentelemetry.context.Scope;
10+
import ratpack.exec.Downstream;
11+
12+
public class DownstreamWrapper<T> implements Downstream<T> {
13+
14+
private final Downstream<T> delegate;
15+
private final Context parentContext;
16+
17+
private DownstreamWrapper(Downstream<T> delegate, Context parentContext) {
18+
assert parentContext != null;
19+
this.delegate = delegate;
20+
this.parentContext = parentContext;
21+
}
22+
23+
@Override
24+
public void success(T value) {
25+
try (Scope ignored = parentContext.makeCurrent()) {
26+
delegate.success(value);
27+
}
28+
}
29+
30+
@Override
31+
public void error(Throwable throwable) {
32+
try (Scope ignored = parentContext.makeCurrent()) {
33+
delegate.error(throwable);
34+
}
35+
}
36+
37+
@Override
38+
public void complete() {
39+
try (Scope ignored = parentContext.makeCurrent()) {
40+
delegate.complete();
41+
}
42+
}
43+
44+
public static <T> Downstream<T> wrapIfNeeded(Downstream<T> delegate) {
45+
if (delegate instanceof DownstreamWrapper) {
46+
return delegate;
47+
}
48+
Context context = Context.current();
49+
if (context == Context.root()) {
50+
// Skip wrapping, there is no need to propagate root context.
51+
return delegate;
52+
}
53+
return new DownstreamWrapper<>(delegate, context);
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
10+
import static net.bytebuddy.matcher.ElementMatchers.named;
11+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
12+
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import net.bytebuddy.asm.Advice;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
import net.bytebuddy.matcher.ElementMatcher;
18+
import ratpack.http.client.HttpClient;
19+
20+
public class HttpClientInstrumentation implements TypeInstrumentation {
21+
22+
@Override
23+
public ElementMatcher<TypeDescription> typeMatcher() {
24+
return named("ratpack.http.client.HttpClient");
25+
}
26+
27+
@Override
28+
public void transform(TypeTransformer transformer) {
29+
transformer.applyAdviceToMethod(
30+
isMethod()
31+
.and(isStatic())
32+
.and(named("of"))
33+
.and(takesArgument(0, named("ratpack.func.Action"))),
34+
HttpClientInstrumentation.class.getName() + "$OfAdvice");
35+
}
36+
37+
@SuppressWarnings("unused")
38+
public static class OfAdvice {
39+
40+
@Advice.OnMethodExit(suppress = Throwable.class)
41+
public static void injectTracing(@Advice.Return(readOnly = false) HttpClient httpClient)
42+
throws Exception {
43+
httpClient = RatpackSingletons.telemetry().instrumentHttpClient(httpClient);
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Arrays.asList;
10+
11+
import com.google.auto.service.AutoService;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
15+
import java.util.List;
16+
import net.bytebuddy.matcher.ElementMatcher;
17+
18+
@AutoService(InstrumentationModule.class)
19+
public class RatpackInstrumentationModule extends InstrumentationModule
20+
implements ExperimentalInstrumentationModule {
21+
public RatpackInstrumentationModule() {
22+
super("ratpack", "ratpack-1.7");
23+
}
24+
25+
@Override
26+
public String getModuleGroup() {
27+
// relies on netty
28+
return "netty";
29+
}
30+
31+
@Override
32+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
33+
// Only activate when running ratpack 1.7 or later
34+
return hasClassesNamed("ratpack.exec.util.retry.Delay");
35+
}
36+
37+
@Override
38+
public List<TypeInstrumentation> typeInstrumentations() {
39+
return asList(
40+
new ServerRegistryInstrumentation(),
41+
new HttpClientInstrumentation(),
42+
new RequestActionSupportInstrumentation());
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
7+
8+
import io.netty.channel.Channel;
9+
import io.opentelemetry.api.GlobalOpenTelemetry;
10+
import io.opentelemetry.context.Context;
11+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
12+
import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys;
13+
import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackTelemetry;
14+
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.ContextHolder;
15+
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
16+
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
17+
import ratpack.exec.Execution;
18+
19+
public final class RatpackSingletons {
20+
21+
static {
22+
TELEMETRY =
23+
RatpackTelemetry.builder(GlobalOpenTelemetry.get())
24+
.configure(AgentCommonConfig.get())
25+
.build();
26+
}
27+
28+
private static final Instrumenter<String, Void> INSTRUMENTER =
29+
Instrumenter.<String, Void>builder(
30+
GlobalOpenTelemetry.get(), "io.opentelemetry.ratpack-1.7", s -> s)
31+
.setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled())
32+
.buildInstrumenter();
33+
34+
public static Instrumenter<String, Void> instrumenter() {
35+
return INSTRUMENTER;
36+
}
37+
38+
private static final RatpackTelemetry TELEMETRY;
39+
40+
public static RatpackTelemetry telemetry() {
41+
return TELEMETRY;
42+
}
43+
44+
public static void propagateContextToChannel(Execution execution, Channel channel) {
45+
Context parentContext =
46+
execution
47+
.maybeGet(ContextHolder.class)
48+
.map(ContextHolder::context)
49+
.orElse(Context.current());
50+
channel.attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext);
51+
}
52+
53+
private RatpackSingletons() {}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPrivate;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
13+
14+
import io.netty.channel.Channel;
15+
import io.opentelemetry.context.Context;
16+
import io.opentelemetry.context.Scope;
17+
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.ContextHolder;
18+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
19+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.description.type.TypeDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import ratpack.exec.Downstream;
24+
import ratpack.exec.Execution;
25+
26+
public class RequestActionSupportInstrumentation implements TypeInstrumentation {
27+
28+
@Override
29+
public ElementMatcher<TypeDescription> typeMatcher() {
30+
return extendsClass(named("ratpack.http.client.internal.RequestActionSupport"));
31+
}
32+
33+
@Override
34+
public void transform(TypeTransformer transformer) {
35+
transformer.applyAdviceToMethod(
36+
isMethod()
37+
.and(isPrivate())
38+
.and(named("send"))
39+
.and(takesArgument(0, named("ratpack.exec.Downstream")))
40+
.and(takesArgument(1, named("io.netty.channel.Channel"))),
41+
RequestActionSupportInstrumentation.class.getName() + "$SendAdvice");
42+
transformer.applyAdviceToMethod(
43+
isMethod().and(named("connect")).and(takesArgument(0, named("ratpack.exec.Downstream"))),
44+
RequestActionSupportInstrumentation.class.getName() + "$ConnectAdvice");
45+
}
46+
47+
@SuppressWarnings("unused")
48+
public static class SendAdvice {
49+
50+
@Advice.OnMethodEnter(suppress = Throwable.class)
51+
public static void injectChannelAttribute(
52+
@Advice.FieldValue("execution") Execution execution,
53+
@Advice.Argument(value = 0, readOnly = false) Downstream<?> downstream,
54+
@Advice.Argument(value = 1, readOnly = false) Channel channel) {
55+
RatpackSingletons.propagateContextToChannel(execution, channel);
56+
}
57+
}
58+
59+
public static class ConnectAdvice {
60+
61+
@Advice.OnMethodEnter(suppress = Throwable.class)
62+
public static Scope injectChannelAttribute(
63+
@Advice.FieldValue("execution") Execution execution,
64+
@Advice.Argument(value = 0, readOnly = false) Downstream<?> downstream) {
65+
// Propagate the current context to downstream
66+
// since that the is the subsequent
67+
downstream = DownstreamWrapper.wrapIfNeeded(downstream);
68+
69+
// Capture the CLIENT span and make it current before cally Netty layer
70+
return execution
71+
.maybeGet(ContextHolder.class)
72+
.map(ContextHolder::context)
73+
.map(Context::makeCurrent)
74+
.orElse(null);
75+
}
76+
77+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
78+
public static void exit(@Advice.Enter Scope scope) {
79+
if (scope != null) {
80+
scope.close();
81+
}
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)