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

feat: instruments finagle's netty-based stack #10141

Merged
merged 28 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8ddf17d
Adds finagle instrumentation
dmarkwat Apr 9, 2023
b4690a0
fix module name
dmarkwat Dec 29, 2023
af15304
spotless
dmarkwat Dec 29, 2023
9331e6a
fix muzzle
dmarkwat Dec 29, 2023
43475c3
cleanup gradle
dmarkwat Dec 29, 2023
809c623
fix exception handling regression
dmarkwat Dec 29, 2023
caed593
remove unnecessary exception mapping
dmarkwat Dec 29, 2023
bbd6076
relocate
dmarkwat Dec 29, 2023
76e3f6b
review
dmarkwat Jan 6, 2024
241f299
refine after review
dmarkwat Jan 18, 2024
a453bc9
force rerun
dmarkwat Jan 19, 2024
c7c6311
review updates
dmarkwat Jan 27, 2024
99cf272
fix imports
dmarkwat Jan 27, 2024
89e4eab
doc
dmarkwat Jan 28, 2024
4260485
missed in rebase
dmarkwat Jan 28, 2024
b00a43f
adds experimental events flag to netty server code paths
dmarkwat Feb 3, 2024
cf55a13
adds experimental events handling to netty client code paths
dmarkwat Feb 3, 2024
47fb660
re-enable h2 protocol test
dmarkwat Feb 4, 2024
1a4e981
review feedback
dmarkwat Feb 6, 2024
603162f
missing final
dmarkwat Feb 6, 2024
af97fe3
make test classes package private
laurit Feb 7, 2024
1025016
fix context leak in netty SingleThreadEventExecutor
laurit Feb 7, 2024
a338bec
removes client worker pool config
dmarkwat Feb 7, 2024
533cd75
reworks client tests to be in line with other impls
dmarkwat Feb 11, 2024
f4c3eb8
clean up client test
dmarkwat Feb 12, 2024
00979f9
move stuff out of library instrumenation public api
laurit Feb 13, 2024
e403b08
namespace netty code
dmarkwat Feb 14, 2024
c0f25ad
spotless
dmarkwat Feb 14, 2024
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
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
Loading