Skip to content

Commit

Permalink
add test for proxy client
Browse files Browse the repository at this point in the history
  • Loading branch information
chenlujjj committed Jan 15, 2025
1 parent 974c315 commit 4a51b46
Show file tree
Hide file tree
Showing 21 changed files with 351 additions and 118 deletions.
8 changes: 8 additions & 0 deletions instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ dependencies {
library("com.github.briandilley.jsonrpc4j:jsonrpc4j:1.3.3")

testImplementation(project(":instrumentation:jsonrpc4j-1.3:testing"))

testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")

testImplementation("org.eclipse.jetty:jetty-server:9.4.49.v20220914")

testImplementation("org.eclipse.jetty:jetty-servlet:9.4.49.v20220914")

testImplementation("javax.portlet:portlet-api:2.0")
}

tasks {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcResponse;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcClientRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcClientResponse;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.util.Map;
Expand Down Expand Up @@ -63,7 +63,7 @@ public static void onEnter(
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = Context.current();
SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(methodName, argument);
JsonRpcClientRequest request = new JsonRpcClientRequest(methodName, argument);
if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
return;
}
Expand All @@ -88,8 +88,8 @@ public static void onExit(
scope.close();
CLIENT_INSTRUMENTER.end(
context,
new SimpleJsonRpcRequest(methodName, argument),
new SimpleJsonRpcResponse(result),
new JsonRpcClientRequest(methodName, argument),
new JsonRpcClientResponse(result),
throwable);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@
package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3.JsonRpcSingletons.CLIENT_INSTRUMENTER;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPrivate;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;

import com.googlecode.jsonrpc4j.IJsonRpcClient;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcClientRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcClientResponse;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
Expand Down Expand Up @@ -49,9 +57,69 @@ public static <T> void onExit(
@Advice.Argument(3) Map<String, String> extraHeaders,
@Advice.Return(readOnly = false) Object proxy) {

proxy =
JsonRpcSingletons.instrumentCreateClientProxy(
classLoader, proxyInterface, client, extraHeaders, proxy);
proxy = instrumentCreateClientProxy(classLoader, proxyInterface, client, extraHeaders, proxy);
}

private static Object proxyObjectMethods(Method method, Object proxyObject, Object[] args) {
String name = method.getName();
switch (name) {
case "toString":
return proxyObject.getClass().getName() + "@" + System.identityHashCode(proxyObject);
case "hashCode":
return System.identityHashCode(proxyObject);
case "equals":
return proxyObject == args[0];
default:
throw new IllegalArgumentException(method.getName() + " is not a member of java.lang.Object");
}
}

@SuppressWarnings({"unchecked"})
public static <T> T instrumentCreateClientProxy(
ClassLoader classLoader,
Class<T> proxyInterface,
IJsonRpcClient client,
Map<String, String> extraHeaders,
Object proxy) {

return (T)
Proxy.newProxyInstance(
classLoader,
new Class<?>[] {proxyInterface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return proxyObjectMethods(method, proxy1, args);
}
// before invoke
Context parentContext = Context.current();
JsonRpcClientRequest request = new JsonRpcClientRequest(method, args);
if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
return method.invoke(proxy, args);
}

Context context = CLIENT_INSTRUMENTER.start(parentContext, request);
Scope scope = context.makeCurrent();
try {
Object result = method.invoke(proxy, args);
// after invoke
scope.close();
CLIENT_INSTRUMENTER.end(
context,
new JsonRpcClientRequest(method, args),
new JsonRpcClientResponse(result),
null);
return result;

} catch (Throwable t) {
// after invoke
scope.close();
CLIENT_INSTRUMENTER.end(context, request, null, t);
throw t;
}
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,18 @@

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import com.googlecode.jsonrpc4j.IJsonRpcClient;
import com.googlecode.jsonrpc4j.InvocationListener;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcClientRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcClientResponse;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcTelemetry;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcRequest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcResponse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

public final class JsonRpcSingletons {

public static final InvocationListener SERVER_INVOCATION_LISTENER;

public static final Instrumenter<SimpleJsonRpcRequest, SimpleJsonRpcResponse> CLIENT_INSTRUMENTER;
public static final Instrumenter<JsonRpcClientRequest, JsonRpcClientResponse> CLIENT_INSTRUMENTER;

static {
JsonRpcTelemetry telemetry = JsonRpcTelemetry.builder(GlobalOpenTelemetry.get()).build();
Expand All @@ -33,49 +26,4 @@ public final class JsonRpcSingletons {
}

private JsonRpcSingletons() {}

@SuppressWarnings({"unchecked"})
public static <T> T instrumentCreateClientProxy(
ClassLoader classLoader,
Class<T> proxyInterface,
IJsonRpcClient client,
Map<String, String> extraHeaders,
Object proxy) {

return (T)
Proxy.newProxyInstance(
classLoader,
new Class<?>[] {proxyInterface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
// before invoke
Context parentContext = Context.current();
SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(method, args);
if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
return method.invoke(proxy, args);
}

Context context = CLIENT_INSTRUMENTER.start(parentContext, request);
Scope scope = context.makeCurrent();
try {
Object result = method.invoke(proxy, args);
// after invoke
scope.close();
CLIENT_INSTRUMENTER.end(
context,
new SimpleJsonRpcRequest(method, args),
new SimpleJsonRpcResponse(result),
null);
return result;

} catch (Throwable t) {
// after invoke
scope.close();
CLIENT_INSTRUMENTER.end(context, request, null, t);
throw t;
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,31 @@

package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_JSONRPC_VERSION;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE;
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
import com.googlecode.jsonrpc4j.JsonRpcHttpClient;
import com.googlecode.jsonrpc4j.ProxyUtil;
import com.googlecode.jsonrpc4j.spring.rest.JsonRpcRestClient;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.AbstractJsonRpcTest;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService;
import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorServiceImpl;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

class AgentJsonRpcTest extends AbstractJsonRpcTest {
Expand All @@ -25,4 +46,99 @@ protected InstrumentationExtension testing() {
protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) {
return server;
}

@Test
void testClient() throws Throwable {
CalculatorService clientProxy =
ProxyUtil.createClientProxy(
this.getClass().getClassLoader(), CalculatorService.class, getHttpClient());
int res =
testing()
.runWithSpan(
"parent",
() -> {
return clientProxy.add(1, 2);
});

assertThat(res).isEqualTo(3);

testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
span ->
span.hasName(
"io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService/add")
.hasKind(SpanKind.CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(RPC_SYSTEM, "jsonrpc"),
equalTo(RPC_JSONRPC_VERSION, "2.0"),
equalTo(
RPC_SERVICE,
"io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService"),
equalTo(RPC_METHOD, "add"))),
trace -> trace.hasSpansSatisfyingExactly(span -> span.hasKind(SpanKind.SERVER)));

testing()
.waitAndAssertMetrics(
"io.opentelemetry.jsonrpc4j-1.3",
"rpc.client.duration",
metrics ->
metrics.anySatisfy(
metric ->
assertThat(metric)
.hasUnit("ms")
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point ->
point.hasAttributesSatisfying(
equalTo(RPC_METHOD, "add"),
equalTo(
RPC_SERVICE,
"io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService"),
equalTo(RPC_SYSTEM, "jsonrpc"))))));
}

private JettyServer jettyServer;

@BeforeAll
public void setup() throws Exception {
this.jettyServer = createServer();
}

private JettyServer createServer() throws Exception {
JettyServer jettyServer = new JettyServer(CalculatorServiceImpl.class);
jettyServer.startup();
return jettyServer;
}

protected JsonRpcRestClient getClient() throws MalformedURLException {
return getClient(JettyServer.SERVLET);
}

protected JsonRpcRestClient getClient(final String servlet) throws MalformedURLException {
return new JsonRpcRestClient(new URL(jettyServer.getCustomServerUrlString(servlet)));
}

protected JsonRpcHttpClient getHttpClient() throws MalformedURLException {
Map<String, String> header = new HashMap<>();
return new JsonRpcHttpClient(
new ObjectMapper(),
new URL(jettyServer.getCustomServerUrlString(JettyServer.SERVLET)),
header);
}

protected JsonRpcHttpClient getHttpClient(final String servlet) throws MalformedURLException {
Map<String, String> header = new HashMap<>();
return new JsonRpcHttpClient(
new ObjectMapper(), new URL(jettyServer.getCustomServerUrlString(servlet)), header);
}

@AfterAll
public void teardown() throws Exception {
jettyServer.stop();
}
}
Loading

0 comments on commit 4a51b46

Please sign in to comment.