Trace DNS resolution in reactor-netty (#4627)

* Trace DNS resolution in reactor-netty

* Code review comments

* removed a little too much

* implementation -> api

* revert and make muzzle happy
This commit is contained in:
Mateusz Rzeszutek 2021-11-16 08:34:27 +01:00 committed by GitHub
parent 39964b39aa
commit cd119f491d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 199 additions and 50 deletions

View File

@ -0,0 +1,9 @@
plugins {
id("otel.javaagent-instrumentation")
}
dependencies {
api(project(":instrumentation:netty:netty-4-common:javaagent"))
compileOnly("io.netty:netty-codec-http:4.1.0.Final")
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.netty.v4_1;
package io.opentelemetry.javaagent.instrumentation.netty.v4_1;
import io.netty.util.AttributeKey;
import io.opentelemetry.context.Context;

View File

@ -3,9 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.netty.v4_1.client;
import static io.opentelemetry.javaagent.instrumentation.netty.v4_1.client.NettyClientSingletons.connectionInstrumenter;
package io.opentelemetry.javaagent.instrumentation.netty.v4_1;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.AddressResolverGroup;
@ -27,7 +25,7 @@ public final class InstrumentedAddressResolverGroup<T extends SocketAddress>
if (delegate instanceof InstrumentedAddressResolverGroup) {
return delegate;
}
return new InstrumentedAddressResolverGroup<>(connectionInstrumenter(), delegate);
return new InstrumentedAddressResolverGroup<>(instrumenter, delegate);
}
private final NettyConnectionInstrumenter instrumenter;

View File

@ -25,8 +25,7 @@ muzzle {
dependencies {
library("io.netty:netty-codec-http:4.1.0.Final")
api(project(":instrumentation:netty:netty-4.1:library"))
implementation(project(":instrumentation:netty:netty-4-common:javaagent"))
api(project(":instrumentation:netty:netty-4.1-common:javaagent"))
testInstrumentation(project(":instrumentation:netty:netty-3.8:javaagent"))
testInstrumentation(project(":instrumentation:netty:netty-4.0:javaagent"))

View File

@ -13,7 +13,6 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.netty.common.HttpRequestAndChannel;

View File

@ -22,7 +22,6 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.netty.common.NettyConnectionRequest;
import io.opentelemetry.javaagent.instrumentation.netty.common.client.ConnectionCompleteListener;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.client.InstrumentedAddressResolverGroup;
import java.net.SocketAddress;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
@ -84,7 +83,6 @@ public class BootstrapInstrumentation implements TypeInstrumentation {
Context parentContext = Java8BytecodeBridge.currentContext();
request = NettyConnectionRequest.connect(remoteAddress);
if (!connectionInstrumenter().shouldStart(parentContext, request)) {
return;
}

View File

@ -12,7 +12,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import io.netty.channel.Channel;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;

View File

@ -15,8 +15,8 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.Attribute;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.common.HttpRequestAndChannel;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapter {

View File

@ -16,8 +16,8 @@ import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.Attribute;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.common.HttpRequestAndChannel;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {

View File

@ -14,8 +14,8 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.Attribute;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.common.HttpRequestAndChannel;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
public class HttpServerRequestTracingHandler extends ChannelInboundHandlerAdapter {

View File

@ -18,9 +18,9 @@ import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.Attribute;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.common.HttpRequestAndChannel;
import io.opentelemetry.javaagent.instrumentation.netty.common.NettyErrorHolder;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
import javax.annotation.Nullable;
public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdapter {

View File

@ -1,7 +0,0 @@
plugins {
id("otel.library-instrumentation")
}
dependencies {
compileOnly("io.netty:netty-codec-http:4.1.0.Final")
}

View File

@ -11,8 +11,8 @@ import static io.opentelemetry.javaagent.instrumentation.ratpack.RatpackSingleto
import io.netty.util.Attribute;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
import ratpack.handling.Context;
import ratpack.handling.Handler;

View File

@ -16,7 +16,7 @@ muzzle {
}
dependencies {
implementation(project(":instrumentation:netty:netty-4.1:library"))
implementation(project(":instrumentation:netty:netty-4.1-common:javaagent"))
library("io.projectreactor.netty:reactor-netty:0.9.0.RELEASE")
testInstrumentation(project(":instrumentation:reactor-netty:reactor-netty-1.0:javaagent"))

View File

@ -8,7 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.reactornetty.v0_9;
import io.netty.channel.Channel;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
import reactor.netty.Connection;

View File

@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.reactornetty.v0_9;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
import java.util.function.BiConsumer;
import reactor.netty.Connection;
import reactor.netty.http.client.HttpClientRequest;

View File

@ -17,8 +17,10 @@ muzzle {
}
dependencies {
implementation(project(":instrumentation:netty:netty-4.1:library"))
implementation(project(":instrumentation:netty:netty-4-common:javaagent"))
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
implementation(project(":instrumentation:netty:netty-4.1-common:javaagent"))
library("io.projectreactor.netty:reactor-netty-http:1.0.0")
testInstrumentation(project(":instrumentation:reactor-netty:reactor-netty-0.9:javaagent"))

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0;
import com.google.auto.value.AutoValue;
import io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.instrumentation.netty.common.NettyConnectionRequest;
@AutoValue
public abstract class ConnectionRequestAndContext {
public static ConnectionRequestAndContext create(
NettyConnectionRequest request, Context context) {
return new AutoValue_ConnectionRequestAndContext(request, context);
}
public abstract NettyConnectionRequest request();
public abstract Context context();
}

View File

@ -8,15 +8,37 @@ package io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0;
import static io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0.ReactorNettySingletons.connectionInstrumenter;
import io.netty.channel.Channel;
import io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.instrumentation.netty.common.NettyConnectionRequest;
import io.netty.channel.ChannelPromise;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import javax.annotation.Nullable;
import reactor.core.publisher.Mono;
public class ConnectionWrapper {
public final class ConnectionWrapper {
public static Mono<Channel> wrap(
Context context, NettyConnectionRequest request, Mono<Channel> mono) {
return mono.doOnError(error -> connectionInstrumenter().end(context, request, null, error))
.doOnSuccess(channel -> connectionInstrumenter().end(context, request, channel, null));
private static final VirtualField<ChannelPromise, ConnectionRequestAndContext>
requestAndContextField =
VirtualField.find(ChannelPromise.class, ConnectionRequestAndContext.class);
public static Mono<Channel> wrap(Mono<Channel> mono) {
// if we didn't attach the connection context&request then we have nothing to instrument
if (!(mono instanceof ChannelPromise)) {
return mono;
}
return mono.doOnError(error -> end(mono, null, error))
.doOnSuccess(channel -> end(mono, channel, null))
.doOnCancel(() -> end(mono, null, null));
}
private static void end(
Mono<Channel> mono, @Nullable Channel channel, @Nullable Throwable error) {
ConnectionRequestAndContext requestAndContext =
requestAndContextField.get((ChannelPromise) mono);
if (requestAndContext == null) {
return;
}
connectionInstrumenter()
.end(requestAndContext.context(), requestAndContext.request(), channel, error);
}
private ConnectionWrapper() {}
}

View File

@ -8,7 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0;
import io.netty.channel.Channel;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
import reactor.netty.Connection;

View File

@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.netty.v4_1.AttributeKeys;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.AttributeKeys;
import java.util.function.BiConsumer;
import reactor.netty.Connection;
import reactor.netty.http.client.HttpClientRequest;

View File

@ -7,22 +7,29 @@ package io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0;
import static io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0.ReactorNettySingletons.connectionInstrumenter;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPromise;
import io.netty.resolver.AddressResolverGroup;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.netty.common.NettyConnectionRequest;
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.InstrumentedAddressResolverGroup;
import java.net.SocketAddress;
import java.util.List;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import reactor.core.publisher.Mono;
public class TransportConnectorInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("reactor.netty.transport.TransportConnector");
@ -31,15 +38,48 @@ public class TransportConnectorInstrumentation implements TypeInstrumentation {
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("connect").and(takesArgument(1, named("java.net.SocketAddress"))),
named("doResolveAndConnect")
.and(takesArgument(3, named("io.netty.resolver.AddressResolverGroup")))
.and(returns(named("reactor.core.publisher.Mono"))),
TransportConnectorInstrumentation.class.getName() + "$ResolveAndConnectAdvice");
// handles [1.0.0, 1.0.6)
transformer.applyAdviceToMethod(
named("doConnect")
.and(takesArgument(0, SocketAddress.class))
.and(takesArgument(2, named("io.netty.channel.ChannelPromise"))),
TransportConnectorInstrumentation.class.getName() + "$ConnectAdvice");
// handles [1.0.6, )
transformer.applyAdviceToMethod(
named("doConnect")
.and(takesArgument(0, List.class))
.and(takesArgument(2, named("io.netty.channel.ChannelPromise")))
.and(takesArgument(3, int.class)),
TransportConnectorInstrumentation.class.getName() + "$ConnectNewAdvice");
}
@SuppressWarnings("unused")
public static class ResolveAndConnectAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(value = 3, readOnly = false) AddressResolverGroup<?> resolver) {
resolver = InstrumentedAddressResolverGroup.wrap(connectionInstrumenter(), resolver);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(@Advice.Return(readOnly = false) Mono<Channel> mono) {
// end the CONNECT span that was started in doConnect() instrumentation
mono = ConnectionWrapper.wrap(mono);
}
}
@SuppressWarnings("unused")
public static class ConnectAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startConnect(
@Advice.Argument(1) SocketAddress remoteAddress,
public static void onEnter(
@Advice.Argument(0) SocketAddress remoteAddress,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelRequest") NettyConnectionRequest request,
@Advice.Local("otelScope") Scope scope) {
@ -57,19 +97,66 @@ public class TransportConnectorInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void endConnect(
@Advice.Thrown Throwable throwable,
@Advice.Return(readOnly = false) Mono<Channel> mono,
@Advice.Argument(2) ChannelPromise channelPromise,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelRequest") NettyConnectionRequest request,
@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
if (scope == null) {
return;
}
scope.close();
if (throwable != null) {
connectionInstrumenter().end(context, request, null, throwable);
} else {
mono = ConnectionWrapper.wrap(context, request, mono);
// the span is finished in the mono decorated by the ConnectionWrapper
VirtualField.find(ChannelPromise.class, ConnectionRequestAndContext.class)
.set(channelPromise, ConnectionRequestAndContext.create(request, context));
}
}
}
@SuppressWarnings("unused")
public static class ConnectNewAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) List<SocketAddress> remoteAddresses,
@Advice.Argument(3) int index,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelRequest") NettyConnectionRequest request,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = Java8BytecodeBridge.currentContext();
request = NettyConnectionRequest.connect(remoteAddresses.get(index));
if (!connectionInstrumenter().shouldStart(parentContext, request)) {
return;
}
context = connectionInstrumenter().start(parentContext, request);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void endConnect(
@Advice.Thrown Throwable throwable,
@Advice.Argument(2) ChannelPromise channelPromise,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelRequest") NettyConnectionRequest request,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
scope.close();
if (throwable != null) {
connectionInstrumenter().end(context, request, null, throwable);
} else {
// the span is finished in the mono decorated by the ConnectionWrapper
VirtualField.find(ChannelPromise.class, ConnectionRequestAndContext.class)
.set(channelPromise, ConnectionRequestAndContext.create(request, context));
}
}
}

View File

@ -50,13 +50,23 @@ class ReactorNettyConnectionSpanTest extends InstrumentationSpecification implem
then:
responseCode == 200
assertTraces(1) {
trace(0, 4) {
trace(0, 5) {
span(0) {
name "parent"
kind INTERNAL
hasNoParent()
}
span(1) {
name "RESOLVE"
kind INTERNAL
childOf span(0)
attributes {
"${SemanticAttributes.NET_TRANSPORT.key}" IP_TCP
"${SemanticAttributes.NET_PEER_NAME.key}" "localhost"
"${SemanticAttributes.NET_PEER_PORT.key}" server.httpPort()
}
}
span(2) {
name "CONNECT"
kind INTERNAL
childOf(span(0))
@ -67,15 +77,15 @@ class ReactorNettyConnectionSpanTest extends InstrumentationSpecification implem
"${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1"
}
}
span(2) {
span(3) {
name "HTTP GET"
kind CLIENT
childOf(span(0))
}
span(3) {
span(4) {
name "test-http-server"
kind SERVER
childOf(span(2))
childOf(span(3))
}
}
}
@ -100,7 +110,7 @@ class ReactorNettyConnectionSpanTest extends InstrumentationSpecification implem
and:
assertTraces(1) {
trace(0, 2) {
trace(0, 3) {
span(0) {
name "parent"
kind INTERNAL
@ -109,6 +119,16 @@ class ReactorNettyConnectionSpanTest extends InstrumentationSpecification implem
errorEvent(thrownException.class, thrownException.message)
}
span(1) {
name "RESOLVE"
kind INTERNAL
childOf span(0)
attributes {
"${SemanticAttributes.NET_TRANSPORT.key}" IP_TCP
"${SemanticAttributes.NET_PEER_NAME.key}" "localhost"
"${SemanticAttributes.NET_PEER_PORT.key}" PortUtils.UNUSABLE_PORT
}
}
span(2) {
name "CONNECT"
kind INTERNAL
childOf(span(0))

View File

@ -255,8 +255,8 @@ include(":instrumentation:mongo:mongo-async-3.3:javaagent")
include(":instrumentation:mongo:mongo-common:testing")
include(":instrumentation:netty:netty-3.8:javaagent")
include(":instrumentation:netty:netty-4.0:javaagent")
include(":instrumentation:netty:netty-4.1:library")
include(":instrumentation:netty:netty-4.1:javaagent")
include(":instrumentation:netty:netty-4.1-common:javaagent")
include(":instrumentation:netty:netty-4-common:javaagent")
include(":instrumentation:netty:netty-common:javaagent")
include(":instrumentation:okhttp:okhttp-2.2:javaagent")