-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #100 from signalfx/netty-3.8-server-timing
Netty 3.8 server timing
- Loading branch information
Showing
6 changed files
with
433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
apply from: "$rootDir/gradle/instrumentation.gradle" | ||
|
||
dependencies { | ||
compileOnly group: 'io.netty', name: 'netty', version: '3.8.0.Final' | ||
compileOnly group: 'io.opentelemetry.javaagent.instrumentation', name: 'opentelemetry-javaagent-netty-3.8', version: versions.opentelemetryJavaagent | ||
|
||
implementation project(':instrumentation:common') | ||
|
||
testInstrumentation group: 'io.opentelemetry.javaagent.instrumentation', name: 'opentelemetry-javaagent-netty-3.8', version: versions.opentelemetryJavaagent | ||
|
||
testImplementation group: 'io.netty', name: 'netty', version: '3.8.0.Final' | ||
} | ||
|
||
tasks.withType(Test) { | ||
jvmArgs '-Dsplunk.context.server-timing.enabled=true' | ||
} |
147 changes: 147 additions & 0 deletions
147
...rc/main/java/com/splunk/opentelemetry/netty/v3_8/NettyChannelPipelineInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
* Copyright Splunk Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.splunk.opentelemetry.netty.v3_8; | ||
|
||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface; | ||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed; | ||
import static net.bytebuddy.matcher.ElementMatchers.isMethod; | ||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; | ||
import static net.bytebuddy.matcher.ElementMatchers.named; | ||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; | ||
|
||
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap; | ||
import io.opentelemetry.javaagent.instrumentation.api.ContextStore; | ||
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; | ||
import io.opentelemetry.javaagent.instrumentation.netty.v3_8.ChannelTraceContext; | ||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import net.bytebuddy.asm.Advice; | ||
import net.bytebuddy.description.method.MethodDescription; | ||
import net.bytebuddy.description.type.TypeDescription; | ||
import net.bytebuddy.matcher.ElementMatcher; | ||
import org.jboss.netty.channel.Channel; | ||
import org.jboss.netty.channel.ChannelHandler; | ||
import org.jboss.netty.channel.ChannelPipeline; | ||
import org.jboss.netty.handler.codec.http.HttpResponseEncoder; | ||
import org.jboss.netty.handler.codec.http.HttpServerCodec; | ||
|
||
public class NettyChannelPipelineInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<ClassLoader> classLoaderOptimization() { | ||
return hasClassesNamed("org.jboss.netty.channel.ChannelPipeline"); | ||
} | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return implementsInterface(named("org.jboss.netty.channel.ChannelPipeline")); | ||
} | ||
|
||
@Override | ||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() { | ||
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>(); | ||
transformers.put( | ||
isMethod() | ||
.and(nameStartsWith("add")) | ||
.and(takesArgument(1, named("org.jboss.netty.channel.ChannelHandler"))), | ||
this.getClass().getName() + "$ChannelPipelineAdd2ArgsAdvice"); | ||
transformers.put( | ||
isMethod() | ||
.and(nameStartsWith("add")) | ||
.and(takesArgument(2, named("org.jboss.netty.channel.ChannelHandler"))), | ||
this.getClass().getName() + "$ChannelPipelineAdd3ArgsAdvice"); | ||
return transformers; | ||
} | ||
|
||
public static class ChannelPipelineAdd2ArgsAdvice { | ||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static int checkDepth( | ||
@Advice.This ChannelPipeline pipeline, @Advice.Argument(1) ChannelHandler handler) { | ||
return ChannelPipelineUtil.removeDuplicatesAndIncrementDepth(pipeline, handler); | ||
} | ||
|
||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) | ||
public static void addHandler( | ||
@Advice.Enter int depth, | ||
@Advice.This ChannelPipeline pipeline, | ||
@Advice.Argument(1) ChannelHandler handler) { | ||
if (depth > 0) { | ||
return; | ||
} | ||
|
||
ContextStore<Channel, ChannelTraceContext> contextStore = | ||
InstrumentationContext.get(Channel.class, ChannelTraceContext.class); | ||
|
||
ChannelPipelineUtil.addServerTimingHandler(pipeline, handler, contextStore); | ||
} | ||
} | ||
|
||
public static class ChannelPipelineAdd3ArgsAdvice { | ||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static int checkDepth( | ||
@Advice.This ChannelPipeline pipeline, @Advice.Argument(2) ChannelHandler handler) { | ||
return ChannelPipelineUtil.removeDuplicatesAndIncrementDepth(pipeline, handler); | ||
} | ||
|
||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) | ||
public static void addHandler( | ||
@Advice.Enter int depth, | ||
@Advice.This ChannelPipeline pipeline, | ||
@Advice.Argument(2) ChannelHandler handler) { | ||
if (depth > 0) { | ||
return; | ||
} | ||
|
||
ContextStore<Channel, ChannelTraceContext> contextStore = | ||
InstrumentationContext.get(Channel.class, ChannelTraceContext.class); | ||
|
||
ChannelPipelineUtil.addServerTimingHandler(pipeline, handler, contextStore); | ||
} | ||
} | ||
|
||
public static final class ChannelPipelineUtil { | ||
public static int removeDuplicatesAndIncrementDepth( | ||
ChannelPipeline pipeline, ChannelHandler handler) { | ||
// Pipelines are created once as a factory and then copied multiple times using the same add | ||
// methods as we are hooking. If our handler has already been added we need to remove it so we | ||
// don't end up with duplicates (this throws an exception) | ||
if (pipeline.get(handler.getClass().getName()) != null) { | ||
pipeline.remove(handler.getClass().getName()); | ||
} | ||
// CallDepth does not allow just getting the depth value, so to avoid interfering with the | ||
// upstream netty implementation we do the same count but with our class | ||
return CallDepthThreadLocalMap.incrementCallDepth(ServerTimingHandler.class); | ||
} | ||
|
||
public static void addServerTimingHandler( | ||
ChannelPipeline pipeline, | ||
ChannelHandler handler, | ||
ContextStore<Channel, ChannelTraceContext> contextStore) { | ||
try { | ||
if (handler instanceof HttpServerCodec || handler instanceof HttpResponseEncoder) { | ||
pipeline.addLast( | ||
ServerTimingHandler.class.getName(), new ServerTimingHandler(contextStore)); | ||
} | ||
} finally { | ||
CallDepthThreadLocalMap.reset(ServerTimingHandler.class); | ||
} | ||
} | ||
|
||
private ChannelPipelineUtil() {} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
...tty-3.8/src/main/java/com/splunk/opentelemetry/netty/v3_8/NettyInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* Copyright Splunk Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.splunk.opentelemetry.netty.v3_8; | ||
|
||
import com.google.auto.service.AutoService; | ||
import com.splunk.opentelemetry.servertiming.ServerTimingHeader; | ||
import io.opentelemetry.javaagent.instrumentation.netty.v3_8.ChannelTraceContext; | ||
import io.opentelemetry.javaagent.tooling.InstrumentationModule; | ||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
@AutoService(InstrumentationModule.class) | ||
public class NettyInstrumentationModule extends InstrumentationModule { | ||
public NettyInstrumentationModule() { | ||
super("netty", "netty-3.8"); | ||
} | ||
|
||
@Override | ||
protected String[] additionalHelperClassNames() { | ||
return new String[] { | ||
ServerTimingHeader.class.getName(), | ||
getClass().getPackage().getName() + ".ServerTimingHandler", | ||
getClass().getPackage().getName() + ".ServerTimingHandler$HeadersSetter", | ||
getClass().getPackage().getName() | ||
+ ".NettyChannelPipelineInstrumentation$ChannelPipelineUtil", | ||
}; | ||
} | ||
|
||
// run after the upstream netty instrumentation | ||
@Override | ||
public int getOrder() { | ||
return 1; | ||
} | ||
|
||
// enable the instrumentation only if the server-timing header flag is on | ||
@Override | ||
protected boolean defaultEnabled() { | ||
return super.defaultEnabled() && ServerTimingHeader.shouldEmitServerTimingHeader(); | ||
} | ||
|
||
@Override | ||
public Map<String, String> contextStore() { | ||
return Collections.singletonMap( | ||
"org.jboss.netty.channel.Channel", ChannelTraceContext.class.getName()); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return Collections.singletonList(new NettyChannelPipelineInstrumentation()); | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...tion/netty-3.8/src/main/java/com/splunk/opentelemetry/netty/v3_8/ServerTimingHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright Splunk Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.splunk.opentelemetry.netty.v3_8; | ||
|
||
import static io.opentelemetry.javaagent.instrumentation.netty.v3_8.server.NettyHttpServerTracer.tracer; | ||
|
||
import com.splunk.opentelemetry.servertiming.ServerTimingHeader; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.propagation.TextMapPropagator.Setter; | ||
import io.opentelemetry.javaagent.instrumentation.api.ContextStore; | ||
import io.opentelemetry.javaagent.instrumentation.netty.v3_8.ChannelTraceContext; | ||
import org.jboss.netty.channel.Channel; | ||
import org.jboss.netty.channel.ChannelHandlerContext; | ||
import org.jboss.netty.channel.MessageEvent; | ||
import org.jboss.netty.channel.SimpleChannelDownstreamHandler; | ||
import org.jboss.netty.handler.codec.http.HttpHeaders; | ||
import org.jboss.netty.handler.codec.http.HttpResponse; | ||
|
||
public class ServerTimingHandler extends SimpleChannelDownstreamHandler { | ||
private final ContextStore<Channel, ChannelTraceContext> contextStore; | ||
|
||
public ServerTimingHandler(ContextStore<Channel, ChannelTraceContext> contextStore) { | ||
this.contextStore = contextStore; | ||
} | ||
|
||
@Override | ||
public void writeRequested(ChannelHandlerContext ctx, MessageEvent msg) { | ||
ChannelTraceContext channelTraceContext = | ||
contextStore.putIfAbsent(ctx.getChannel(), ChannelTraceContext.Factory.INSTANCE); | ||
|
||
Context context = tracer().getServerContext(channelTraceContext); | ||
if (context == null || !(msg.getMessage() instanceof HttpResponse)) { | ||
ctx.sendDownstream(msg); | ||
return; | ||
} | ||
|
||
HttpResponse response = (HttpResponse) msg.getMessage(); | ||
ServerTimingHeader.setHeaders(context, response.headers(), HeadersSetter.INSTANCE); | ||
ctx.sendDownstream(msg); | ||
} | ||
|
||
public static final class HeadersSetter implements Setter<HttpHeaders> { | ||
private static final HeadersSetter INSTANCE = new HeadersSetter(); | ||
|
||
@Override | ||
public void set(HttpHeaders carrier, String key, String value) { | ||
carrier.add(key, value); | ||
} | ||
} | ||
} |
Oops, something went wrong.