Skip to content

Commit

Permalink
feat: instruments finagle's netty-based stack (open-telemetry#10141)
Browse files Browse the repository at this point in the history
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
  • Loading branch information
2 people authored and steverao committed Feb 16, 2024
1 parent 611a788 commit 518c303
Show file tree
Hide file tree
Showing 31 changed files with 1,341 additions and 91 deletions.
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ These are the supported libraries and frameworks:
| [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16+ | N/A | [Elasticsearch Client Spans] |
| [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html) | 5.0+ | N/A | [Database Client Spans] |
| [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans] |
| [Finagle](https://github.com/twitter/finagle) | 23.11+ | N/A | Provides `http.route` [2] |
| [Finatra](https://github.com/twitter/finatra) | 2.9+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [Geode Client](https://geode.apache.org/) | 1.4+ | N/A | [Database Client Spans] |
| [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
Expand Down
48 changes: 48 additions & 0 deletions instrumentation/finagle-http-23.11/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
plugins {
id("otel.javaagent-instrumentation")
id("otel.scala-conventions")
}

muzzle {
pass {
group.set("com.twitter")
module.set("finagle-http_2.12")
versions.set("[23.11.0,]")
}

pass {
group.set("com.twitter")
module.set("finagle-http_2.13")
versions.set("[23.11.0,]")
}
}

val finagleVersion = "23.11.0"
val scalaVersion = "2.13.10"

val scalaMinor = Regex("""^([0-9]+\.[0-9]+)\.?.*$""").find(scalaVersion)!!.run {
val (minorVersion) = this.destructured
minorVersion
}

val scalified = fun(pack: String): String {
return "${pack}_$scalaMinor"
}

dependencies {
library("${scalified("com.twitter:finagle-http")}:$finagleVersion")

// should wire netty contexts
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))

implementation(project(":instrumentation:netty:netty-4.1:javaagent"))
implementation(project(":instrumentation:netty:netty-4.1:library"))
implementation(project(":instrumentation:netty:netty-4-common:library"))
}

tasks {
test {
jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true")
jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package com.twitter.finagle;

import com.twitter.finagle.netty4.transport.ChannelTransport;

/** Exposes the finagle-internal {@link ChannelTransport#HandlerName()}. */
public final class ChannelTransportHelpers {
private ChannelTransportHelpers() {}

public static String getHandlerName() {
return ChannelTransport.HandlerName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.netty.channel;

/** Exists to correctly expose and propagate the {@link #initChannel(Channel)} calls. */
public abstract class OpenTelemetryChannelInitializerDelegate<T extends Channel>
extends ChannelInitializer<T> {

private final ChannelInitializer<T> initializer;

public OpenTelemetryChannelInitializerDelegate(ChannelInitializer<T> initializer) {
this.initializer = initializer;
}

@Override
protected void initChannel(T t) throws Exception {
initializer.initChannel(t);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.v23_11;

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

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
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 scala.Option;

public class ChannelTransportInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.twitter.finagle.netty4.transport.ChannelTransport");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(named("write")),
ChannelTransportInstrumentation.class.getName() + "$WriteAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(@Advice.Local("otelScope") Scope scope) {
Option<Context> ref = Helpers.CONTEXT_LOCAL.apply();
if (ref.isDefined()) {
scope = ref.get().makeCurrent();
}
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void methodExit(
@Advice.Local("otelScope") Scope scope, @Advice.Thrown Throwable thrown) {
if (scope != null) {
scope.close();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.v23_11;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Arrays;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class FinagleCoreInstrumentationModule extends InstrumentationModule {

public FinagleCoreInstrumentationModule() {
super("finagle-http");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Arrays.asList(
new GenStreamingServerDispatcherInstrumentation(),
new ChannelTransportInstrumentation(),
new H2StreamChannelInitInstrumentation());
}

@Override
public boolean isHelperClass(String className) {
return className.equals("com.twitter.finagle.ChannelTransportHelpers")
|| className.equals("io.netty.channel.OpenTelemetryChannelInitializerDelegate");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.v23_11;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.context.Context;
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;

public class GenStreamingServerDispatcherInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return hasSuperType(named("com.twitter.finagle.http.GenStreamingSerialServerDispatcher"));
}

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("com.twitter.finagle.http.GenStreamingSerialServerDispatcher");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(named("loop")),
GenStreamingServerDispatcherInstrumentation.class.getName() + "$LoopAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter() {
// this works bc at this point in the server evaluation, the netty
// instrumentation has already gone to work and assigned the context to the
// local thread;
//
// this works specifically in finagle's netty stack bc at this point the loop()
// method is running on a netty thread with the necessary access to the
// java-native ThreadLocal where the Context is stored
Helpers.CONTEXT_LOCAL.update(Context.current());
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void methodExit(@Advice.Thrown Throwable thrown) {
// always clear this
Helpers.CONTEXT_LOCAL.clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.v23_11;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
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;

public class H2StreamChannelInitInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
// scala object instance -- append $ to name
return named("com.twitter.finagle.http2.transport.common.H2StreamChannelInit$");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("initServer"))
.and(returns(named("io.netty.channel.ChannelInitializer"))),
H2StreamChannelInitInstrumentation.class.getName() + "$InitServerAdvice");
transformer.applyAdviceToMethod(
isMethod()
.and(named("initClient"))
.and(returns(named("io.netty.channel.ChannelInitializer"))),
H2StreamChannelInitInstrumentation.class.getName() + "$InitClientAdvice");
}

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

@Advice.OnMethodExit
public static void handleExit(
@Advice.Return(readOnly = false) ChannelInitializer<Channel> initializer) {
initializer = Helpers.wrapServer(initializer);
}
}

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

@Advice.OnMethodExit
public static void handleExit(
@Advice.Return(readOnly = false) ChannelInitializer<Channel> initializer) {
initializer = Helpers.wrapClient(initializer);
}
}
}
Loading

0 comments on commit 518c303

Please sign in to comment.