gRPC plugin support trace client generic call (#168)

This commit is contained in:
hutaishi 2022-05-05 11:07:49 +08:00 committed by GitHub
parent edc69ed847
commit d45818911d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2242 additions and 1 deletions

View File

@ -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:

View File

@ -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

View File

@ -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";
}

View File

@ -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();
}
}
}
}

View File

@ -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) {
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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];
}
}

View File

@ -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];
}
}

View File

@ -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
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

View File

@ -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: []

View File

@ -0,0 +1,22 @@
# 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.
type: jvm
entryService: http://localhost:8888/case/generic-call
healthCheck: http://localhost:8888/case/healthCheck
startScript: ./bin/startup.sh
environment:
dependencies:

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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

View File

@ -0,0 +1,27 @@
#!/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
# 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.
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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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";
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -0,0 +1,18 @@
# 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.
# lists your version here (Contains only the last version number of each minor version.)
1.6.0