Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add agent instrumentation for Ratpack 1.7 #12572

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@

public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void> {

private final ExecHarness exec = ExecHarness.harness();
protected final ExecHarness exec = ExecHarness.harness();

private HttpClient client;
private HttpClient singleConnectionClient;
protected HttpClient client;
protected HttpClient singleConnectionClient;

@BeforeAll
void setUpClient() throws Exception {
protected void setUpClient() throws Exception {
exec.run(
unused -> {
client = buildHttpClient();
Expand Down Expand Up @@ -66,7 +66,7 @@ public Void buildRequest(String method, URI uri, Map<String, String> headers) {
@Override
public int sendRequest(Void request, String method, URI uri, Map<String, String> headers)
throws Exception {
return exec.yield(unused -> internalSendRequest(client, method, uri, headers))
return exec.yield(execution -> internalSendRequest(client, method, uri, headers))
.getValueOrThrow();
}

Expand All @@ -78,13 +78,17 @@ public final void sendRequestWithCallback(
Map<String, String> headers,
HttpClientResult httpClientResult)
throws Exception {
exec.execute(
Operation.of(
() ->
internalSendRequest(client, method, uri, headers)
.result(
result ->
httpClientResult.complete(result::getValue, result.getThrowable()))));
exec.yield(
(e) ->
Operation.of(
() ->
internalSendRequest(client, method, uri, headers)
.result(
result ->
httpClientResult.complete(
result::getValue, result.getThrowable())))
.promise())
.getValueOrThrow();
}

// overridden in RatpackForkedHttpClientTest
Expand Down
36 changes: 36 additions & 0 deletions instrumentation/ratpack/ratpack-1.7/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("io.ratpack")
module.set("ratpack-core")
versions.set("[1.7.0,)")
}
}

dependencies {
library("io.ratpack:ratpack-core:1.7.0")

implementation(project(":instrumentation:netty:netty-4.1:library"))
implementation(project(":instrumentation:ratpack:ratpack-1.7:library"))

testLibrary("io.ratpack:ratpack-test:1.7.0")
testImplementation(project(":instrumentation:ratpack:ratpack-1.4:testing"))
testInstrumentation(project(":instrumentation:ratpack:ratpack-1.4:javaagent"))

if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) {
testImplementation("com.sun.activation:jakarta.activation:1.2.2")
}
}

tasks {
withType<Test>().configureEach {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
}
}

tasks.withType<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.common.collect.ImmutableList;
import io.opentelemetry.instrumentation.ratpack.v1_7.OpenTelemetryExecInitializer;
import io.opentelemetry.instrumentation.ratpack.v1_7.OpenTelemetryExecInterceptor;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
import net.bytebuddy.asm.Advice.AssignReturned.ToFields.ToField;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import ratpack.exec.ExecInitializer;
import ratpack.exec.ExecInterceptor;

public class DefaultExecControllerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("ratpack.exec.internal.DefaultExecController");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("setInitializers")
.and(takesArgument(0, named("com.google.common.collect.ImmutableList"))),
DefaultExecControllerInstrumentation.class.getName() + "$SetInitializersAdvice");

transformer.applyAdviceToMethod(
named("setInterceptors")
.and(takesArgument(0, named("com.google.common.collect.ImmutableList"))),
DefaultExecControllerInstrumentation.class.getName() + "$SetInterceptorsAdvice");

transformer.applyAdviceToMethod(
isConstructor(),
DefaultExecControllerInstrumentation.class.getName() + "$ConstructorAdvice");
}

@SuppressWarnings("unused")
public static class SetInitializersAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@Advice.AssignReturned.ToArguments(@ToArgument(0))
public static ImmutableList<? extends ExecInitializer> enter(
@Advice.Argument(0) ImmutableList<? extends ExecInitializer> initializers) {
return ImmutableList.<ExecInitializer>builder()
.addAll(initializers)
.add(OpenTelemetryExecInitializer.INSTANCE)
.build();
}
}

