This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-java.git
The following commit(s) were added to refs/heads/main by this push:
new d45818911 gRPC plugin support trace client generic call (#168)
d45818911 is described below
commit d45818911d9ecd656fb5ab1e6e072e737d57e3ee
Author: hutaishi <[email protected]>
AuthorDate: Thu May 5 11:07:49 2022 +0800
gRPC plugin support trace client generic call (#168)
---
.github/workflows/plugins-test.3.yaml | 1 +
CHANGES.md | 1 +
.../skywalking/apm/plugin/grpc/v1/Constants.java | 4 +
.../ClientCallImplGenericCallInterceptor.java | 224 +++++++++++++++++++++
.../client/ClientCallsGenericCallInterceptor.java | 55 +++++
.../v1/client/NettyClientStreamInterceptor.java | 44 ++++
.../v1/define/ClientCallImplInstrumentation.java | 89 ++++++++
.../ClientCallsGenericCallInstrumentation.java | 100 +++++++++
.../define/NettyClientStreamInstrumentation.java | 71 +++++++
.../ShadedNettyClientStreamInstrumentation.java | 71 +++++++
.../src/main/resources/skywalking-plugin.def | 6 +-
.../config/expectedData.yaml | 105 ++++++++++
.../grpc-generic-call-scenario/configuration.yml | 9 +-
.../grpc-consumer/pom.xml | 79 ++++++++
.../call/consumer/GrpcConsumerApplication.java | 29 +++
.../call/consumer/client/DynamicJsonMessage.java | 134 ++++++++++++
.../consumer/client/GrpcGenericCallClient.java | 82 ++++++++
.../call/consumer/client/SimpleStreamObserver.java | 74 +++++++
.../call/consumer/controller/CaseController.java | 56 ++++++
.../src/main/resources/application.yml | 19 ++
.../grpc-dist/bin/startup.sh | 14 +-
.../grpc-generic-call-scenario/grpc-dist/pom.xml | 59 ++++++
.../grpc-dist/src/main/assembly/assembly.xml | 48 +++++
.../grpc-provider/pom.xml | 123 +++++++++++
.../call/provider/GrpcProviderApplication.java | 29 +++
.../provider/provider/ProviderConfiguration.java | 51 +++++
.../provider/provider/service/TestServiceImpl.java | 53 +++++
.../call/provider/server/constant/Constants.java | 28 +++
.../server/dynamic/DynamicJsonMessage.java | 130 ++++++++++++
.../dynamic/DynamicMessageServiceTranslator.java | 126 ++++++++++++
.../dynamic/DynamicToProtoServerCallListener.java | 81 ++++++++
.../server/dynamic/ProtoToDynamicServerCall.java | 52 +++++
.../grpc-provider/src/main/proto/TestService.proto | 39 ++++
.../src/main/resources/application.yml | 20 ++
.../scenarios/grpc-generic-call-scenario/pom.xml | 93 +++++++++
.../support-version.list | 5 +-
36 files changed, 2194 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/plugins-test.3.yaml
b/.github/workflows/plugins-test.3.yaml
index 655b1b5e2..3e9f4ffd0 100644
--- a/.github/workflows/plugins-test.3.yaml
+++ b/.github/workflows/plugins-test.3.yaml
@@ -100,6 +100,7 @@ jobs:
- shenyu-2.4.x-scenario
- jdk-threadpool-scenario
- shenyu-2.4.x-dubbo-scenario
+ - grpc-generic-call-scenario
steps:
- uses: actions/checkout@v2
with:
diff --git a/CHANGES.md b/CHANGES.md
index 8945f8364..3605abad9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,6 +13,7 @@ Release Notes.
* Add an optional plugin, trace-sampler-cpu-policy-plugin, which could disable
trace collecting in high CPU load.
* Change the dateformat of logs to `yyyy-MM-dd HH:mm:ss.SSS`(was `yyyy-MM-dd
HH:mm:ss:SSS`).
* Fix NPE in elasticsearch plugin.
+* Grpc plugin support trace client async generic call(without grpc stubs),
support Method type: `UNARY`、`SERVER_STREAMING`.
#### Documentation
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/Constants.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/Constants.java
index 532dffbe8..a5e8dfac8 100644
---
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/Constants.java
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/Constants.java
@@ -67,4 +67,8 @@ public class Constants {
public static final String RESPONSE_ON_CLOSE_OPERATION_NAME =
"/Response/onClose";
public static final String BLOCKING_CALL_EXIT_SPAN =
"SW_GRPC_BLOCKING_CALL_EXIT_SPAN";
+
+ public static final String GENERIC_CALL_METHOD = "GENERIC_CALL_METHOD";
+
+ public static final String CLIENT_STREAM_PEER = "CLIENT_STREAM_PEER";
}
\ No newline at end of file
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/ClientCallImplGenericCallInterceptor.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/ClientCallImplGenericCallInterceptor.java
new file mode 100755
index 000000000..9e551275b
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/ClientCallImplGenericCallInterceptor.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.client;
+
+import static org.apache.skywalking.apm.plugin.grpc.v1.Constants.CLIENT;
+import static
org.apache.skywalking.apm.plugin.grpc.v1.Constants.CLIENT_STREAM_PEER;
+import static
org.apache.skywalking.apm.plugin.grpc.v1.Constants.GENERIC_CALL_METHOD;
+import static
org.apache.skywalking.apm.plugin.grpc.v1.Constants.RESPONSE_ON_MESSAGE_OPERATION_NAME;
+import static
org.apache.skywalking.apm.plugin.grpc.v1.OperationNameFormatUtil.formatOperationName;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import org.apache.skywalking.apm.agent.core.context.CarrierItem;
+import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.ExitSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.apache.skywalking.apm.plugin.grpc.v1.OperationNameFormatUtil;
+import org.apache.skywalking.apm.util.StringUtil;
+
+import com.google.common.base.Strings;
+
+import io.grpc.Attributes;
+import io.grpc.ClientCall.Listener;
+import io.grpc.ForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ClientCallImplGenericCallInterceptor
+ implements InstanceMethodsAroundInterceptor,
InstanceConstructorInterceptor {
+
+ @Override
+ public void onConstruct(EnhancedInstance objInst, Object[] allArguments)
throws Throwable {
+ MethodDescriptor methodDescriptor = (MethodDescriptor) allArguments[0];
+ objInst.setSkyWalkingDynamicField(methodDescriptor);
+ }
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[]
allArguments, Class<?>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ String asyncCallMethod = (String)
ContextManager.getRuntimeContext().get(GENERIC_CALL_METHOD);
+ // only trace generic call
+ if (StringUtil.isBlank(asyncCallMethod)) {
+ return;
+ }
+ ContextManager.getRuntimeContext().remove(GENERIC_CALL_METHOD);
+
+ Listener<?> observer = (Listener<?>) allArguments[0];
+ Metadata headers = (Metadata) allArguments[1];
+ MethodDescriptor methodDescriptor = (MethodDescriptor)
objInst.getSkyWalkingDynamicField();
+ String serviceName = formatOperationName(methodDescriptor);
+ // channel.authority() In some scenes, it is not accurate. eg:Load
balancing, NameResolver
+ // The server IP and PORT can be obtained accurately BY clientStream.
+ // afterMethod method will set remotePeer.
+ String remotePeer = "No Peer";
+ String operationPrefix =
OperationNameFormatUtil.formatOperationName(methodDescriptor) + CLIENT;
+
+ ContextCarrier contextCarrier = new ContextCarrier();
+ AbstractSpan span = ContextManager.createExitSpan(serviceName,
contextCarrier, remotePeer);
+ span.setComponent(ComponentsDefine.GRPC);
+ span.setLayer(SpanLayer.RPC_FRAMEWORK);
+ span.tag(Tags.ofKey(GENERIC_CALL_METHOD), asyncCallMethod);
+
+ CarrierItem contextItem = contextCarrier.items();
+ while (contextItem.hasNext()) {
+ contextItem = contextItem.next();
+ Metadata.Key<String> headerKey =
+ Metadata.Key.of(contextItem.getHeadKey(),
Metadata.ASCII_STRING_MARSHALLER);
+ headers.put(headerKey, contextItem.getHeadValue());
+ }
+ ContextSnapshot snapshot = ContextManager.capture();
+ span.prepareForAsync();
+ ContextManager.stopSpan(span);
+ objInst.setSkyWalkingDynamicField(span);
+
+ allArguments[0] = new TracingClientCallListener<>(observer,
methodDescriptor, operationPrefix, snapshot, span);
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method,
Object[] allArguments, Class<?>[] argumentsTypes,
+ Object ret) throws Throwable {
+ if (objInst.getSkyWalkingDynamicField() == null
+ || !(objInst.getSkyWalkingDynamicField() instanceof
AbstractSpan)) {
+ return ret;
+ }
+ AbstractSpan span = (AbstractSpan) objInst.getSkyWalkingDynamicField();
+ // Scenario of specifying IP + port
+ String remotePeer = (String)
ContextManager.getRuntimeContext().get(CLIENT_STREAM_PEER);
+ ContextManager.getRuntimeContext().remove(CLIENT_STREAM_PEER);
+ span.setPeer(remotePeer);
+ Arrays.stream(objInst.getClass().getDeclaredMethods()).filter(m ->
m.getName().equals("getAttributes"))
+ .findFirst()
+ .ifPresent(m -> {
+ try {
+ m.setAccessible(true);
+ Attributes attributes = (Attributes) m.invoke(objInst);
+ attributes.keys().stream()
+ .filter(k ->
k.toString().equals("remote-addr")).findFirst()
+ .map(attributes::get)
+ .ifPresent(v -> {
+ String peer = v.toString();
+ if (StringUtil.isNotBlank(peer)) {
+ if (peer.startsWith("/")) {
+ peer = peer.substring(1);
+ }
+ // Accurate IP acquisition.Scenario:
Name Resolver , load balancing
+ span.setPeer(peer);
+ }
+ });
+ } catch (Exception e) {
+ // ignore
+ }
+ });
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method,
Object[] allArguments,
+ Class<?>[] argumentsTypes, Throwable t) {
+ AbstractSpan span = (AbstractSpan) objInst.getSkyWalkingDynamicField();
+ if (span != null) {
+ span.errorOccurred().log(t);
+ }
+ }
+
+ static class TracingClientCallListener<RESPONSE>
+ extends
ForwardingClientCallListener.SimpleForwardingClientCallListener<RESPONSE> {
+
+ private final ContextSnapshot contextSnapshot;
+
+ private final MethodDescriptor<?, ?> methodDescriptor;
+
+ private final String operationPrefix;
+
+ private final AbstractSpan asyncSpan;
+
+ TracingClientCallListener(Listener<RESPONSE> delegate,
MethodDescriptor<?, ?> methodDescriptor,
+ String operationPrefix, ContextSnapshot contextSnapshot,
AbstractSpan asyncSpan) {
+ super(delegate);
+ this.methodDescriptor = methodDescriptor;
+ this.operationPrefix = operationPrefix;
+ this.contextSnapshot = contextSnapshot;
+ this.asyncSpan = asyncSpan;
+ }
+
+ @Override
+ public void onMessage(RESPONSE message) {
+ if (methodDescriptor.getType().serverSendsOneMessage()) {
+ super.onMessage(message);
+ } else {
+ // tracing SERVER_STREAMING
+ final AbstractSpan span = ContextManager
+ .createLocalSpan(operationPrefix +
RESPONSE_ON_MESSAGE_OPERATION_NAME);
+ span.setComponent(ComponentsDefine.GRPC);
+ span.setLayer(SpanLayer.RPC_FRAMEWORK);
+ ContextManager.continued(contextSnapshot);
+ try {
+ delegate().onMessage(message);
+ } catch (Throwable t) {
+ span.log(t);
+ throw t;
+ } finally {
+ ContextManager.stopSpan(span);
+ }
+ }
+ }
+
+ @Override
+ public void onClose(Status status, Metadata trailers) {
+ if (!status.isOk()) {
+ asyncSpan.log(status.asRuntimeException());
+ }
+ Tags.RPC_RESPONSE_STATUS_CODE.set(asyncSpan,
status.getCode().name());
+ try {
+ delegate().onClose(status, trailers);
+ } catch (Throwable t) {
+ asyncSpan.log(t);
+ throw t;
+ } finally {
+ // finish async exitSpan
+ if (asyncSpan instanceof ExitSpan
+ &&
ContextManager.getRuntimeContext().get(CLIENT_STREAM_PEER) != null) {
+ // why need this? because first grpc call will create
PendingStream(Unable to get IP)
+ // Delayed create NettyClientStream.
+ // In this case, the constructor of NettyClientStream and
the onClose are executed in the same thread
+ ExitSpan exitSpan = (ExitSpan) asyncSpan;
+ if (Strings.isNullOrEmpty(exitSpan.getPeer())) {
+ asyncSpan.setPeer((String)
ContextManager.getRuntimeContext().get(CLIENT_STREAM_PEER));
+
ContextManager.getRuntimeContext().remove(CLIENT_STREAM_PEER);
+ }
+ }
+ asyncSpan.asyncFinish();
+ }
+ }
+ }
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/ClientCallsGenericCallInterceptor.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/ClientCallsGenericCallInterceptor.java
new file mode 100755
index 000000000..420c962c5
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/ClientCallsGenericCallInterceptor.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.client;
+
+import static
org.apache.skywalking.apm.plugin.grpc.v1.Constants.GENERIC_CALL_METHOD;
+
+import java.lang.reflect.Method;
+
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor;
+
+/**
+ * Trace calling services without grpc stubs.
+ * This type of request is characterized by the parameter DynamicMessage
+ */
+public class ClientCallsGenericCallInterceptor implements
StaticMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(Class clazz, Method method, Object[]
allArguments, Class<?>[] parameterTypes,
+ MethodInterceptResult result) {
+ // only trace async generic call. Determine if the request parameter
is a DynamicMessage
+ if (allArguments[1] instanceof com.google.protobuf.DynamicMessage) {
+ ContextManager.getRuntimeContext().put(GENERIC_CALL_METHOD,
method.getName());
+ }
+ }
+
+ @Override
+ public Object afterMethod(Class clazz, Method method, Object[]
allArguments, Class<?>[] parameterTypes,
+ Object ret) {
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(Class clazz, Method method, Object[]
allArguments, Class<?>[] parameterTypes,
+ Throwable t) {
+ }
+
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/NettyClientStreamInterceptor.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/NettyClientStreamInterceptor.java
new file mode 100644
index 000000000..17c4ee097
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/client/NettyClientStreamInterceptor.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.client;
+
+import static
org.apache.skywalking.apm.plugin.grpc.v1.Constants.CLIENT_STREAM_PEER;
+
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Intercept constructor to obtain server IP.
+ */
+@Slf4j
+public class NettyClientStreamInterceptor implements
InstanceConstructorInterceptor {
+
+ @Override
+ public void onConstruct(EnhancedInstance objInst, Object[] allArguments)
throws Throwable {
+ String authorityClass = allArguments[4].getClass().getName();
+ if ("io.netty.util.AsciiString".equals(authorityClass)
+ ||
"io.grpc.netty.shaded.io.netty.util.AsciiString".equals(authorityClass)) {
+ ContextManager.getRuntimeContext().put(CLIENT_STREAM_PEER,
allArguments[4].toString());
+ }
+ }
+
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ClientCallImplInstrumentation.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ClientCallImplInstrumentation.java
new file mode 100755
index 000000000..453748931
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ClientCallImplInstrumentation.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.define;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static
org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * create exitSpan in io.grpc.internal.ClientCallImpl#start method.
+ */
+public class ClientCallImplInstrumentation extends
ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS =
"io.grpc.internal.ClientCallImpl";
+
+ private static final String ENHANCE_METHOD = "start";
+
+ private static final String INTERCEPTOR_CLASS =
+
"org.apache.skywalking.apm.plugin.grpc.v1.client.ClientCallImplGenericCallInterceptor";
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[] {
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getConstructorMatcher() {
+ return any();
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+ }
+ };
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints()
{
+ return new InstanceMethodsInterceptPoint[] {
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getMethodsMatcher() {
+ return named(ENHANCE_METHOD);
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return true;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(ENHANCE_CLASS);
+ }
+
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ClientCallsGenericCallInstrumentation.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ClientCallsGenericCallInstrumentation.java
new file mode 100755
index 000000000..b1a73cbed
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ClientCallsGenericCallInstrumentation.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.define;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static
org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassStaticMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * trace generic call(Calling services without stubs).
+ */
+public class ClientCallsGenericCallInstrumentation extends
ClassStaticMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS = "io.grpc.stub.ClientCalls";
+
+ private static final String INTERCEPTOR_CLASS
+ =
"org.apache.skywalking.apm.plugin.grpc.v1.client.ClientCallsGenericCallInterceptor";
+
+ @Override
+ public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
+ return new StaticMethodsInterceptPoint[] {
+ new StaticMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getMethodsMatcher() {
+ return named("asyncUnaryCall");
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ },
+ new StaticMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getMethodsMatcher() {
+ return named("asyncServerStreamingCall");
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ },
+ new StaticMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getMethodsMatcher() {
+ return named("futureUnaryCall");
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(ENHANCE_CLASS);
+ }
+
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/NettyClientStreamInstrumentation.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/NettyClientStreamInstrumentation.java
new file mode 100644
index 000000000..dfe74285c
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/NettyClientStreamInstrumentation.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.define;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static
org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * Intercept constructor to obtain server IP.
+ * grpc Low version(eg:1.6.0): use io.grpc.netty.NettyClientStream.
+ * grpc High version(eg:1.33.1): use
io.grpc.netty.shaded.io.grpc.netty.NettyClientStream.
+ */
+public class NettyClientStreamInstrumentation extends
ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS =
"io.grpc.netty.NettyClientStream";
+
+ private static final String INTERCEPTOR_CLASS =
+
"org.apache.skywalking.apm.plugin.grpc.v1.client.NettyClientStreamInterceptor";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(ENHANCE_CLASS);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[] {
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getConstructorMatcher() {
+ // only one constructor
+ return any();
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+ }
+ };
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints()
{
+ return new InstanceMethodsInterceptPoint[0];
+ }
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ShadedNettyClientStreamInstrumentation.java
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ShadedNettyClientStreamInstrumentation.java
new file mode 100644
index 000000000..eb13db910
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/grpc/v1/define/ShadedNettyClientStreamInstrumentation.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.grpc.v1.define;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static
org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * Intercept constructor to obtain server IP.
+ * grpc Low version(eg:1.6.0): use io.grpc.netty.NettyClientStream.
+ * grpc High version(eg:1.33.1): use
io.grpc.netty.shaded.io.grpc.netty.NettyClientStream.
+ */
+public class ShadedNettyClientStreamInstrumentation extends
ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS =
"io.grpc.netty.shaded.io.grpc.netty.NettyClientStream";
+
+ private static final String INTERCEPTOR_CLASS =
+
"org.apache.skywalking.apm.plugin.grpc.v1.client.NettyClientStreamInterceptor";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(ENHANCE_CLASS);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[] {
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher<MethodDescription>
getConstructorMatcher() {
+ // only one constructor
+ return any();
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+ }
+ };
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints()
{
+ return new InstanceMethodsInterceptPoint[0];
+ }
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
index 7a151a7e3..11a137f4e 100644
---
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
+++
b/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
@@ -16,4 +16,8 @@
grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractStubInstrumentation
grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractServerImplBuilderInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallsInstrumentation
\ No newline at end of file
+grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallsInstrumentation
+grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallsGenericCallInstrumentation
+grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallImplInstrumentation
+grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.NettyClientStreamInstrumentation
+grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ShadedNettyClientStreamInstrumentation
\ No newline at end of file
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/config/expectedData.yaml
b/test/plugin/scenarios/grpc-generic-call-scenario/config/expectedData.yaml
new file mode 100644
index 000000000..0d214640a
--- /dev/null
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/config/expectedData.yaml
@@ -0,0 +1,105 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+segmentItems:
+ - serviceName: grpc-provider
+ segmentSize: nq 0
+ segments:
+ - segmentId: not null
+ spans:
+ - operationName:
org.apache.skywalking.apm.testcase.grpc.proto.TestServiceDynamic.unary/server/Response/onClose
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: RPCFramework
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 23
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: false
+ tags:
+ - {key: rpc.status_code, value: OK}
+ - operationName:
org.apache.skywalking.apm.testcase.grpc.proto.TestServiceDynamic.unary/server/Request/onComplete
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: RPCFramework
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 23
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: false
+ refs:
+ - { parentEndpoint:
org.apache.skywalking.apm.testcase.grpc.proto.TestServiceDynamic.unary,
+ networkAddress: '', refType: CrossThread,
+ parentSpanId: 0, parentTraceSegmentId: not null,
+ parentServiceInstance: not null, parentService:
grpc-provider,
+ traceId: not null }
+ - segmentId: not null
+ spans:
+ - operationName:
org.apache.skywalking.apm.testcase.grpc.proto.TestServiceDynamic.unary
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: RPCFramework
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 23
+ isError: false
+ spanType: Entry
+ peer: ''
+ skipAnalysis: false
+ refs:
+ - { parentEndpoint: 'GET:/case/generic-call', networkAddress:
not null,
+ refType: CrossProcess, parentSpanId: 1,
parentTraceSegmentId: not null,
+ parentServiceInstance: not null, parentService:
grpc-consumer,
+ traceId: not null }
+
+ - serviceName: grpc-consumer
+ segmentSize: nq 0
+ segments:
+ - segmentId: not null
+ spans:
+ - operationName:
org.apache.skywalking.apm.testcase.grpc.proto.TestServiceDynamic.unary
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: RPCFramework
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 23
+ isError: false
+ spanType: Exit
+ peer: 'localhost:18080'
+ skipAnalysis: false
+ tags:
+ - {key: GENERIC_CALL_METHOD, value: asyncUnaryCall}
+ - {key: rpc.status_code, value: OK}
+ - operationName: GET:/case/generic-call
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 14
+ isError: false
+ spanType: Entry
+ peer: ''
+ skipAnalysis: false
+ tags:
+ - {key: url, value: 'http://localhost:8888/case/generic-call'}
+ - {key: http.method, value: GET}
+
+meterItems: []
\ No newline at end of file
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
b/test/plugin/scenarios/grpc-generic-call-scenario/configuration.yml
similarity index 74%
copy from
apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
copy to test/plugin/scenarios/grpc-generic-call-scenario/configuration.yml
index 7a151a7e3..9b232529b 100644
---
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/configuration.yml
@@ -14,6 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractStubInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractServerImplBuilderInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallsInstrumentation
\ No newline at end of file
+type: jvm
+entryService: http://localhost:8888/case/generic-call
+healthCheck: http://localhost:8888/case/healthCheck
+startScript: ./bin/startup.sh
+environment:
+dependencies:
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/pom.xml
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/pom.xml
new file mode 100644
index 000000000..4150dc06f
--- /dev/null
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>grpc-generic-call-scenario</artifactId>
+ <groupId>org.apache.skywalking</groupId>
+ <version>1.0.0</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>grpc-consumer</artifactId>
+
+ <properties>
+ <maven.compiler.source>8</maven.compiler.source>
+ <maven.compiler.target>8</maven.compiler.target>
+ </properties>
+
+ <dependencies>
+ <!-- grpc -->
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-all</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <!--just for test-->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>grpc-consumer</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring.boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/GrpcConsumerApplication.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/GrpcConsumerApplication.java
new file mode 100644
index 000000000..98f8fc7a0
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/GrpcConsumerApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package test.apache.skywalking.apm.testcase.grpc.generic.call.consumer;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GrpcConsumerApplication {
+
+ public static void main(final String[] args) {
+ SpringApplication.run(GrpcConsumerApplication.class, args);
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/DynamicJsonMessage.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/DynamicJsonMessage.java
new file mode 100755
index 000000000..de7149bbc
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/DynamicJsonMessage.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package test.apache.skywalking.apm.testcase.grpc.generic.call.consumer.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.protobuf.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.ExtensionRegistryLite;
+
+import io.grpc.MethodDescriptor;
+
+public class DynamicJsonMessage {
+ public static final String DYNAMIC_SERVICE_SUFFIX = "Dynamic";
+
+ public static final String DYNAMIC_MESSAGE_NAME = "DynamicJsonMessage";
+
+ public static final String DYNAMIC_MESSAGE_DATA_FILED = "data";
+
+ private static final Logger LOG =
LoggerFactory.getLogger(DynamicJsonMessage.class);
+
+ private static Descriptors.Descriptor buildJsonMarshallerDescriptor() {
+
+ DescriptorProtos.DescriptorProto.Builder jsonMarshaller =
DescriptorProtos.DescriptorProto.newBuilder();
+ jsonMarshaller.setName(DYNAMIC_MESSAGE_NAME);
+ jsonMarshaller.addFieldBuilder()
+ .setName(DYNAMIC_MESSAGE_DATA_FILED)
+ .setNumber(1)
+
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING);
+
+ DescriptorProtos.FileDescriptorProto.Builder
fileDescriptorProtoBuilder =
+ DescriptorProtos.FileDescriptorProto.newBuilder();
+ fileDescriptorProtoBuilder.addMessageType(jsonMarshaller);
+
+ DescriptorProtos.FileDescriptorProto fileDescriptorProto =
fileDescriptorProtoBuilder.build();
+ try {
+ Descriptors.FileDescriptor fileDescriptor =
Descriptors.FileDescriptor
+ .buildFrom(fileDescriptorProto, new
Descriptors.FileDescriptor[0]);
+ return fileDescriptor.findMessageTypeByName(DYNAMIC_MESSAGE_NAME);
+ } catch (Exception e) {
+ LOG.error("buildJsonMarshallerDescriptor error: {}",
e.getMessage());
+ throw new RuntimeException("buildJsonMarshallerDescriptor error",
e);
+ }
+ }
+
+ public static DynamicMessage buildJsonDynamicMessage(String jsonParam) {
+ // build Descriptor and set request param
+ Descriptors.Descriptor jsonDescriptor =
buildJsonMarshallerDescriptor();
+ DynamicMessage.Builder jsonDynamicMessage =
DynamicMessage.newBuilder(jsonDescriptor);
+
jsonDynamicMessage.setField(jsonDescriptor.findFieldByName(DYNAMIC_MESSAGE_DATA_FILED),
+ jsonParam);
+ return jsonDynamicMessage.build();
+ }
+
+ public static DynamicMessage buildJsonDynamicMessage() {
+ Descriptors.Descriptor jsonDescriptor =
buildJsonMarshallerDescriptor();
+ DynamicMessage.Builder jsonDynamicMessage =
DynamicMessage.newBuilder(jsonDescriptor);
+ return jsonDynamicMessage.build();
+ }
+
+ public static String getDataFromDynamicMessage(DynamicMessage message) {
+ for (Map.Entry<Descriptors.FieldDescriptor, Object> entry :
message.getAllFields().entrySet()) {
+ Descriptors.FieldDescriptor key = entry.getKey();
+ Object value = entry.getValue();
+ String fullName = key.getFullName();
+ String jsonMessageFullName = DYNAMIC_MESSAGE_NAME + "." +
DYNAMIC_MESSAGE_DATA_FILED;
+ if (jsonMessageFullName.equals(fullName)) {
+ return (String) value;
+ }
+ }
+ return "";
+ }
+
+ public static MethodDescriptor<DynamicMessage, DynamicMessage>
createJsonMarshallerMethodDescriptor(
+ String serviceName, String methodName, MethodDescriptor.MethodType
methodType,
+ DynamicMessage request, DynamicMessage response) {
+
+ return MethodDescriptor.<DynamicMessage, DynamicMessage>newBuilder()
+ .setType(methodType)
+ .setFullMethodName(
+ MethodDescriptor.generateFullMethodName(serviceName +
DYNAMIC_SERVICE_SUFFIX,
+ methodName))
+ .setRequestMarshaller(new
DynamicMessageMarshaller(request.getDescriptorForType()))
+ .setResponseMarshaller(new
DynamicMessageMarshaller(response.getDescriptorForType()))
+ .build();
+ }
+
+ private static class DynamicMessageMarshaller implements
MethodDescriptor.Marshaller<DynamicMessage> {
+
+ private Descriptors.Descriptor messageDescriptor;
+
+ private DynamicMessageMarshaller(Descriptors.Descriptor
messageDescriptor) {
+ this.messageDescriptor = messageDescriptor;
+ }
+
+ @Override
+ public DynamicMessage parse(InputStream inputStream) {
+ try {
+ return DynamicMessage.newBuilder(messageDescriptor)
+ .mergeFrom(inputStream,
ExtensionRegistryLite.getEmptyRegistry())
+ .build();
+ } catch (IOException e) {
+ throw new RuntimeException("parse inputStream error", e);
+ }
+ }
+
+ @Override
+ public InputStream stream(DynamicMessage abstractMessage) {
+ return abstractMessage.toByteString().newInput();
+ }
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/GrpcGenericCallClient.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/GrpcGenericCallClient.java
new file mode 100755
index 000000000..2a429334c
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/GrpcGenericCallClient.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package test.apache.skywalking.apm.testcase.grpc.generic.call.consumer.client;
+
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson.JSON;
+import com.google.protobuf.DynamicMessage;
+
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.MethodDescriptor;
+
+@Component
+public class GrpcGenericCallClient implements InitializingBean {
+
+ @Value("${grpc.port}")
+ private int grpcPort;
+
+ private ManagedChannel channel;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ channel = ManagedChannelBuilder.forAddress("localhost",
grpcPort).usePlaintext(true).build();
+ }
+
+ /**
+ * generic call(without grpc stubs and proto)
+ */
+ public Object genericCall(String serviceName, String methodName,
MethodDescriptor.MethodType methodType,
+ List<Map<String, Object>> paramsList) throws Exception {
+ // param to dynamicMessage
+ List<DynamicMessage> jsonRequestList = paramsList.stream()
+ .map(params ->
DynamicJsonMessage.buildJsonDynamicMessage(JSON.toJSONString(params))).collect(Collectors.toList());
+ MethodDescriptor<DynamicMessage, DynamicMessage>
jsonMarshallerMethodDescriptor =
+
DynamicJsonMessage.createJsonMarshallerMethodDescriptor(serviceName,
methodName, methodType,
+ jsonRequestList.get(0),
DynamicJsonMessage.buildJsonDynamicMessage());
+ SimpleStreamObserver<DynamicMessage> simpleStreamObserver = new
SimpleStreamObserver<>();
+ ClientCall<DynamicMessage, DynamicMessage> call =
channel.newCall(jsonMarshallerMethodDescriptor,
+ CallOptions.DEFAULT.withDeadlineAfter(10000,
TimeUnit.MILLISECONDS));
+
+ switch (methodType) {
+ case UNARY:
+ asyncUnaryCall(call, jsonRequestList.get(0),
simpleStreamObserver);
+ return simpleStreamObserver.syncGetResponseDataList();
+ case SERVER_STREAMING:
+ asyncServerStreamingCall(call, jsonRequestList.get(0),
simpleStreamObserver);
+ return simpleStreamObserver.syncGetResponseDataList();
+ default:
+ return null;
+ }
+ }
+
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/SimpleStreamObserver.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/SimpleStreamObserver.java
new file mode 100755
index 000000000..dd446e4ca
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/client/SimpleStreamObserver.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package test.apache.skywalking.apm.testcase.grpc.generic.call.consumer.client;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.fastjson.JSON;
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.Message;
+
+import io.grpc.stub.StreamObserver;
+
+public class SimpleStreamObserver<T extends Message> implements
StreamObserver<T> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(SimpleStreamObserver.class);
+
+ private final List<String> responseDataList = new ArrayList<>();
+
+ private final CompletableFuture<Void> completableFuture = new
CompletableFuture<>();
+
+ @Override
+ public void onNext(T value) {
+ try {
+ // dynamicMessage to json
+ String respData =
DynamicJsonMessage.getDataFromDynamicMessage((DynamicMessage) value);
+ responseDataList.add(respData);
+ } catch (Exception e) {
+ LOG.error("parse error", e);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ LOG.error("SimpleStreamObserver onError", t);
+ completableFuture.completeExceptionally(t);
+ }
+
+ @Override
+ public void onCompleted() {
+ LOG.info("SimpleStreamObserver onCompleted");
+ completableFuture.complete(null);
+ }
+
+ public List<Object> syncGetResponseDataList() throws Exception {
+ completableFuture.get();
+ return
responseDataList.stream().map(JSON::parseObject).collect(Collectors.toList());
+ }
+
+ public CompletableFuture<Void> getCompletableFuture() {
+ return completableFuture;
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/controller/CaseController.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/controller/CaseController.java
new file mode 100755
index 000000000..895e56800
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/java/test/apache/skywalking/apm/testcase/grpc/generic/call/consumer/controller/CaseController.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
test.apache.skywalking.apm.testcase.grpc.generic.call.consumer.controller;
+
+import java.util.Map;
+
+import
test.apache.skywalking.apm.testcase.grpc.generic.call.consumer.client.GrpcGenericCallClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import io.grpc.MethodDescriptor.MethodType;
+
+@RestController
+@RequestMapping("/case")
+public class CaseController {
+
+ @Autowired
+ private GrpcGenericCallClient grpcGenericCallClient;
+
+ @RequestMapping("/healthCheck")
+ @ResponseBody
+ public String healthCheck() {
+ return "OK";
+ }
+
+ @RequestMapping("/generic-call")
+ @ResponseBody
+ public Object genericCall() throws Exception {
+ Thread.sleep(1000);
+ Map<String, Object> params = ImmutableMap.of("data", "hello unary");
+ return
grpcGenericCallClient.genericCall("org.apache.skywalking.apm.testcase.grpc.proto.TestService",
+ "unary", MethodType.UNARY, Lists.newArrayList(params));
+ }
+
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/resources/application.yml
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/resources/application.yml
new file mode 100644
index 000000000..d7f4f635d
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-consumer/src/main/resources/application.yml
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+server:
+ port: 8888
+grpc:
+ port: 18080
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/bin/startup.sh
similarity index 73%
copy from
apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
copy to
test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/bin/startup.sh
index 7a151a7e3..a03ec2fdf 100644
---
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/bin/startup.sh
@@ -1,3 +1,5 @@
+#!/bin/bash
+#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -14,6 +16,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractStubInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractServerImplBuilderInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallsInstrumentation
\ No newline at end of file
+home="$(cd "$(dirname $0)"; pwd)"
+
+java -jar ${agent_opts} "-Dskywalking.agent.service_name=grpc-provider"
${home}/../libs/grpc-provider.jar &
+sleep 2
+
+java -jar ${agent_opts} "-Dskywalking.agent.service_name=grpc-consumer"
${home}/../libs/grpc-consumer.jar &
+sleep 2
+
+
diff --git a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/pom.xml
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/pom.xml
new file mode 100644
index 000000000..7574e8bf9
--- /dev/null
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>grpc-generic-call-scenario</artifactId>
+ <groupId>org.apache.skywalking</groupId>
+ <version>1.0.0</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>grpc-dist</artifactId>
+
+ <properties>
+ <maven.compiler.source>8</maven.compiler.source>
+ <maven.compiler.target>8</maven.compiler.target>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+
<descriptor>src/main/assembly/assembly.xml</descriptor>
+ </descriptors>
+ <outputDirectory>../target/</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/src/main/assembly/assembly.xml
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/src/main/assembly/assembly.xml
new file mode 100644
index 000000000..8a2ae21fa
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-dist/src/main/assembly/assembly.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<assembly
+
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <directory>./bin</directory>
+ <fileMode>0775</fileMode>
+ </fileSet>
+ </fileSets>
+
+ <files>
+ <file>
+ <source>../grpc-provider/target/grpc-provider.jar</source>
+ <outputDirectory>./libs</outputDirectory>
+ <fileMode>0775</fileMode>
+ </file>
+
+ <file>
+ <source>../grpc-consumer/target/grpc-consumer.jar</source>
+ <outputDirectory>./libs</outputDirectory>
+ <fileMode>0775</fileMode>
+ </file>
+
+ </files>
+</assembly>
\ No newline at end of file
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/pom.xml
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/pom.xml
new file mode 100644
index 000000000..9003c03bf
--- /dev/null
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/pom.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>grpc-generic-call-scenario</artifactId>
+ <groupId>org.apache.skywalking</groupId>
+ <version>1.0.0</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>grpc-provider</artifactId>
+
+ <properties>
+ <maven.compiler.source>8</maven.compiler.source>
+ <maven.compiler.target>8</maven.compiler.target>
+ <os-maven-plugin.version>1.6.2</os-maven-plugin.version>
+ </properties>
+
+ <dependencies>
+
+ <!-- grpc -->
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-all</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <!--just for test-->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ </dependencies>
+
+
+ <build>
+ <finalName>grpc-provider</finalName>
+ <plugins>
+ <plugin>
+ <groupId>kr.motd.maven</groupId>
+ <artifactId>os-maven-plugin</artifactId>
+ <version>${os-maven-plugin.version}</version>
+ <executions>
+ <execution>
+ <phase>initialize</phase>
+ <goals>
+ <goal>detect</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring.boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.xolstice.maven.plugins</groupId>
+ <artifactId>protobuf-maven-plugin</artifactId>
+ <version>0.5.0</version>
+ <configuration>
+ <!--
+ The version of protoc must match protobuf-java. If you
don't depend on
+ protobuf-java directly, you will be transitively
depending on the
+ protobuf-java version that grpc depends on.
+ -->
+
<protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}
+ </protocArtifact>
+ <pluginId>grpc-java</pluginId>
+
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${test.framework.version}:exe:${os.detected.classifier}
+ </pluginArtifact>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>compile</goal>
+ <goal>compile-custom</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/GrpcProviderApplication.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/GrpcProviderApplication.java
new file mode 100644
index 000000000..361dd51cf
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/GrpcProviderApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.apm.testcase.grpc.generic.call.provider;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GrpcProviderApplication {
+
+ public static void main(final String[] args) {
+ SpringApplication.run(GrpcProviderApplication.class, args);
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/provider/ProviderConfiguration.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/provider/ProviderConfiguration.java
new file mode 100755
index 000000000..680f55ff8
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/provider/ProviderConfiguration.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.testcase.grpc.generic.call.provider.provider;
+
+import
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.provider.service.TestServiceImpl;
+import
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.dynamic.DynamicMessageServiceTranslator;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerServiceDefinition;
+
+@Configuration
+public class ProviderConfiguration {
+
+ @Value("${grpc.port}")
+ private int grpcPort;
+
+ @Bean(initMethod = "start", destroyMethod = "shutdown")
+ public Server server(TestServiceImpl testService) throws Exception {
+ ServerServiceDefinition serviceDefinition = testService.bindService();
+ // For testing purposes, the `GRPC Server Reflection Protocol` is not
used here.
+ // GRPC Server Reflection Protocol:
https://github.com/grpc/grpc/blob/master/doc/server-reflection.md
+ // No dependency: io.grpc:grpc-services:${grpcVersion}
+ ServerServiceDefinition jsonDefinition =
+
DynamicMessageServiceTranslator.buildDynamicServerServiceDefinition(serviceDefinition);
+ return ServerBuilder.forPort(grpcPort)
+ .addService(serviceDefinition)
+ .addService(jsonDefinition)
+ .build();
+ }
+
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/provider/service/TestServiceImpl.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/provider/service/TestServiceImpl.java
new file mode 100755
index 000000000..fe97ad44d
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/provider/service/TestServiceImpl.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.provider.service;
+
+import org.apache.skywalking.apm.testcase.grpc.proto.RequestData;
+import org.apache.skywalking.apm.testcase.grpc.proto.ResponseData;
+import org.apache.skywalking.apm.testcase.grpc.proto.TestServiceGrpc;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import io.grpc.stub.StreamObserver;
+
+@Service
+public class TestServiceImpl extends TestServiceGrpc.TestServiceImplBase {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(TestServiceImpl.class);
+
+ @Override
+ public void unary(RequestData request, StreamObserver<ResponseData>
responseObserver) {
+ LOG.info("server unary receive data:{}", request.getData());
+ ResponseData responseData =
ResponseData.newBuilder().setData("unaryFun response").build();
+ responseObserver.onNext(responseData);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void serverStreaming(RequestData request,
StreamObserver<ResponseData> responseObserver) {
+ LOG.info("server serverStreaming receive data:{}", request.getData());
+ for (int i = 0; i < 2; i++) {
+ ResponseData responseData =
ResponseData.newBuilder().setData("serverStreaming response data " + i).build();
+ responseObserver.onNext(responseData);
+ }
+ responseObserver.onCompleted();
+ }
+
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/constant/Constants.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/constant/Constants.java
new file mode 100755
index 000000000..7bf080ca4
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/constant/Constants.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.constant;
+
+public class Constants {
+
+ public static final String DYNAMIC_SERVICE_SUFFIX = "Dynamic";
+
+ public static final String DYNAMIC_MESSAGE_NAME = "DynamicJsonMessage";
+
+ public static final String DYNAMIC_MESSAGE_DATA_FILED = "data";
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicJsonMessage.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicJsonMessage.java
new file mode 100755
index 000000000..00fe5cfa6
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicJsonMessage.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.dynamic;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.constant.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.protobuf.DescriptorProtos;
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.ExtensionRegistryLite;
+
+import io.grpc.MethodDescriptor;
+
+public class DynamicJsonMessage {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(DynamicJsonMessage.class);
+
+ private static Descriptors.Descriptor buildJsonMarshallerDescriptor() {
+
+ DescriptorProtos.DescriptorProto.Builder jsonMarshaller =
DescriptorProtos.DescriptorProto.newBuilder();
+ jsonMarshaller.setName(Constants.DYNAMIC_MESSAGE_NAME);
+ jsonMarshaller.addFieldBuilder()
+ .setName(Constants.DYNAMIC_MESSAGE_DATA_FILED)
+ .setNumber(1)
+
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING);
+
+ DescriptorProtos.FileDescriptorProto.Builder
fileDescriptorProtoBuilder =
+ DescriptorProtos.FileDescriptorProto.newBuilder();
+ fileDescriptorProtoBuilder.addMessageType(jsonMarshaller);
+
+ DescriptorProtos.FileDescriptorProto fileDescriptorProto =
fileDescriptorProtoBuilder.build();
+ try {
+ Descriptors.FileDescriptor fileDescriptor =
Descriptors.FileDescriptor
+ .buildFrom(fileDescriptorProto, new
Descriptors.FileDescriptor[0]);
+ return
fileDescriptor.findMessageTypeByName(Constants.DYNAMIC_MESSAGE_NAME);
+ } catch (Exception e) {
+ LOG.error("buildJsonMarshallerDescriptor error: {}",
e.getMessage());
+ throw new RuntimeException("buildJsonMarshallerDescriptor error",
e);
+ }
+ }
+
+ public static DynamicMessage buildJsonDynamicMessage(String jsonParam) {
+ // build Descriptor and set request param
+ Descriptors.Descriptor jsonDescriptor =
buildJsonMarshallerDescriptor();
+ DynamicMessage.Builder jsonDynamicMessage =
DynamicMessage.newBuilder(jsonDescriptor);
+
jsonDynamicMessage.setField(jsonDescriptor.findFieldByName(Constants.DYNAMIC_MESSAGE_DATA_FILED),
+ jsonParam);
+ return jsonDynamicMessage.build();
+ }
+
+ public static DynamicMessage buildJsonDynamicMessage() {
+ Descriptors.Descriptor jsonDescriptor =
buildJsonMarshallerDescriptor();
+ DynamicMessage.Builder jsonDynamicMessage =
DynamicMessage.newBuilder(jsonDescriptor);
+ return jsonDynamicMessage.build();
+ }
+
+ public static String getDataFromDynamicMessage(DynamicMessage message) {
+ for (Map.Entry<Descriptors.FieldDescriptor, Object> entry :
message.getAllFields().entrySet()) {
+ Descriptors.FieldDescriptor key = entry.getKey();
+ Object value = entry.getValue();
+ String fullName = key.getFullName();
+ String jsonMessageFullName = Constants.DYNAMIC_MESSAGE_NAME + "."
+ Constants.DYNAMIC_MESSAGE_DATA_FILED;
+ if (jsonMessageFullName.equals(fullName)) {
+ return (String) value;
+ }
+ }
+ return "";
+ }
+
+ public static MethodDescriptor<DynamicMessage, DynamicMessage>
createJsonMarshallerMethodDescriptor(
+ String serviceName, String methodName, MethodDescriptor.MethodType
methodType,
+ DynamicMessage request, DynamicMessage response) {
+
+ return MethodDescriptor.<DynamicMessage, DynamicMessage>newBuilder()
+ .setType(methodType)
+ .setFullMethodName(
+ MethodDescriptor.generateFullMethodName(serviceName +
Constants.DYNAMIC_SERVICE_SUFFIX,
+ methodName))
+ .setRequestMarshaller(new
DynamicMessageMarshaller(request.getDescriptorForType()))
+ .setResponseMarshaller(new
DynamicMessageMarshaller(response.getDescriptorForType()))
+ .build();
+ }
+
+ private static class DynamicMessageMarshaller implements
MethodDescriptor.Marshaller<DynamicMessage> {
+
+ private Descriptors.Descriptor messageDescriptor;
+
+ private DynamicMessageMarshaller(Descriptors.Descriptor
messageDescriptor) {
+ this.messageDescriptor = messageDescriptor;
+ }
+
+ @Override
+ public DynamicMessage parse(InputStream inputStream) {
+ try {
+ return DynamicMessage.newBuilder(messageDescriptor)
+ .mergeFrom(inputStream,
ExtensionRegistryLite.getEmptyRegistry())
+ .build();
+ } catch (IOException e) {
+ throw new RuntimeException("parse inputStream error", e);
+ }
+ }
+
+ @Override
+ public InputStream stream(DynamicMessage abstractMessage) {
+ return abstractMessage.toByteString().newInput();
+ }
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicMessageServiceTranslator.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicMessageServiceTranslator.java
new file mode 100755
index 000000000..cc4c87a27
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicMessageServiceTranslator.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.dynamic;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.constant.Constants;
+
+import com.google.protobuf.DynamicMessage;
+
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.PrototypeMarshaller;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+
+public class DynamicMessageServiceTranslator {
+
+ private static final Map<String, Class<?>> REQUEST_PARAMS_CLASS_MAP = new
HashMap<>();
+
+ public static ServerServiceDefinition
buildDynamicServerServiceDefinition(ServerServiceDefinition serviceDef)
+ throws Exception {
+ MethodDescriptor.Marshaller<DynamicMessage> marshaller =
+
io.grpc.protobuf.ProtoUtils.marshaller(DynamicJsonMessage.buildJsonDynamicMessage());
+ List<ServerMethodDefinition<?, ?>> wrappedMethods = new ArrayList<>();
+ List<MethodDescriptor<?, ?>> wrappedDescriptors = new ArrayList<>();
+
+ for (ServerMethodDefinition<?, ?> definition :
serviceDef.getMethods()) {
+ MethodDescriptor.Marshaller<?> requestMarshaller =
definition.getMethodDescriptor().getRequestMarshaller();
+
+ String fullMethodName =
definition.getMethodDescriptor().getFullMethodName();
+
+ String[] splitMethodName = fullMethodName.split("/");
+ fullMethodName = splitMethodName[0] +
Constants.DYNAMIC_SERVICE_SUFFIX + "/" + splitMethodName[1];
+ if (requestMarshaller instanceof
MethodDescriptor.PrototypeMarshaller) {
+ MethodDescriptor.PrototypeMarshaller<?> prototypeMarshaller =
+ (PrototypeMarshaller<?>) requestMarshaller;
+ REQUEST_PARAMS_CLASS_MAP.put(fullMethodName,
prototypeMarshaller.getMessagePrototype().getClass());
+ }
+ MethodDescriptor<?, ?> originalMethodDescriptor =
definition.getMethodDescriptor();
+ MethodDescriptor<DynamicMessage, DynamicMessage>
wrappedMethodDescriptor = originalMethodDescriptor
+ .toBuilder(marshaller, marshaller).build();
+ wrappedDescriptors.add(wrappedMethodDescriptor);
+ wrappedMethods.add(wrapMethod(definition,
wrappedMethodDescriptor));
+ }
+
+ ServiceDescriptor.Builder build = ServiceDescriptor.newBuilder(
+ serviceDef.getServiceDescriptor().getName() +
Constants.DYNAMIC_SERVICE_SUFFIX);
+ for (MethodDescriptor<?, ?> md : wrappedDescriptors) {
+ Field fullMethodNameField = getField(md.getClass(),
"fullMethodName");
+ fullMethodNameField.setAccessible(true);
+ String fullMethodName = (String) fullMethodNameField.get(md);
+ String[] splitMethodName = fullMethodName.split("/");
+ fullMethodName = splitMethodName[0] +
Constants.DYNAMIC_SERVICE_SUFFIX + "/" + splitMethodName[1];
+ fullMethodNameField.set(md, fullMethodName);
+
+ String serviceName;
+ Field serviceNameField = getField(md.getClass(), "serviceName");
+ if (Objects.nonNull(serviceNameField)) {
+ serviceNameField.setAccessible(true);
+ serviceName = (String) serviceNameField.get(md);
+ serviceName = serviceName + Constants.DYNAMIC_SERVICE_SUFFIX;
+ serviceNameField.set(md, serviceName);
+ }
+ build.addMethod(md);
+ }
+ ServerServiceDefinition.Builder serviceBuilder =
ServerServiceDefinition.builder(build.build());
+
+ for (ServerMethodDefinition<?, ?> definition : wrappedMethods) {
+ serviceBuilder.addMethod(definition);
+ }
+ return serviceBuilder.build();
+ }
+
+ private static Field getField(final Class<?> beanClass, final String name)
throws SecurityException {
+ final Field[] fields = beanClass.getDeclaredFields();
+ return Arrays.stream(fields).filter(field -> Objects.equals(name,
field.getName()))
+ .findFirst().orElse(null);
+ }
+
+ private static <R, P, W, M> ServerMethodDefinition<W, M> wrapMethod(
+ ServerMethodDefinition<R, P> definition,
+ MethodDescriptor<W, M> wrappedMethod) {
+ ServerCallHandler<W, M> wrappedHandler =
wrapHandler(definition.getServerCallHandler());
+ return ServerMethodDefinition.create(wrappedMethod, wrappedHandler);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <R, P, W, M> ServerCallHandler<W, M> wrapHandler(
+ ServerCallHandler<R, P> originalHandler) {
+ return (call, headers) -> {
+ ServerCall<R, P> unwrappedCall = new
ProtoToDynamicServerCall<>((ServerCall<R, P>) call);
+ ServerCall.Listener<R> originalListener =
originalHandler.startCall(unwrappedCall, headers);
+ return new DynamicToProtoServerCallListener(originalListener,
unwrappedCall);
+ };
+ }
+
+ public static Map<String, Class<?>> getRequestParamsClassMap() {
+ return REQUEST_PARAMS_CLASS_MAP;
+ }
+
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicToProtoServerCallListener.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicToProtoServerCallListener.java
new file mode 100755
index 000000000..5443d4d9f
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/DynamicToProtoServerCallListener.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.dynamic;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+
+import io.grpc.ForwardingServerCallListener.SimpleForwardingServerCallListener;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.Status;
+
+public class DynamicToProtoServerCallListener<R, P> extends
SimpleForwardingServerCallListener<R> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(DynamicToProtoServerCallListener.class);
+
+ private final ServerCall<R, P> call;
+
+ public DynamicToProtoServerCallListener(final Listener<R> delegate, final
ServerCall<R, P> call) {
+ super(delegate);
+ this.call = call;
+ }
+
+ /**
+ * dynamicMessage to proto message
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void onMessage(final R message) {
+ Message.Builder builder;
+ Class<?> t =
+
DynamicMessageServiceTranslator.getRequestParamsClassMap().get(call.getMethodDescriptor().getFullMethodName());
+ try {
+ builder = (Message.Builder) invokeStaticMethod(t, "newBuilder");
+ // dynamicMessage to proto message
+ String reqData =
DynamicJsonMessage.getDataFromDynamicMessage((DynamicMessage) message);
+ JsonFormat.parser().ignoringUnknownFields().merge(reqData,
builder);
+ if (Objects.isNull(builder)) {
+ throw new RuntimeException("build json response message is
error, newBuilder method is null");
+ }
+
+ delegate().onMessage((R) builder.build());
+ } catch (Exception e) {
+ LOG.error("handle json generic request is error", e);
+ throw
Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException();
+ }
+ }
+
+ public static Object invokeStaticMethod(final Class<?> clazz, final String
method) {
+ try {
+ return MethodUtils.invokeStaticMethod(clazz, method);
+ } catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/ProtoToDynamicServerCall.java
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/ProtoToDynamicServerCall.java
new file mode 100755
index 000000000..e007fef0d
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/java/org/apache/skywalking/apm/testcase/grpc/generic/call/provider/server/dynamic/ProtoToDynamicServerCall.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package
org.apache.skywalking.apm.testcase.grpc.generic.call.provider.server.dynamic;
+
+import com.google.protobuf.DynamicMessage;
+import com.google.protobuf.MessageOrBuilder;
+import com.google.protobuf.util.JsonFormat;
+
+import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
+import io.grpc.ServerCall;
+import io.grpc.Status;
+
+public class ProtoToDynamicServerCall<R, P> extends
SimpleForwardingServerCall<R, P> {
+
+ protected ProtoToDynamicServerCall(ServerCall<R, P> delegate) {
+ super(delegate);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void sendMessage(final P message) {
+ try {
+ if (message == null) {
+ delegate().sendMessage(null);
+ return;
+ }
+ // proto message to dynamicMessage
+ String jsonFormat =
JsonFormat.printer().includingDefaultValueFields().preservingProtoFieldNames()
+ .print((MessageOrBuilder) message);
+ DynamicMessage respMessage =
DynamicJsonMessage.buildJsonDynamicMessage(jsonFormat);
+ delegate().sendMessage((P) respMessage);
+ } catch (Exception e) {
+ throw
Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException();
+ }
+ }
+}
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/proto/TestService.proto
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/proto/TestService.proto
new file mode 100755
index 000000000..3d3b6f953
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/proto/TestService.proto
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+syntax = "proto3";
+
+package org.apache.skywalking.apm.testcase.grpc.proto;
+
+option java_multiple_files = true;
+option java_package = "org.apache.skywalking.apm.testcase.grpc.proto";
+
+service TestService {
+
+ rpc unary(RequestData) returns (ResponseData){}
+
+ rpc serverStreaming(RequestData) returns (stream ResponseData){}
+
+}
+
+message RequestData {
+ string data = 1;
+}
+
+message ResponseData {
+ string data = 1;
+}
\ No newline at end of file
diff --git
a/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/resources/application.yml
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/resources/application.yml
new file mode 100644
index 000000000..87862a8f9
--- /dev/null
+++
b/test/plugin/scenarios/grpc-generic-call-scenario/grpc-provider/src/main/resources/application.yml
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+server:
+ port: 9999
+grpc:
+ port: 18080
+
diff --git a/test/plugin/scenarios/grpc-generic-call-scenario/pom.xml
b/test/plugin/scenarios/grpc-generic-call-scenario/pom.xml
new file mode 100644
index 000000000..406f1c397
--- /dev/null
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>grpc-generic-call-scenario</artifactId>
+ <version>1.0.0</version>
+ <packaging>pom</packaging>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <modules>
+ <module>grpc-consumer</module>
+ <module>grpc-provider</module>
+ <module>grpc-dist</module>
+ </modules>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <compiler.version>1.8</compiler.version>
+ <test.framework.version>1.6.0</test.framework.version>
+ <grpc.version>1.6.0</grpc.version>
+ <spring.boot.version>2.2.2.RELEASE</spring.boot.version>
+ <lombok.version>1.18.20</lombok.version>
+ <!--just for test-->
+ <fastjson.version>1.2.79</fastjson.version>
+ </properties>
+
+ <name>skywalking-grpc-generic-call-scenario</name>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>${spring.boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <!-- grpc -->
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-all</artifactId>
+ <version>${test.framework.version}</version>
+ </dependency>
+
+ <!--just for test-->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>${fastjson.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <finalName>grpc-generic-call-scenario</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.10.1</version>
+ <configuration>
+ <source>${compiler.version}</source>
+ <target>${compiler.version}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project>
diff --git
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
b/test/plugin/scenarios/grpc-generic-call-scenario/support-version.list
similarity index 74%
copy from
apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
copy to test/plugin/scenarios/grpc-generic-call-scenario/support-version.list
index 7a151a7e3..2515c3319 100644
---
a/apm-sniffer/apm-sdk-plugin/grpc-1.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/test/plugin/scenarios/grpc-generic-call-scenario/support-version.list
@@ -14,6 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractStubInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.AbstractServerImplBuilderInstrumentation
-grpc-1.x=org.apache.skywalking.apm.plugin.grpc.v1.define.ClientCallsInstrumentation
\ No newline at end of file
+# lists your version here (Contains only the last version number of each minor
version.)
+1.6.0