diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml new file mode 100644 index 000000000..e5da82123 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/pom.xml @@ -0,0 +1,44 @@ + + + + + + apm-sdk-plugin + org.apache.skywalking + 6.1.0-SNAPSHOT + + 4.0.0 + + dubbo-2.7.x-conflict-patch + + apm-dubbo-2.7.x-conflict-path + + UTF-8 + 2.7.0 + + + + + org.apache.dubbo + dubbo + ${dubbo.version} + provided + + + + diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/patch/MakeWrapperInterceptor.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/patch/MakeWrapperInterceptor.java new file mode 100644 index 000000000..7e8b5e997 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/patch/MakeWrapperInterceptor.java @@ -0,0 +1,272 @@ +/* + * 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.asf.dubbo.patch; + +import org.apache.dubbo.common.bytecode.ClassGenerator; +import org.apache.dubbo.common.bytecode.NoSuchPropertyException; +import org.apache.dubbo.common.bytecode.Wrapper; +import org.apache.dubbo.common.utils.ClassHelper; +import org.apache.dubbo.common.utils.ReflectUtils; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Matcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor; + +public class MakeWrapperInterceptor implements StaticMethodsAroundInterceptor { + + private static final AtomicLong WRAPPER_CLASS_COUNTER = new AtomicLong(0); + + @Override + public void beforeMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, + MethodInterceptResult result) { + Class wrapperClass = (Class)allArguments[0]; + if (EnhancedInstance.class.isAssignableFrom(wrapperClass)) { + result.defineReturnValue(makeWrapper(wrapperClass)); + } + } + + @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) { + } + + private static Wrapper makeWrapper(Class c) { + if (c.isPrimitive()) + throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c); + + String name = c.getName(); + ClassLoader cl = ClassHelper.getClassLoader(c); + + StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ "); + StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ "); + StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ "); + + c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); + c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); + c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); + + Map> pts = new HashMap>(); // + Map ms = new LinkedHashMap(); // + List mns = new ArrayList(); // method names. + List dmns = new ArrayList(); // declaring method names. + + // get all public field. + for (Field f : c.getFields()) { + String fn = f.getName(); + Class ft = f.getType(); + if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) + continue; + + c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }"); + c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }"); + pts.put(fn, ft); + } + + Method[] methods = c.getMethods(); + // get all public method. + boolean hasMethod = hasMethods(methods); + if (hasMethod) { + c3.append(" try{"); + } + for (Method m : methods) { + // ignore Object's method + // ignore EnhanceInstance's method + if (m.getDeclaringClass() == Object.class || "getSkyWalkingDynamicField".equals(m.getName()) || "setSkyWalkingDynamicField".equals(m.getName())) { + continue; + } + + String mn = m.getName(); + c3.append(" if( \"").append(mn).append("\".equals( $2 ) "); + int len = m.getParameterTypes().length; + c3.append(" && ").append(" $3.length == ").append(len); + + boolean override = false; + for (Method m2 : methods) { + if (m != m2 && m.getName().equals(m2.getName())) { + override = true; + break; + } + } + if (override) { + if (len > 0) { + for (int l = 0; l < len; l++) { + c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"") + .append(m.getParameterTypes()[l].getName()).append("\")"); + } + } + } + + c3.append(" ) { "); + + if (m.getReturnType() == Void.TYPE) + c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;"); + else + c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");"); + + c3.append(" }"); + + mns.add(mn); + if (m.getDeclaringClass() == c) + dmns.add(mn); + ms.put(ReflectUtils.getDesc(m), m); + } + if (hasMethod) { + c3.append(" } catch(Throwable e) { "); + c3.append(" throw new java.lang.reflect.InvocationTargetException(e); "); + c3.append(" }"); + } + + c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }"); + + // deal with get/set method. + Matcher matcher; + for (Map.Entry entry : ms.entrySet()) { + String md = entry.getKey(); + Method method = (Method)entry.getValue(); + if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { + String pn = propertyName(matcher.group(1)); + c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }"); + pts.put(pn, method.getReturnType()); + } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) { + String pn = propertyName(matcher.group(1)); + c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }"); + pts.put(pn, method.getReturnType()); + } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { + Class pt = method.getParameterTypes()[0]; + String pn = propertyName(matcher.group(1)); + c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }"); + pts.put(pn, pt); + } + } + c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); + c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); + + // make class + long id = WRAPPER_CLASS_COUNTER.getAndIncrement(); + ClassGenerator cc = ClassGenerator.newInstance(cl); + cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() + "_swEnhance" : c.getName() + "$sw$_swEnhance") + id); + cc.setSuperClass(Wrapper.class); + + cc.addDefaultConstructor(); + cc.addField("public static String[] pns;"); // property name array. + cc.addField("public static " + Map.class.getName() + " pts;"); // property type map. + cc.addField("public static String[] mns;"); // all method name array. + cc.addField("public static String[] dmns;"); // declared method name array. + for (int i = 0, len = ms.size(); i < len; i++) + cc.addField("public static Class[] mts" + i + ";"); + + cc.addMethod("public String[] getPropertyNames(){ return pns; }"); + cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }"); + cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }"); + cc.addMethod("public String[] getMethodNames(){ return mns; }"); + cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }"); + cc.addMethod(c1.toString()); + cc.addMethod(c2.toString()); + cc.addMethod(c3.toString()); + + try { + Class wc = cc.toClass(); + // setup static field. + wc.getField("pts").set(null, pts); + wc.getField("pns").set(null, pts.keySet().toArray(new String[0])); + wc.getField("mns").set(null, mns.toArray(new String[0])); + wc.getField("dmns").set(null, dmns.toArray(new String[0])); + int ix = 0; + for (Method m : ms.values()) + wc.getField("mts" + ix++).set(null, m.getParameterTypes()); + return (Wrapper)wc.newInstance(); + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + cc.release(); + ms.clear(); + mns.clear(); + dmns.clear(); + } + } + + private static String args(Class[] cs, String name) { + int len = cs.length; + if (len == 0) + return ""; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + if (i > 0) + sb.append(','); + sb.append(arg(cs[i], name + "[" + i + "]")); + } + return sb.toString(); + } + + private static String propertyName(String pn) { + return pn.length() == 1 || Character.isLowerCase(pn.charAt(1)) ? Character.toLowerCase(pn.charAt(0)) + pn.substring(1) : pn; + } + + private static boolean hasMethods(Method[] methods) { + if (methods == null || methods.length == 0) { + return false; + } + for (Method m : methods) { + if (m.getDeclaringClass() != Object.class) { + return true; + } + } + return false; + } + + private static String arg(Class cl, String name) { + if (cl.isPrimitive()) { + if (cl == Boolean.TYPE) + return "((Boolean)" + name + ").booleanValue()"; + if (cl == Byte.TYPE) + return "((Byte)" + name + ").byteValue()"; + if (cl == Character.TYPE) + return "((Character)" + name + ").charValue()"; + if (cl == Double.TYPE) + return "((Number)" + name + ").doubleValue()"; + if (cl == Float.TYPE) + return "((Number)" + name + ").floatValue()"; + if (cl == Integer.TYPE) + return "((Number)" + name + ").intValue()"; + if (cl == Long.TYPE) + return "((Number)" + name + ").longValue()"; + if (cl == Short.TYPE) + return "((Number)" + name + ").shortValue()"; + throw new RuntimeException("Unknown primitive type: " + cl.getName()); + } + return "(" + ReflectUtils.getName(cl) + ")" + name; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/patch/WrapperInstrumentation.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/patch/WrapperInstrumentation.java new file mode 100644 index 000000000..09af65ea2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/patch/WrapperInstrumentation.java @@ -0,0 +1,59 @@ +/* + * 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.asf.dubbo.patch; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +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 static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * The dubbo conflict plugin resolver the problem about the wrapper class generated by Dubbo core cannot be compiled successfully. As we known, + * The wrapper class traverses all the methods. In usual, it works unless this + * class has been enhanced by Skywalking. The javasist cannot found the `EnhanceInstance` method when generate. + * + * The plugin excludes {@link org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance} + * methods to ensure the correct compilation of the code. + */ +public class WrapperInstrumentation extends ClassStaticMethodsEnhancePluginDefine { + @Override protected StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() { + return new StaticMethodsInterceptPoint[] { + new StaticMethodsInterceptPoint() { + @Override public ElementMatcher getMethodsMatcher() { + return named("makeWrapper"); + } + + @Override public String getMethodsInterceptor() { + return "org.apache.skywalking.apm.plugin.dubbo.patch.MakeWrapperInterceptor"; + } + + @Override public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override protected ClassMatch enhanceClass() { + return byName("org.apache.dubbo.common.bytecode.Wrapper"); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..d26e43aa2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-conflict-patch/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# 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. + +dubbo=org.apache.skywalking.apm.plugin.asf.dubbo.patch.WrapperInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml new file mode 100644 index 000000000..d9d30c7eb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/pom.xml @@ -0,0 +1,46 @@ + + + + + apm-sdk-plugin + org.apache.skywalking + 6.1.0-SNAPSHOT + + 4.0.0 + + apm-dubbo-2.7.x-plugin + jar + + dubbo-2.7.x-plugin + http://maven.apache.org + + + UTF-8 + 2.7.0 + + + + + org.apache.dubbo + dubbo + ${dubbo.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/DubboInstrumentation.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/DubboInstrumentation.java new file mode 100644 index 000000000..1bde0b44b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/DubboInstrumentation.java @@ -0,0 +1,68 @@ +/* + * 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.asf.dubbo; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +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 org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.apache.dubbo.monitor.support.MonitorFilter"; + + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("invoke"); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/DubboInterceptor.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/DubboInterceptor.java new file mode 100644 index 000000000..616717999 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asf/dubbo/DubboInterceptor.java @@ -0,0 +1,155 @@ +/* + * 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.asf.dubbo; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import java.lang.reflect.Method; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +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.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +/** + * {@link DubboInterceptor} define how to enhance class {@link org.apache.dubbo.monitor.support.MonitorFilter#invoke(Invoker, + * Invocation)}. the trace context transport to the provider side by {@link RpcContext#attachments}.but all the version + * of dubbo framework below 2.8.3 don't support {@link RpcContext#attachments}, we support another way to support it. + * + */ +public class DubboInterceptor implements InstanceMethodsAroundInterceptor { + /** + *

Consumer:

The serialized trace context data will + * inject to the {@link RpcContext#attachments} for transport to provider side. + *

+ *

Provider:

The serialized trace context data will extract from + * {@link RpcContext#attachments}. current trace segment will ref if the serialize context data is not null. + */ + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + Invoker invoker = (Invoker)allArguments[0]; + Invocation invocation = (Invocation)allArguments[1]; + RpcContext rpcContext = RpcContext.getContext(); + boolean isConsumer = rpcContext.isConsumerSide(); + URL requestURL = invoker.getUrl(); + + AbstractSpan span; + + final String host = requestURL.getHost(); + final int port = requestURL.getPort(); + if (isConsumer) { + final ContextCarrier contextCarrier = new ContextCarrier(); + span = ContextManager.createExitSpan(generateOperationName(requestURL, invocation), contextCarrier, host + ":" + port); + //invocation.getAttachments().put("contextData", contextDataStr); + //@see https://github.com/alibaba/dubbo/blob/dubbo-2.5.3/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/RpcInvocation.java#L154-L161 + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + rpcContext.getAttachments().put(next.getHeadKey(), next.getHeadValue()); + } + } else { + ContextCarrier contextCarrier = new ContextCarrier(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(rpcContext.getAttachment(next.getHeadKey())); + } + + span = ContextManager.createEntrySpan(generateOperationName(requestURL, invocation), contextCarrier); + } + + Tags.URL.set(span, generateRequestURL(requestURL, invocation)); + span.setComponent(ComponentsDefine.DUBBO); + SpanLayer.asRPCFramework(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret) throws Throwable { + Result result = (Result)ret; + if (result != null && result.getException() != null) { + dealException(result.getException()); + } + + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + dealException(t); + } + + /** + * Log the throwable, which occurs in Dubbo RPC service. + */ + private void dealException(Throwable throwable) { + AbstractSpan span = ContextManager.activeSpan(); + span.errorOccurred(); + span.log(throwable); + } + + /** + * Format operation name. e.g. org.apache.skywalking.apm.plugin.test.Test.test(String) + * + * @return operation name. + */ + private String generateOperationName(URL requestURL, Invocation invocation) { + StringBuilder operationName = new StringBuilder(); + operationName.append(requestURL.getPath()); + operationName.append("." + invocation.getMethodName() + "("); + for (Class classes : invocation.getParameterTypes()) { + operationName.append(classes.getSimpleName() + ","); + } + + if (invocation.getParameterTypes().length > 0) { + operationName.delete(operationName.length() - 1, operationName.length()); + } + + operationName.append(")"); + + return operationName.toString(); + } + + /** + * Format request url. + * e.g. dubbo://127.0.0.1:20880/org.apache.skywalking.apm.plugin.test.Test.test(String). + * + * @return request url. + */ + private String generateRequestURL(URL url, Invocation invocation) { + StringBuilder requestURL = new StringBuilder(); + requestURL.append(url.getProtocol() + "://"); + requestURL.append(url.getHost()); + requestURL.append(":" + url.getPort() + "/"); + requestURL.append(generateOperationName(url, invocation)); + return requestURL.toString(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..cd31eec95 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# 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. + +dubbo=org.apache.skywalking.apm.plugin.asf.dubbo.DubboInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/ContextManagerExtendOverrideService.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/ContextManagerExtendOverrideService.java new file mode 100644 index 000000000..9a2cacc2e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/ContextManagerExtendOverrideService.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.plugin.dubbo; + +import org.apache.skywalking.apm.agent.core.boot.OverrideImplementor; +import org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService; + +/** + * @author wusheng + */ +@OverrideImplementor(ContextManagerExtendService.class) +public class ContextManagerExtendOverrideService extends ContextManagerExtendService { +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/DubboInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/DubboInterceptorTest.java new file mode 100644 index 000000000..f31ed7316 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/DubboInterceptorTest.java @@ -0,0 +1,225 @@ +/* + * 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.dubbo; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import java.util.List; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService; +import org.apache.skywalking.apm.agent.core.context.SW3CarrierItem; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegmentRef; +import org.apache.skywalking.apm.agent.core.context.util.TagValuePair; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.helper.SegmentRefHelper; +import org.apache.skywalking.apm.agent.test.helper.SpanHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor; +import org.hamcrest.CoreMatchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.springframework.util.Assert; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +@PrepareForTest({RpcContext.class}) +public class DubboInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + + @Mock + private EnhancedInstance enhancedInstance; + + private DubboInterceptor dubboInterceptor; + + @Mock + private RpcContext rpcContext; + @Mock + private Invoker invoker; + @Mock + private Invocation invocation; + @Mock + private MethodInterceptResult methodInterceptResult; + @Mock + private Result result; + + private Object[] allArguments; + private Class[] argumentTypes; + + @Before + public void setUp() throws Exception { + Config.Agent.ACTIVE_V1_HEADER = true; + dubboInterceptor = new DubboInterceptor(); + + PowerMockito.mockStatic(RpcContext.class); + + when(invoker.getUrl()).thenReturn(URL.valueOf("dubbo://127.0.0.1:20880/org.apache.skywalking.apm.test.TestDubboService")); + when(invocation.getMethodName()).thenReturn("test"); + when(invocation.getParameterTypes()).thenReturn(new Class[] {String.class}); + when(invocation.getArguments()).thenReturn(new Object[] {"abc"}); + PowerMockito.when(RpcContext.getContext()).thenReturn(rpcContext); + when(rpcContext.isConsumerSide()).thenReturn(true); + allArguments = new Object[] {invoker, invocation}; + argumentTypes = new Class[] {invoker.getClass(), invocation.getClass()}; + Config.Agent.SERVICE_NAME = "DubboTestCases-APP"; + } + + @After + public void clear() { + Config.Agent.ACTIVE_V1_HEADER = false; + } + + @Test + public void testServiceFromPlugin() { + PluginBootService service = ServiceManager.INSTANCE.findService(PluginBootService.class); + + Assert.notNull(service); + } + + @Test + public void testServiceOverrideFromPlugin() { + ContextManagerExtendService service = ServiceManager.INSTANCE.findService(ContextManagerExtendService.class); + + Assert.isInstanceOf(ContextManagerExtendOverrideService.class, service); + } + + @Test + public void testConsumerWithAttachment() throws Throwable { + dubboInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, methodInterceptResult); + dubboInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentTypes, result); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertThat(spans.size(), is(1)); + assertConsumerSpan(spans.get(0)); + } + + @Test + public void testConsumerWithException() throws Throwable { + dubboInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, methodInterceptResult); + dubboInterceptor.handleMethodException(enhancedInstance, null, allArguments, argumentTypes, new RuntimeException()); + dubboInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentTypes, result); + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + assertConsumerTraceSegmentInErrorCase(traceSegment); + } + + @Test + public void testConsumerWithResultHasException() throws Throwable { + when(result.getException()).thenReturn(new RuntimeException()); + + dubboInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, methodInterceptResult); + dubboInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentTypes, result); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + assertConsumerTraceSegmentInErrorCase(traceSegment); + } + + @Test + public void testProviderWithAttachment() throws Throwable { + when(rpcContext.isConsumerSide()).thenReturn(false); + when(rpcContext.getAttachment(SW3CarrierItem.HEADER_NAME)).thenReturn("1.323.4433|3|1|1|#192.168.1.8 :18002|#/portal/|#/testEntrySpan|#AQA*#AQA*Et0We0tQNQA*"); + + dubboInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, methodInterceptResult); + dubboInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentTypes, result); + assertProvider(); + } + + private void assertConsumerTraceSegmentInErrorCase( + TraceSegment traceSegment) { + List spans = SegmentHelper.getSpans(traceSegment); + assertThat(spans.size(), is(1)); + assertConsumerSpan(spans.get(0)); + AbstractTracingSpan span = spans.get(0); + assertThat(SpanHelper.getLogs(span).size(), is(1)); + assertErrorLog(SpanHelper.getLogs(span).get(0)); + } + + private void assertErrorLog(LogDataEntity logData) { + assertThat(logData.getLogs().size(), is(4)); + assertThat(logData.getLogs().get(0).getValue(), CoreMatchers.is("error")); + assertThat(logData.getLogs().get(1).getValue(), CoreMatchers.is(RuntimeException.class.getName())); + assertNull(logData.getLogs().get(2).getValue()); + } + + private void assertProvider() { + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + assertThat(SegmentHelper.getSpans(traceSegment).size(), is(1)); + assertProviderSpan(SegmentHelper.getSpans(traceSegment).get(0)); + assertTraceSegmentRef(traceSegment.getRefs().get(0)); + } + + private void assertTraceSegmentRef(TraceSegmentRef actual) { + assertThat(SegmentRefHelper.getSpanId(actual), is(3)); + assertThat(SegmentRefHelper.getEntryServiceInstanceId(actual), is(1)); + assertThat(SegmentRefHelper.getTraceSegmentId(actual).toString(), is("1.323.4433")); + } + + private void assertProviderSpan(AbstractTracingSpan span) { + assertCommonsAttribute(span); + assertTrue(span.isEntry()); + } + + private void assertConsumerSpan(AbstractTracingSpan span) { + assertCommonsAttribute(span); + assertTrue(span.isExit()); + } + + private void assertCommonsAttribute(AbstractTracingSpan span) { + List tags = SpanHelper.getTags(span); + assertThat(tags.size(), is(1)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.RPC_FRAMEWORK)); + assertThat(SpanHelper.getComponentId(span), is(3)); + assertThat(tags.get(0).getValue(), is("dubbo://127.0.0.1:20880/org.apache.skywalking.apm.test.TestDubboService.test(String)")); + assertThat(span.getOperationName(), is("org.apache.skywalking.apm.test.TestDubboService.test(String)")); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/PluginBootService.java b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/PluginBootService.java new file mode 100644 index 000000000..b91e137c3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/dubbo/PluginBootService.java @@ -0,0 +1,42 @@ +/* + * 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.dubbo; + +import org.apache.skywalking.apm.agent.core.boot.BootService; + +/** + * @author wusheng + */ +public class PluginBootService implements BootService { + @Override public void prepare() throws Throwable { + + } + + @Override public void boot() throws Throwable { + + } + + @Override public void onComplete() throws Throwable { + + } + + @Override public void shutdown() throws Throwable { + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService new file mode 100644 index 000000000..ccf706712 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/dubbo-2.7.x-plugin/src/test/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService @@ -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. +# +# + +org.apache.skywalking.apm.plugin.dubbo.PluginBootService +org.apache.skywalking.apm.plugin.dubbo.ContextManagerExtendOverrideService diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 63e97a71e..b2f002381 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -67,6 +67,8 @@ rabbitmq-5.x-plugin dubbo-conflict-patch canal-1.x-plugin + dubbo-2.7.x-plugin + dubbo-2.7.x-conflict-patch pom