Skip to content

Commit 4c54caf

Browse files
Split sofarpc into sofarpc-5.0/sofarpc-5.14 modules, fix Triple+gRPC span hierarchy, add REST and Triple tests
1 parent 530087e commit 4c54caf

13 files changed

Lines changed: 614 additions & 52 deletions

File tree

dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ dependencies {
1515
compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0"
1616

1717
testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.6.0"
18+
// JAX-RS annotations required for the REST protocol test interface (@Path, @GET, etc.)
19+
testImplementation group: "javax.ws.rs", name: "javax.ws.rs-api", version: "2.1.1"
1820

1921
latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+"
2022
}

dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/ProviderProxyInvokerInstrumentation.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,21 @@ public static AgentScope enter(@Advice.Argument(0) SofaRequest request) {
4646
if (protocol == null) {
4747
return null;
4848
}
49-
AgentSpan span;
50-
if ("bolt".equals(protocol) || "h2c".equals(protocol)) {
51-
// Bolt propagates Datadog trace headers via SofaRequest.requestProps — extract from there.
52-
AgentSpanContext parentContext = extractContextAndGetSpanContext(request, GETTER);
53-
span = startSpan(SOFA_RPC_SERVER, parentContext);
54-
} else {
55-
// For Triple and other protocols, trace context is propagated at the transport layer
56-
// (e.g. gRPC Metadata). The transport instrumentation already activated the parent span,
57-
// so startSpan without an explicit parent inherits it automatically.
58-
span = startSpan(SOFA_RPC_SERVER);
49+
// Prefer the parent context stored by transport-specific instrumentation (e.g.
50+
// TripleServerInstrumentation reads it from gRPC Metadata). For Bolt and H2C the
51+
// transport instrumentations only set the protocol, so parentContext is null here and
52+
// we fall back to extracting from SofaRequest.requestProps as before.
53+
AgentSpanContext parentContext = SofaRpcProtocolContext.getParentContext();
54+
if (parentContext == null) {
55+
parentContext = extractContextAndGetSpanContext(request, GETTER);
5956
}
57+
// parentContext may be null for Triple+gRPC-enabled: TripleServerInstrumentation skips
58+
// Metadata extraction when a grpc.server span is already active. In that case
59+
// startSpan() without an explicit parent naturally attaches to the active grpc.server span.
60+
AgentSpan span =
61+
parentContext != null
62+
? startSpan(SOFA_RPC_SERVER, parentContext)
63+
: startSpan(SOFA_RPC_SERVER);
6064
DECORATE.afterStart(span);
6165
DECORATE.onRequest(span, request);
6266
span.setTag("sofarpc.protocol", protocol);

dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/SofaRpcModule.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public List<Instrumenter> typeInstrumentations() {
3131
new AbstractClusterInstrumentation(),
3232
new BoltServerProcessorInstrumentation(),
3333
new H2cServerTaskInstrumentation(),
34-
new TripleServerInstrumentation(),
3534
new ProviderProxyInvokerInstrumentation());
3635
}
3736
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
11
package datadog.trace.instrumentation.sofarpc;
22

3-
/** Thread-local carrier for the SOFA RPC transport protocol name. */
3+
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
4+
5+
/**
6+
* Thread-local carrier for the SOFA RPC transport protocol name and the propagated parent span
7+
* context. The parent context is extracted by transport-specific instrumentation (e.g.
8+
* TripleServerInstrumentation reads it from gRPC Metadata) so that ProviderProxyInvokerInstrumentation
9+
* can start the server span with an explicit parent regardless of whether other instrumentation
10+
* (e.g. gRPC) is enabled.
11+
*/
412
public final class SofaRpcProtocolContext {
513

614
private static final ThreadLocal<String> PROTOCOL = new ThreadLocal<>();
15+
private static final ThreadLocal<AgentSpanContext> PARENT_CONTEXT = new ThreadLocal<>();
716

817
private SofaRpcProtocolContext() {}
918

1019
public static void set(String protocol) {
1120
PROTOCOL.set(protocol);
1221
}
1322

23+
public static void setParentContext(AgentSpanContext parentContext) {
24+
PARENT_CONTEXT.set(parentContext);
25+
}
26+
1427
public static String get() {
1528
return PROTOCOL.get();
1629
}
1730

31+
public static AgentSpanContext getParentContext() {
32+
return PARENT_CONTEXT.get();
33+
}
34+
1835
public static void clear() {
1936
PROTOCOL.remove();
37+
PARENT_CONTEXT.remove();
2038
}
2139
}

dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/main/java/datadog/trace/instrumentation/sofarpc/TripleServerInstrumentation.java

Lines changed: 0 additions & 40 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package datadog.trace.instrumentation.sofarpc
2+
3+
import com.alipay.sofa.rpc.bootstrap.ProviderBootstrap
4+
import com.alipay.sofa.rpc.config.ApplicationConfig
5+
import com.alipay.sofa.rpc.config.ConsumerConfig
6+
import com.alipay.sofa.rpc.config.ProviderConfig
7+
import com.alipay.sofa.rpc.config.ServerConfig
8+
import datadog.trace.agent.test.InstrumentationSpecification
9+
import datadog.trace.bootstrap.instrumentation.api.Tags
10+
import spock.lang.Shared
11+
12+
import javax.ws.rs.GET
13+
import javax.ws.rs.Path
14+
import javax.ws.rs.PathParam
15+
import javax.ws.rs.Produces
16+
import javax.ws.rs.core.MediaType
17+
18+
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
19+
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
20+
21+
/**
22+
* Tests SOFA RPC REST protocol instrumentation.
23+
*
24+
* REST works out-of-the-box via dd-trace-java's Apache HttpClient and HTTP server
25+
* instrumentation. Our SOFA RPC instrumentation contributes the sofarpc.request[client]
26+
* span (from AbstractClusterInstrumentation). There is no sofarpc.request[server] span
27+
* because no transport-level instrumentation sets SofaRpcProtocolContext for REST — the
28+
* server side is covered by JAX-RS / HTTP server instrumentation instead.
29+
*/
30+
class SofaRpcRestTest extends InstrumentationSpecification {
31+
32+
@Shared
33+
int port = 12205
34+
35+
@Shared
36+
ProviderBootstrap restProviderBootstrap
37+
38+
@Shared
39+
GreeterService greeterService
40+
41+
def setupSpec() {
42+
ServerConfig serverConfig =
43+
new ServerConfig()
44+
.setProtocol("rest")
45+
.setHost("127.0.0.1")
46+
.setPort(port)
47+
48+
ProviderConfig<GreeterService> providerConfig =
49+
new ProviderConfig<GreeterService>()
50+
.setApplication(new ApplicationConfig().setAppName("test-server"))
51+
.setInterfaceId(GreeterService.name)
52+
.setRef(new GreeterServiceImpl())
53+
.setServer(serverConfig)
54+
.setRegister(false)
55+
56+
restProviderBootstrap = providerConfig.export()
57+
58+
greeterService =
59+
new ConsumerConfig<GreeterService>()
60+
.setApplication(new ApplicationConfig().setAppName("test-client"))
61+
.setInterfaceId(GreeterService.name)
62+
.setDirectUrl("rest://127.0.0.1:${port}")
63+
.setProtocol("rest")
64+
.setRegister(false)
65+
.setSubscribe(false)
66+
.refer()
67+
}
68+
69+
def cleanupSpec() {
70+
restProviderBootstrap?.unExport()
71+
}
72+
73+
def "client span created for REST call"() {
74+
setup:
75+
String serviceUniqueName = GreeterService.name + ":1.0"
76+
77+
when:
78+
String reply = runUnderTrace("caller") { greeterService.sayHello("World") }
79+
80+
then:
81+
reply == "Hello, World"
82+
83+
and:
84+
// REST is instrumented by Apache HttpClient (client) + HTTP server (server) integrations.
85+
// Our SOFA RPC instrumentation adds sofarpc.request[client] on top. No server-side
86+
// SofaRpcProtocolContext is set for REST, so ProviderProxyInvoker produces no sofarpc span.
87+
// Only the client-side trace is collected here.
88+
assertTraces(1) {
89+
trace(2) {
90+
basicSpan(it, "caller")
91+
span {
92+
operationName "sofarpc.request"
93+
resourceName "${serviceUniqueName}/sayHello"
94+
spanType "rpc"
95+
errored false
96+
childOf span(0)
97+
tags {
98+
"$Tags.RPC_SERVICE" serviceUniqueName
99+
"rpc.system" "sofarpc"
100+
"sofarpc.protocol" "rest"
101+
"component" "sofarpc-client"
102+
"span.kind" "client"
103+
peerServiceFrom(Tags.RPC_SERVICE)
104+
defaultTags()
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
@Path("/greeter")
112+
interface GreeterService {
113+
@GET
114+
@Path("/hello/{name}")
115+
@Produces(MediaType.TEXT_PLAIN)
116+
String sayHello(@PathParam("name") String name)
117+
}
118+
119+
static class GreeterServiceImpl implements GreeterService {
120+
@Override
121+
String sayHello(String name) {
122+
return "Hello, ${name}"
123+
}
124+
}
125+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
muzzle {
2+
pass {
3+
group = "com.alipay.sofa"
4+
module = "sofa-rpc-all"
5+
versions = "[5.14.2,)"
6+
assertInverse = true
7+
}
8+
}
9+
10+
apply from: "$rootDir/gradle/java.gradle"
11+
12+
configurations.testRuntimeClasspath {
13+
// Force the JRE variant of Guava so that java.time-based APIs (e.g.
14+
// CacheBuilder.expireAfterWrite(Duration)) are available at test runtime.
15+
// Without this, dependency resolution picks the android variant which lacks
16+
// those overloads, causing NoSuchMethodError during SOFA RPC initialisation.
17+
resolutionStrategy.force "com.google.guava:guava:32.1.3-jre"
18+
}
19+
20+
addTestSuiteForDir('latestDepTest', 'test')
21+
22+
dependencies {
23+
compileOnly project(':dd-java-agent:instrumentation:sofarpc:sofarpc-5.0')
24+
compileOnly group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2"
25+
// grpc-api provides io.grpc.Metadata used by TripleGrpcMetadataExtractAdapter.
26+
compileOnly group: "io.grpc", name: "grpc-api", version: "1.53.0"
27+
28+
testImplementation project(':dd-java-agent:instrumentation:sofarpc:sofarpc-5.0')
29+
// Required so that GrpcServerModule / GrpcClientModule are discovered via ServiceLoader
30+
// in SofaRpcTripleWithGrpcForkedTest (the test framework loads InstrumenterModules from
31+
// the test classpath, not from a shadow jar).
32+
testImplementation project(':dd-java-agent:instrumentation:grpc-1.5')
33+
testImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "5.14.2"
34+
// sofa-rpc-all:5.14.2 was compiled against gRPC 1.53.0 and does not bundle its
35+
// transitive dependencies — they must be declared explicitly at the matching version.
36+
testImplementation group: "io.grpc", name: "grpc-netty", version: "1.53.0"
37+
testImplementation group: "io.grpc", name: "grpc-core", version: "1.53.0"
38+
testImplementation group: "io.grpc", name: "grpc-stub", version: "1.53.0"
39+
testImplementation group: "com.google.protobuf", name: "protobuf-java", version: "3.25.3"
40+
testImplementation group: "com.alibaba", name: "fastjson", version: "1.2.83"
41+
// sofa-common-tools (transitive via sofa-rpc-all:5.14.2) uses Guava 28+ API
42+
// (CacheBuilder.expireAfterWrite(Duration)); force the JRE variant — the android
43+
// variant omits java.time-based overloads that are required at runtime.
44+
testImplementation group: "com.google.guava", name: "guava", version: "32.1.3-jre"
45+
46+
latestDepTestImplementation group: "com.alipay.sofa", name: "sofa-rpc-all", version: "+"
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package datadog.trace.instrumentation.sofarpc;
2+
3+
import com.google.auto.service.AutoService;
4+
import datadog.trace.agent.tooling.Instrumenter;
5+
import datadog.trace.agent.tooling.InstrumenterModule;
6+
import java.util.Collections;
7+
import java.util.List;
8+
9+
@AutoService(InstrumenterModule.class)
10+
public class SofaRpcTripleModule extends InstrumenterModule.Tracing {
11+
12+
public SofaRpcTripleModule() {
13+
super("sofarpc");
14+
}
15+
16+
@Override
17+
public String[] helperClassNames() {
18+
return new String[] {
19+
packageName + ".TripleGrpcMetadataExtractAdapter",
20+
};
21+
}
22+
23+
@Override
24+
public List<Instrumenter> typeInstrumentations() {
25+
return Collections.singletonList(new TripleServerInstrumentation());
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package datadog.trace.instrumentation.sofarpc;
2+
3+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
4+
import io.grpc.Metadata;
5+
6+
/**
7+
* Extracts Datadog propagation headers from gRPC {@link Metadata}.
8+
*
9+
* <p>Used by {@link TripleServerInstrumentation} to read the trace context that was injected by
10+
* {@link AbstractClusterInstrumentation} into {@code SofaRequest.requestProps} on the client side
11+
* and then serialised into gRPC Metadata by SOFA RPC's {@code TripleTracerAdapter.beforeSend()}.
12+
*
13+
* <p>Identical in structure to {@code GrpcExtractAdapter} in the grpc-1.5 instrumentation.
14+
*/
15+
public final class TripleGrpcMetadataExtractAdapter
16+
implements AgentPropagation.ContextVisitor<Metadata> {
17+
18+
public static final TripleGrpcMetadataExtractAdapter GETTER =
19+
new TripleGrpcMetadataExtractAdapter();
20+
21+
@Override
22+
public void forEachKey(Metadata carrier, AgentPropagation.KeyClassifier classifier) {
23+
for (String key : carrier.keys()) {
24+
if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
25+
if (!classifier.accept(
26+
key, carrier.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)))) {
27+
return;
28+
}
29+
}
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)