@SuppressWarnings("unused")
public static class SetInterceptorsAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@Advice.AssignReturned.ToArguments(@ToArgument(0))
public static ImmutableList<? extends ExecInterceptor> enter(
@Advice.Argument(0) ImmutableList<? extends ExecInterceptor> interceptors) {
return ImmutableList.<ExecInterceptor>builder()
.addAll(interceptors)
.add(OpenTelemetryExecInterceptor.INSTANCE)
.build();
}
}

@SuppressWarnings("unused")
public static class ConstructorAdvice {

@SuppressWarnings("UnusedVariable")
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
@Advice.AssignReturned.ToFields({
@ToField(value = "initializers", index = 0),
@ToField(value = "interceptors", index = 1)
})
public static Object[] exit(
@Advice.FieldValue("initializers") ImmutableList<? extends ExecInitializer> initializers,
@Advice.FieldValue("interceptors") ImmutableList<? extends ExecInterceptor> interceptors) {
return new Object[] {
ImmutableList.of(OpenTelemetryExecInitializer.INSTANCE),
ImmutableList.of(OpenTelemetryExecInterceptor.INSTANCE)
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import ratpack.exec.Downstream;

public class DownstreamWrapper<T> implements Downstream<T> {

private final Downstream<T> delegate;
private final Context parentContext;

private DownstreamWrapper(Downstream<T> delegate, Context parentContext) {
assert parentContext != null;
this.delegate = delegate;
this.parentContext = parentContext;
}

@Override
public void success(T value) {
try (Scope ignored = parentContext.makeCurrent()) {
delegate.success(value);
}
}

@Override
public void error(Throwable throwable) {
try (Scope ignored = parentContext.makeCurrent()) {
delegate.error(throwable);
}
}

@Override
public void complete() {
try (Scope ignored = parentContext.makeCurrent()) {
delegate.complete();
}
}

public static <T> Downstream<T> wrapIfNeeded(Downstream<T> delegate) {
if (delegate instanceof DownstreamWrapper) {
return delegate;
}
Context context = Context.current();
if (context == Context.root()) {
// Skip wrapping, there is no need to propagate root context.
return delegate;
}
return new DownstreamWrapper<>(delegate, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import ratpack.http.client.HttpClient;

public class HttpClientInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("ratpack.http.client.HttpClient");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(isStatic())
.and(named("of"))
.and(takesArgument(0, named("ratpack.func.Action"))),
HttpClientInstrumentation.class.getName() + "$OfAdvice");
}

@SuppressWarnings("unused")
public static class OfAdvice {

@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
@Advice.AssignReturned.ToReturned
public static HttpClient injectTracing(@Advice.Return HttpClient httpClient) throws Exception {
return RatpackSingletons.telemetry().instrumentHttpClient(httpClient);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class RatpackInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public RatpackInstrumentationModule() {
super("ratpack", "ratpack-1.7");
}

@Override
public String getModuleGroup() {
// relies on netty
return "netty";
}

@Override
public boolean isIndyModule() {
return true;
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// Only activate when running ratpack 1.7 or later
return hasClassesNamed("ratpack.exec.util.retry.Delay");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new DefaultExecControllerInstrumentation(),
new ServerRegistryInstrumentation(),
new HttpClientInstrumentation(),
new RequestActionSupportInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;

import io.netty.channel.Channel;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys;
import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackTelemetry;
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.ContextHolder;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
import ratpack.exec.Execution;

public final class RatpackSingletons {

static {
TELEMETRY =
RatpackTelemetry.builder(GlobalOpenTelemetry.get())
.configure(AgentCommonConfig.get())
.build();
}

private static final Instrumenter<String, Void> INSTRUMENTER =
Instrumenter.<String, Void>builder(
GlobalOpenTelemetry.get(), "io.opentelemetry.ratpack-1.7", s -> s)
.setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled())
.buildInstrumenter();

public static Instrumenter<String, Void> instrumenter() {
return INSTRUMENTER;
}

private static final RatpackTelemetry TELEMETRY;

public static RatpackTelemetry telemetry() {
return TELEMETRY;
}

public static void propagateContextToChannel(Execution execution, Channel channel) {
Context parentContext =
execution
.maybeGet(ContextHolder.class)
.map(ContextHolder::context)
.orElse(Context.current());
channel.attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext);
}

private RatpackSingletons() {}
}
Loading
Loading