Skip to content

Commit 205100e

Browse files
dmarkwatlaurit
andauthored
feat: instruments finagle's netty-based stack (#10141)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 6ec0d02 commit 205100e

31 files changed

+1341
-91
lines changed

docs/supported-libraries.md

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ These are the supported libraries and frameworks:
5959
| [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16+ | N/A | [Elasticsearch Client Spans] |
6060
| [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html) | 5.0+ | N/A | [Database Client Spans] |
6161
| [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans] |
62+
| [Finagle](https://github.com/twitter/finagle) | 23.11+ | N/A | Provides `http.route` [2] |
6263
| [Finatra](https://github.com/twitter/finatra) | 2.9+ | N/A | Provides `http.route` [2], Controller Spans [3] |
6364
| [Geode Client](https://geode.apache.org/) | 1.4+ | N/A | [Database Client Spans] |
6465
| [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
id("otel.scala-conventions")
4+
}
5+
6+
muzzle {
7+
pass {
8+
group.set("com.twitter")
9+
module.set("finagle-http_2.12")
10+
versions.set("[23.11.0,]")
11+
}
12+
13+
pass {
14+
group.set("com.twitter")
15+
module.set("finagle-http_2.13")
16+
versions.set("[23.11.0,]")
17+
}
18+
}
19+
20+
val finagleVersion = "23.11.0"
21+
val scalaVersion = "2.13.10"
22+
23+
val scalaMinor = Regex("""^([0-9]+\.[0-9]+)\.?.*$""").find(scalaVersion)!!.run {
24+
val (minorVersion) = this.destructured
25+
minorVersion
26+
}
27+
28+
val scalified = fun(pack: String): String {
29+
return "${pack}_$scalaMinor"
30+
}
31+
32+
dependencies {
33+
library("${scalified("com.twitter:finagle-http")}:$finagleVersion")
34+
35+
// should wire netty contexts
36+
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
37+
38+
implementation(project(":instrumentation:netty:netty-4.1:javaagent"))
39+
implementation(project(":instrumentation:netty:netty-4.1:library"))
40+
implementation(project(":instrumentation:netty:netty-4-common:library"))
41+
}
42+
43+
tasks {
44+
test {
45+
jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true")
46+
jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true")
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.twitter.finagle;
7+
8+
import com.twitter.finagle.netty4.transport.ChannelTransport;
9+
10+
/** Exposes the finagle-internal {@link ChannelTransport#HandlerName()}. */
11+
public final class ChannelTransportHelpers {
12+
private ChannelTransportHelpers() {}
13+
14+
public static String getHandlerName() {
15+
return ChannelTransport.HandlerName();
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.netty.channel;
7+
8+
/** Exists to correctly expose and propagate the {@link #initChannel(Channel)} calls. */
9+
public abstract class OpenTelemetryChannelInitializerDelegate<T extends Channel>
10+
extends ChannelInitializer<T> {
11+
12+
private final ChannelInitializer<T> initializer;
13+
14+
public OpenTelemetryChannelInitializerDelegate(ChannelInitializer<T> initializer) {
15+
this.initializer = initializer;
16+
}
17+
18+
@Override
19+
protected void initChannel(T t) throws Exception {
20+
initializer.initChannel(t);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.v23_11;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.context.Scope;
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 scala.Option;
19+
20+
public class ChannelTransportInstrumentation implements TypeInstrumentation {
21+
@Override
22+
public ElementMatcher<TypeDescription> typeMatcher() {
23+
return named("com.twitter.finagle.netty4.transport.ChannelTransport");
24+
}
25+
26+
@Override
27+
public void transform(TypeTransformer transformer) {
28+
transformer.applyAdviceToMethod(
29+
isMethod().and(named("write")),
30+
ChannelTransportInstrumentation.class.getName() + "$WriteAdvice");
31+
}
32+
33+
@SuppressWarnings("unused")
34+
public static class WriteAdvice {
35+
36+
@Advice.OnMethodEnter(suppress = Throwable.class)
37+
public static void methodEnter(@Advice.Local("otelScope") Scope scope) {
38+
Option<Context> ref = Helpers.CONTEXT_LOCAL.apply();
39+
if (ref.isDefined()) {
40+
scope = ref.get().makeCurrent();
41+
}
42+
}
43+
44+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
45+
public static void methodExit(
46+
@Advice.Local("otelScope") Scope scope, @Advice.Thrown Throwable thrown) {
47+
if (scope != null) {
48+
scope.close();
49+
}
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.v23_11;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
10+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
@AutoService(InstrumentationModule.class)
15+
public class FinagleCoreInstrumentationModule extends InstrumentationModule {
16+
17+
public FinagleCoreInstrumentationModule() {
18+
super("finagle-http");
19+
}
20+
21+
@Override
22+
public List<TypeInstrumentation> typeInstrumentations() {
23+
return Arrays.asList(
24+
new GenStreamingServerDispatcherInstrumentation(),
25+
new ChannelTransportInstrumentation(),
26+
new H2StreamChannelInitInstrumentation());
27+
}
28+
29+
@Override
30+
public boolean isHelperClass(String className) {
31+
return className.equals("com.twitter.finagle.ChannelTransportHelpers")
32+
|| className.equals("io.netty.channel.OpenTelemetryChannelInitializerDelegate");
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.v23_11;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
10+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
20+
public class GenStreamingServerDispatcherInstrumentation implements TypeInstrumentation {
21+
@Override
22+
public ElementMatcher<TypeDescription> typeMatcher() {
23+
return hasSuperType(named("com.twitter.finagle.http.GenStreamingSerialServerDispatcher"));
24+
}
25+
26+
@Override
27+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
28+
return hasClassesNamed("com.twitter.finagle.http.GenStreamingSerialServerDispatcher");
29+
}
30+
31+
@Override
32+
public void transform(TypeTransformer transformer) {
33+
transformer.applyAdviceToMethod(
34+
isMethod().and(named("loop")),
35+
GenStreamingServerDispatcherInstrumentation.class.getName() + "$LoopAdvice");
36+
}
37+
38+
@SuppressWarnings("unused")
39+
public static class LoopAdvice {
40+
41+
@Advice.OnMethodEnter(suppress = Throwable.class)
42+
public static void methodEnter() {
43+
// this works bc at this point in the server evaluation, the netty
44+
// instrumentation has already gone to work and assigned the context to the
45+
// local thread;
46+
//
47+
// this works specifically in finagle's netty stack bc at this point the loop()
48+
// method is running on a netty thread with the necessary access to the
49+
// java-native ThreadLocal where the Context is stored
50+
Helpers.CONTEXT_LOCAL.update(Context.current());
51+
}
52+
53+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
54+
public static void methodExit(@Advice.Thrown Throwable thrown) {
55+
// always clear this
56+
Helpers.CONTEXT_LOCAL.clear();
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.v23_11;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
import static net.bytebuddy.matcher.ElementMatchers.returns;
11+
12+
import io.netty.channel.Channel;
13+
import io.netty.channel.ChannelInitializer;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
20+
public class H2StreamChannelInitInstrumentation implements TypeInstrumentation {
21+
@Override
22+
public ElementMatcher<TypeDescription> typeMatcher() {
23+
// scala object instance -- append $ to name
24+
return named("com.twitter.finagle.http2.transport.common.H2StreamChannelInit$");
25+
}
26+
27+
@Override
28+
public void transform(TypeTransformer transformer) {
29+
transformer.applyAdviceToMethod(
30+
isMethod()
31+
.and(named("initServer"))
32+
.and(returns(named("io.netty.channel.ChannelInitializer"))),
33+
H2StreamChannelInitInstrumentation.class.getName() + "$InitServerAdvice");
34+
transformer.applyAdviceToMethod(
35+
isMethod()
36+
.and(named("initClient"))
37+
.and(returns(named("io.netty.channel.ChannelInitializer"))),
38+
H2StreamChannelInitInstrumentation.class.getName() + "$InitClientAdvice");
39+
}
40+
41+
@SuppressWarnings("unused")
42+
public static class InitServerAdvice {
43+
44+
@Advice.OnMethodExit
45+
public static void handleExit(
46+
@Advice.Return(readOnly = false) ChannelInitializer<Channel> initializer) {
47+
initializer = Helpers.wrapServer(initializer);
48+
}
49+
}
50+
51+
@SuppressWarnings("unused")
52+
public static class InitClientAdvice {
53+
54+
@Advice.OnMethodExit
55+
public static void handleExit(
56+
@Advice.Return(readOnly = false) ChannelInitializer<Channel> initializer) {
57+
initializer = Helpers.wrapClient(initializer);
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)