Skip to content

Commit

Permalink
feat: support instrumentation for jsonrpc4j
Browse files Browse the repository at this point in the history
  • Loading branch information
chenlujjj committed Jan 7, 2025
1 parent 2e959d3 commit 0190c13
Show file tree
Hide file tree
Showing 29 changed files with 969 additions and 0 deletions.
29 changes: 29 additions & 0 deletions instrumentation/jsonrpc4j-1.6/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.github.briandilley.jsonrpc4j")
module.set("jsonrpc4j")
versions.set("[1.6,)")
assertInverse.set(true)
}
}

val jsonrpcVersion = "1.6"

dependencies {
implementation(project(":instrumentation:jsonrpc4j-1.6:library"))
implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion")
testImplementation(project(":instrumentation:jsonrpc4j-1.6:testing"))
}


tasks {
test {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
jvmArgs("-Dotel.javaagent.experimental.thread-propagation-debugger.enabled=false")
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcResponse;
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 JsonRpcClientBuilderInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("com.googlecode.jsonrpc4j.IJsonRpcClient");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
// match JsonRpcHttpClient and JsonRpcRestClient
return implementsInterface(named("com.googlecode.jsonrpc4j.IJsonRpcClient"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("invoke"))
.and(takesArguments(4))
.and(takesArgument(0, String.class))
.and(takesArgument(1, Object.class))
.and(takesArgument(2, named("java.lang.reflect.Type")))
.and(takesArgument(3, named("java.util.Map")))
.and(returns(Object.class)),
this.getClass().getName() + "$InvokeAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) String methodName,
@Advice.Argument(1) Object argument,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(
methodName,
argument
);
if (!JsonRpcSingletons.CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
return;
}

context = JsonRpcSingletons.CLIENT_INSTRUMENTER.start(parentContext, request);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Argument(0) String methodName,
@Advice.Argument(1) Object argument,
@Advice.Return Object result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

scope.close();
JsonRpcSingletons.CLIENT_INSTRUMENTER.end(context, new SimpleJsonRpcRequest(methodName, argument), new SimpleJsonRpcResponse(result), throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;

import static java.util.Arrays.asList;

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

@AutoService(InstrumentationModule.class)
public class JsonRpcInstrumentationModule extends InstrumentationModule {
public JsonRpcInstrumentationModule() {
super("jsonrpc4j", "jsonrpc4j-1.6");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new JsonRpcServerBuilderInstrumentation(),
new JsonServiceExporterBuilderInstrumentation(),
new JsonRpcClientBuilderInstrumentation()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;

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

import com.googlecode.jsonrpc4j.InvocationListener;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.description.type.TypeDescription;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import net.bytebuddy.matcher.ElementMatcher;
import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
import net.bytebuddy.asm.Advice;

public class JsonRpcServerBuilderInstrumentation implements TypeInstrumentation {


@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("com.googlecode.jsonrpc4j.JsonRpcBasicServer");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.googlecode.jsonrpc4j.JsonRpcBasicServer");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isConstructor(),
this.getClass().getName() + "$ConstructorAdvice");
}

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

@Advice.OnMethodExit(suppress = Throwable.class)
public static void setInvocationListener(
@Advice.This JsonRpcBasicServer jsonRpcServer,
@Advice.FieldValue("invocationListener") InvocationListener invocationListener) {
VirtualField<JsonRpcBasicServer, Boolean> instrumented =
VirtualField.find(JsonRpcBasicServer.class, Boolean.class);
if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) {
jsonRpcServer.setInvocationListener(JsonRpcSingletons.SERVER_INVOCATION_LISTENER);
instrumented.set(jsonRpcServer, true);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;

import com.googlecode.jsonrpc4j.InvocationListener;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.JsonRpcTelemetry;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcResponse;


public final class JsonRpcSingletons {

public static final InvocationListener SERVER_INVOCATION_LISTENER;

public static final Instrumenter<SimpleJsonRpcRequest, SimpleJsonRpcResponse> CLIENT_INSTRUMENTER;


static {
JsonRpcTelemetry telemetry =
JsonRpcTelemetry.builder(GlobalOpenTelemetry.get())
.build();

SERVER_INVOCATION_LISTENER = telemetry.newServerInvocationListener();
CLIENT_INSTRUMENTER = telemetry.getClientInstrumenter();
}


private JsonRpcSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;

import com.googlecode.jsonrpc4j.JsonRpcServer;
import com.googlecode.jsonrpc4j.spring.JsonServiceExporter;
import io.opentelemetry.instrumentation.api.util.VirtualField;
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 static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;

public class JsonServiceExporterBuilderInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("com.googlecode.jsonrpc4j.spring.JsonServiceExporter");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.googlecode.jsonrpc4j.spring.JsonServiceExporter");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(named("exportService")),
this.getClass().getName() + "$ExportAdvice");
}

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

@Advice.OnMethodExit(suppress = Throwable.class)
public static void setInvocationListener(
@Advice.This JsonServiceExporter exporter,
@Advice.FieldValue("jsonRpcServer") JsonRpcServer jsonRpcServer) {
VirtualField<JsonRpcServer, Boolean> instrumented =
VirtualField.find(JsonRpcServer.class, Boolean.class);
if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) {
jsonRpcServer.setInvocationListener(JsonRpcSingletons.SERVER_INVOCATION_LISTENER);
instrumented.set(jsonRpcServer, true);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6;

import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.AbstractJsonRpcTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;

public class AgentJsonRpcTest extends AbstractJsonRpcTest {


@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();


@Override
protected InstrumentationExtension testing() {
return testing;
}

@Override
protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) {
return server;
}




}
14 changes: 14 additions & 0 deletions instrumentation/jsonrpc4j-1.6/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id("otel.library-instrumentation")
}

val jsonrpcVersion = "1.6"
val jacksonVersion = "2.13.3"

dependencies {
implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion")

implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")

testImplementation(project(":instrumentation:jsonrpc4j-1.6:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.opentelemetry.instrumentation.jsonrpc4j.v1_6;

import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import javax.annotation.Nullable;

// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
final class JsonRpcClientAttributesExtractor implements AttributesExtractor<SimpleJsonRpcRequest, SimpleJsonRpcResponse> {

// private final JsonRpcClientAttributesGetter getter;
//
//
// JsonRpcClientAttributesExtractor(JsonRpcClientAttributesGetter getter) {
// this.getter = getter;
// }

@Override
public void onStart(AttributesBuilder attributes, Context parentContext,
SimpleJsonRpcRequest jsonRpcRequest) {
attributes.put("rpc.jsonrpc.version", "2.0");
}

@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
SimpleJsonRpcRequest jsonRpcRequest,
@Nullable SimpleJsonRpcResponse jsonRpcResponse,
@Nullable Throwable error) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.opentelemetry.instrumentation.jsonrpc4j.v1_6;

import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter;

// Check https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes
// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
public enum JsonRpcClientAttributesGetter implements RpcAttributesGetter<SimpleJsonRpcRequest> {
INSTANCE;

@Override
public String getSystem(SimpleJsonRpcRequest request) {
return "jsonrpc";
}

@Override
public String getService(SimpleJsonRpcRequest request) {
// TODO
return "NOT_IMPLEMENTED";
}

@Override
public String getMethod(SimpleJsonRpcRequest request) {
return request.getMethodName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.opentelemetry.instrumentation.jsonrpc4j.v1_6;

import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;

public class JsonRpcClientSpanNameExtractor implements SpanNameExtractor<SimpleJsonRpcRequest> {
@Override
public String extract(SimpleJsonRpcRequest request) {
return request.getMethodName();
}
}
Loading

0 comments on commit 0190c13

Please sign in to comment.