diff --git a/.gitignore b/.gitignore
index 1a468655b..bdc57edb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ target/
.DS_Store
*~
packages/
+**/dependency-reduced-pom.xml
diff --git a/README.md b/README.md
index 15037a4f1..09eb5e25c 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDU
- JVM Detail.
-
+
- Services Dependency Tree.
diff --git a/README_ZH.md b/README_ZH.md
index 12edcffdb..f86fcd3cc 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -60,7 +60,7 @@ This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDU
- JVM明细信息
-
+
- 服务依赖树.
diff --git a/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java
index b37078faa..c635c2d2e 100644
--- a/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java
+++ b/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java
@@ -43,8 +43,10 @@ public class ComponentsDefine {
public static final OfficialComponent JETTY_CLIENT = new OfficialComponent(18, "JettyClient");
- public static final OfficialComponent JETTY_SERVER = new OfficialComponent(19, "JettyServer");
-
+ public static final OfficialComponent JETTY_SERVER = new OfficialComponent(19, "JettyServer");
+
+ public static final OfficialComponent MEMCACHE = new OfficialComponent(20, "Memcache");
+
private static ComponentsDefine instance = new ComponentsDefine();
private String[] components;
@@ -54,7 +56,7 @@ public class ComponentsDefine {
}
public ComponentsDefine() {
- components = new String[20];
+ components = new String[21];
addComponent(TOMCAT);
addComponent(HTTPCLIENT);
addComponent(DUBBO);
@@ -74,6 +76,7 @@ public class ComponentsDefine {
addComponent(NUTZ_HTTP);
addComponent(JETTY_CLIENT);
addComponent(JETTY_SERVER);
+ addComponent(MEMCACHE);
}
private void addComponent(OfficialComponent component) {
@@ -87,4 +90,4 @@ public class ComponentsDefine {
return components[componentId];
}
}
-}
+}
\ No newline at end of file
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java
index 87bfae3ed..131ddfb86 100644
--- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java
@@ -26,7 +26,7 @@ public abstract class AbstractClassEnhancePluginDefine {
* @throws PluginException, when set builder failure.
*/
public DynamicType.Builder> define(String transformClassName,
- DynamicType.Builder> builder, ClassLoader classLoader) throws PluginException {
+ DynamicType.Builder> builder, ClassLoader classLoader, EnhanceContext context) throws PluginException {
String interceptorDefineClassName = this.getClass().getName();
if (StringUtil.isEmpty(transformClassName)) {
@@ -53,15 +53,16 @@ public abstract class AbstractClassEnhancePluginDefine {
/**
* find origin class source code for interceptor
*/
- DynamicType.Builder> newClassBuilder = this.enhance(transformClassName, builder, classLoader);
+ DynamicType.Builder> newClassBuilder = this.enhance(transformClassName, builder, classLoader, context);
+ context.initializationStageCompleted();
logger.debug("enhance class {} by {} completely.", transformClassName, interceptorDefineClassName);
return newClassBuilder;
}
protected abstract DynamicType.Builder> enhance(String enhanceOriginClassName,
- DynamicType.Builder> newClassBuilder, ClassLoader classLoader) throws PluginException;
+ DynamicType.Builder> newClassBuilder, ClassLoader classLoader, EnhanceContext context) throws PluginException;
/**
* Define the {@link ClassMatch} for filtering class.
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/EnhanceContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/EnhanceContext.java
new file mode 100644
index 000000000..e3417c38d
--- /dev/null
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/EnhanceContext.java
@@ -0,0 +1,34 @@
+package org.skywalking.apm.agent.core.plugin;
+
+/**
+ * The EnhanceContext represents the context or status for processing a class.
+ *
+ * Based on this context, the plugin core {@link org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine}
+ * knows how to process the specific steps for every particular plugin.
+ *
+ * @author wusheng
+ */
+public class EnhanceContext {
+ private boolean isEnhanced = false;
+ /**
+ * The object has already been enhanced or extended.
+ * e.g. added the new field, or implemented the new interface
+ */
+ private boolean objectExtended = false;
+
+ public boolean isEnhanced() {
+ return isEnhanced;
+ }
+
+ public void initializationStageCompleted() {
+ isEnhanced = true;
+ }
+
+ public boolean isObjectExtended() {
+ return objectExtended;
+ }
+
+ public void extendObjectCompleted() {
+ objectExtended = true;
+ }
+}
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java
index 271b32cee..96e90fee6 100644
--- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java
@@ -22,7 +22,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
* @author wusheng
*/
public class PluginFinder {
- private final Map nameMatchDefine = new HashMap();
+ private final Map> nameMatchDefine = new HashMap>();
private final List signatureMatchDefine = new LinkedList();
public PluginFinder(List plugins) {
@@ -35,28 +35,34 @@ public class PluginFinder {
if (match instanceof NameMatch) {
NameMatch nameMatch = (NameMatch)match;
- nameMatchDefine.put(nameMatch.getClassName(), plugin);
+ LinkedList pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
+ if (pluginDefines == null) {
+ pluginDefines = new LinkedList();
+ nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
+ }
+ pluginDefines.add(plugin);
} else {
signatureMatchDefine.add(plugin);
}
}
}
- public AbstractClassEnhancePluginDefine find(TypeDescription typeDescription,
+ public List find(TypeDescription typeDescription,
ClassLoader classLoader) {
+ List matchedPlugins = new LinkedList();
String typeName = typeDescription.getTypeName();
if (nameMatchDefine.containsKey(typeName)) {
- return nameMatchDefine.get(typeName);
+ matchedPlugins.addAll(nameMatchDefine.get(typeName));
}
for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {
IndirectMatch match = (IndirectMatch)pluginDefine.enhanceClass();
if (match.isMatch(typeDescription)) {
- return pluginDefine;
+ matchedPlugins.add(pluginDefine);
}
}
- return null;
+ return matchedPlugins;
}
public ElementMatcher super TypeDescription> buildMatch() {
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java
index 3dc7075cb..b31e2c3b2 100644
--- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java
@@ -6,6 +6,7 @@ import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.implementation.bind.annotation.Morph;
import org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
+import org.skywalking.apm.agent.core.plugin.EnhanceContext;
import org.skywalking.apm.agent.core.plugin.PluginException;
import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.skywalking.apm.agent.core.plugin.interceptor.EnhanceException;
@@ -46,10 +47,11 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
*/
@Override
protected DynamicType.Builder> enhance(String enhanceOriginClassName,
- DynamicType.Builder> newClassBuilder, ClassLoader classLoader) throws PluginException {
+ DynamicType.Builder> newClassBuilder, ClassLoader classLoader,
+ EnhanceContext context) throws PluginException {
newClassBuilder = this.enhanceClass(enhanceOriginClassName, newClassBuilder, classLoader);
- newClassBuilder = this.enhanceInstance(enhanceOriginClassName, newClassBuilder, classLoader);
+ newClassBuilder = this.enhanceInstance(enhanceOriginClassName, newClassBuilder, classLoader, context);
return newClassBuilder;
}
@@ -62,7 +64,8 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
* @return new byte-buddy's builder for further manipulation.
*/
private DynamicType.Builder> enhanceInstance(String enhanceOriginClassName,
- DynamicType.Builder> newClassBuilder, ClassLoader classLoader) throws PluginException {
+ DynamicType.Builder> newClassBuilder, ClassLoader classLoader,
+ EnhanceContext context) throws PluginException {
ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints();
InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints();
@@ -83,16 +86,21 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
}
/**
- * alter class source code.
+ * Manipulate class source code.
*
* new class need:
* 1.Add field, name {@link #CONTEXT_ATTR_NAME}.
* 2.Add a field accessor for this field.
*
+ * And make sure the source codes manipulation only occurs once.
+ *
*/
- newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE)
- .implement(EnhancedInstance.class)
- .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));
+ if (!context.isObjectExtended()) {
+ newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE)
+ .implement(EnhancedInstance.class)
+ .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));
+ context.extendObjectCompleted();
+ }
/**
* 2. enhance constructors
diff --git a/apm-sniffer/apm-agent/pom.xml b/apm-sniffer/apm-agent/pom.xml
index de97031ce..064e0917c 100644
--- a/apm-sniffer/apm-agent/pom.xml
+++ b/apm-sniffer/apm-agent/pom.xml
@@ -125,6 +125,11 @@
apm-jetty-server-9.x-plugin
${project.version}
+
+ org.skywalking
+ apm-spymemcached-2.x-plugin
+ ${project.version}
+
diff --git a/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java b/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java
index 1edf9f841..0d8a51d62 100644
--- a/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java
+++ b/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java
@@ -1,6 +1,7 @@
package org.skywalking.apm.agent;
import java.lang.instrument.Instrumentation;
+import java.util.List;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
@@ -9,6 +10,7 @@ import org.skywalking.apm.agent.core.boot.ServiceManager;
import org.skywalking.apm.agent.core.conf.SnifferConfigInitializer;
import org.skywalking.apm.agent.core.logging.EasyLogResolver;
import org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
+import org.skywalking.apm.agent.core.plugin.EnhanceContext;
import org.skywalking.apm.agent.core.plugin.PluginBootstrap;
import org.skywalking.apm.agent.core.plugin.PluginException;
import org.skywalking.apm.agent.core.plugin.PluginFinder;
@@ -54,13 +56,21 @@ public class SkyWalkingAgent {
@Override
public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
- AbstractClassEnhancePluginDefine pluginDefine = pluginFinder.find(typeDescription, classLoader);
- if (pluginDefine != null) {
- DynamicType.Builder> newBuilder = pluginDefine.define(typeDescription.getTypeName(), builder, classLoader);
- if (newBuilder != null) {
- logger.debug("Finish the prepare stage for {}.", typeDescription.getName());
- return newBuilder;
+ List pluginDefines = pluginFinder.find(typeDescription, classLoader);
+ if (pluginDefines.size() > 0) {
+ DynamicType.Builder> newBuilder = builder;
+ EnhanceContext context = new EnhanceContext();
+ for (AbstractClassEnhancePluginDefine define : pluginDefines) {
+ DynamicType.Builder> possibleNewBuilder = define.define(typeDescription.getTypeName(), newBuilder, classLoader, context);
+ if (possibleNewBuilder != null) {
+ newBuilder = possibleNewBuilder;
+ }
}
+ if (context.isEnhanced()) {
+ logger.debug("Finish the prepare stage for {}.", typeDescription.getName());
+ }
+
+ return newBuilder;
}
logger.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName());
diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml
index 976ca663c..145822656 100644
--- a/apm-sniffer/apm-sdk-plugin/pom.xml
+++ b/apm-sniffer/apm-sdk-plugin/pom.xml
@@ -26,6 +26,7 @@
struts2-2.x-plugin
nutz-plugins
jetty-plugin
+ spymemcached-2.x-plugin
pom
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/ControllerServiceMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/AbstractMethodInteceptor.java
similarity index 73%
rename from apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/ControllerServiceMethodInterceptor.java
rename to apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/AbstractMethodInteceptor.java
index 08967f935..a96f94a78 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/ControllerServiceMethodInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/AbstractMethodInteceptor.java
@@ -1,8 +1,5 @@
package org.skywalking.apm.plugin.spring.mvc;
-import java.lang.reflect.Method;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
import org.skywalking.apm.agent.core.context.CarrierItem;
import org.skywalking.apm.agent.core.context.ContextCarrier;
import org.skywalking.apm.agent.core.context.ContextManager;
@@ -13,33 +10,30 @@ import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance
import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.skywalking.apm.network.trace.component.ComponentsDefine;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
/**
- * The ControllerServiceMethodInterceptor only use the first mapping value.
+ * the abstract method inteceptor
*/
-public class ControllerServiceMethodInterceptor implements InstanceMethodsAroundInterceptor {
+public abstract class AbstractMethodInteceptor implements InstanceMethodsAroundInterceptor {
+ public abstract String getRequestURL(Method method);
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
- MethodInterceptResult result) throws Throwable {
+ MethodInterceptResult result) throws Throwable {
PathMappingCache pathMappingCache = (PathMappingCache)objInst.getSkyWalkingDynamicField();
String requestURL = pathMappingCache.findPathMapping(method);
if (requestURL == null) {
- RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
- if (methodRequestMapping.value().length > 0) {
- requestURL = methodRequestMapping.value()[0];
- } else if (methodRequestMapping.path().length > 0) {
- requestURL = methodRequestMapping.path()[0];
- } else {
- requestURL = "";
- }
+ requestURL = getRequestURL(method);
pathMappingCache.addPathMapping(method, requestURL);
requestURL = pathMappingCache.findPathMapping(method);
}
- HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
+ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ContextCarrier contextCarrier = new ContextCarrier();
CarrierItem next = contextCarrier.items();
@@ -57,7 +51,7 @@ public class ControllerServiceMethodInterceptor implements InstanceMethodsAround
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
- Object ret) throws Throwable {
+ Object ret) throws Throwable {
HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
AbstractSpan span = ContextManager.activeSpan();
@@ -69,8 +63,9 @@ public class ControllerServiceMethodInterceptor implements InstanceMethodsAround
return ret;
}
- @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
- Class>[] argumentsTypes, Throwable t) {
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
ContextManager.activeSpan().errorOccurred().log(t);
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptor.java
new file mode 100644
index 000000000..b04942cb1
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptor.java
@@ -0,0 +1,24 @@
+package org.skywalking.apm.plugin.spring.mvc;
+
+import java.lang.reflect.Method;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * The RequestMappingMethodInterceptor only use the first mapping value.
+ * it will inteceptor with @RequestMapping
+ * @author clevertension
+ */
+public class RequestMappingMethodInterceptor extends AbstractMethodInteceptor {
+ @Override
+ public String getRequestURL(Method method) {
+ String requestURL = "";
+ RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
+ if (methodRequestMapping.value().length > 0) {
+ requestURL = methodRequestMapping.value()[0];
+ } else if (methodRequestMapping.path().length > 0) {
+ requestURL = methodRequestMapping.path()[0];
+ }
+ return requestURL;
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptor.java
new file mode 100644
index 000000000..ee69bde38
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptor.java
@@ -0,0 +1,56 @@
+package org.skywalking.apm.plugin.spring.mvc;
+
+import java.lang.reflect.Method;
+
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * The RestMappingMethodInterceptor only use the first mapping value.
+ * it will inteceptor with
+ * @GetMapping, @PostMapping, @PutMapping
+ * @DeleteMapping, @PatchMapping
+ * @author clevertension
+ */
+public class RestMappingMethodInterceptor extends AbstractMethodInteceptor {
+ @Override
+ public String getRequestURL(Method method) {
+ String requestURL = "";
+ GetMapping getMapping = method.getAnnotation(GetMapping.class);
+ PostMapping postMapping = method.getAnnotation(PostMapping.class);
+ PutMapping putMapping = method.getAnnotation(PutMapping.class);
+ DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
+ PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
+ if (getMapping != null) {
+ if (getMapping.value().length > 0) {
+ requestURL = getMapping.value()[0];
+ } else if (getMapping.path().length > 0) {
+ requestURL = getMapping.path()[0];
+ }
+ } else if (postMapping != null) {
+ if (postMapping.value().length > 0) {
+ requestURL = postMapping.value()[0];
+ } else if (postMapping.path().length > 0) {
+ requestURL = postMapping.path()[0];
+ }
+ } else if (putMapping != null) {
+ if (putMapping.value().length > 0) {
+ requestURL = putMapping.value()[0];
+ } else if (putMapping.path().length > 0) {
+ requestURL = putMapping.path()[0];
+ }
+ } else if (deleteMapping != null) {
+ if (deleteMapping.value().length > 0) {
+ requestURL = deleteMapping.value()[0];
+ } else if (deleteMapping.path().length > 0) {
+ requestURL = deleteMapping.path()[0];
+ }
+ } else if (patchMapping != null) {
+ if (patchMapping.value().length > 0) {
+ requestURL = patchMapping.value()[0];
+ } else if (patchMapping.path().length > 0) {
+ requestURL = patchMapping.path()[0];
+ }
+ }
+ return requestURL;
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java
index 96f7715d7..feb6323a2 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java
@@ -20,8 +20,8 @@ import static org.skywalking.apm.agent.core.plugin.match.ClassAnnotationMatch.by
* org.skywalking.apm.plugin.spring.mvc.ControllerConstructorInterceptor set the controller base path to
* dynamic field before execute constructor.
*
- * org.skywalking.apm.plugin.spring.mvc.ControllerServiceMethodInterceptor get the request path from
- * dynamic field first, if not found, ControllerServiceMethodInterceptor generate request path that
+ * org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor get the request path from
+ * dynamic field first, if not found, RequestMappingMethodInterceptor generate request path that
* combine the path value of current annotation on current method and the base path and set the new path to the dynamic
* filed
*
@@ -56,7 +56,26 @@ public abstract class AbstractControllerInstrumentation extends ClassInstanceMet
@Override
public String getMethodsInterceptor() {
- return "org.skywalking.apm.plugin.spring.mvc.ControllerServiceMethodInterceptor";
+ return "org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor";
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ },
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return isAnnotatedWith(named("org.springframework.web.bind.annotation.GetMapping"))
+ .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PostMapping")))
+ .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PutMapping")))
+ .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.DeleteMapping")))
+ .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PatchMapping")));
+ }
+ @Override
+ public String getMethodsInterceptor() {
+ return "org.skywalking.apm.plugin.spring.mvc.RestMappingMethodInterceptor";
}
@Override
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/ControllerConstructorInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/ControllerConstructorInterceptorTest.java
new file mode 100644
index 000000000..365e252b1
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/ControllerConstructorInterceptorTest.java
@@ -0,0 +1,107 @@
+package org.skywalking.apm.plugin.spring.mvc;
+
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.lang.reflect.Method;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class ControllerConstructorInterceptorTest {
+ private ControllerConstructorInterceptor controllerConstructorInterceptor;
+ private final MockEnhancedInstance1 inst1 = new MockEnhancedInstance1();
+ private final MockEnhancedInstance2 inst2 = new MockEnhancedInstance2();
+ private final MockEnhancedInstance3 inst3 = new MockEnhancedInstance3();
+ @Before
+ public void setUp() throws Exception {
+ controllerConstructorInterceptor = new ControllerConstructorInterceptor();
+ }
+
+ @Test
+ public void testOnConstruct_Accuracy1() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(inst1, null);
+ PathMappingCache cache = (PathMappingCache)inst1.getSkyWalkingDynamicField();
+ Assert.assertNotNull(cache);
+
+ Object obj = new Object();
+ Method m = obj.getClass().getMethods()[0];
+ cache.addPathMapping(m, "#toString");
+
+ Assert.assertEquals("the two value should be equal", cache.findPathMapping(m), "/test1#toString");
+ }
+
+ @Test
+ public void testOnConstruct_Accuracy2() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(inst2, null);
+ PathMappingCache cache = (PathMappingCache)inst2.getSkyWalkingDynamicField();
+ Assert.assertNotNull(cache);
+
+ Object obj = new Object();
+ Method m = obj.getClass().getMethods()[0];
+ cache.addPathMapping(m, "#toString");
+
+ Assert.assertEquals("the two value should be equal", cache.findPathMapping(m), "#toString");
+ }
+
+ @Test
+ public void testOnConstruct_Accuracy3() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(inst3, null);
+ PathMappingCache cache = (PathMappingCache)inst3.getSkyWalkingDynamicField();
+ Assert.assertNotNull(cache);
+
+ Object obj = new Object();
+ Method m = obj.getClass().getMethods()[0];
+ cache.addPathMapping(m, "#toString");
+
+ Assert.assertEquals("the two value should be equal", cache.findPathMapping(m), "/test3#toString");
+ }
+
+ @RequestMapping(value="/test1")
+ private class MockEnhancedInstance1 implements EnhancedInstance {
+ private Object value;
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return value;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.value = value;
+ }
+ }
+
+ private class MockEnhancedInstance2 implements EnhancedInstance {
+ private Object value;
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return value;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.value = value;
+ }
+ }
+
+ @RequestMapping(path="/test3")
+ private class MockEnhancedInstance3 implements EnhancedInstance {
+ private Object value;
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return value;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/PathMappingCacheTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/PathMappingCacheTest.java
new file mode 100644
index 000000000..e7dd987a5
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/PathMappingCacheTest.java
@@ -0,0 +1,34 @@
+package org.skywalking.apm.plugin.spring.mvc;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+
+import java.lang.reflect.Method;
+
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class PathMappingCacheTest {
+ private PathMappingCache pathMappingCache;
+
+ @Before
+ public void setUp() throws Exception {
+ pathMappingCache = new PathMappingCache("org.skywalking.apm.plugin.spring.mvc");
+ }
+
+ @Test
+ public void testAddPathMapping1() throws Throwable {
+ Object obj = new Object();
+ Method m = obj.getClass().getMethods()[0];
+ pathMappingCache.addPathMapping(m, "#toString");
+
+ Assert.assertEquals("the two value should be equal", pathMappingCache.findPathMapping(m), "org.skywalking.apm.plugin.spring.mvc#toString");
+
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptorTest.java
new file mode 100644
index 000000000..7fa10b10c
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptorTest.java
@@ -0,0 +1,157 @@
+package org.skywalking.apm.plugin.spring.mvc;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.core.context.trace.*;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.skywalking.apm.agent.test.helper.SegmentRefHelper;
+import org.skywalking.apm.agent.test.helper.SpanHelper;
+import org.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+import static org.skywalking.apm.agent.test.tools.SpanAssert.*;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class RequestMappingMethodInterceptorTest {
+ private RequestMappingMethodInterceptor interceptor;
+
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+
+ private ServletRequestAttributes servletRequestAttributes;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+ @Mock
+ private MethodInterceptResult methodInterceptResult;
+
+ private Object[] arguments;
+ private Class[] argumentType;
+
+ private EnhancedInstance enhancedInstance;
+
+ private ControllerConstructorInterceptor controllerConstructorInterceptor;
+
+ @Before
+ public void setUp() throws Exception {
+ interceptor = new RequestMappingMethodInterceptor();
+ enhancedInstance = new MockEnhancedInstance1();
+ controllerConstructorInterceptor = new ControllerConstructorInterceptor();
+ servletRequestAttributes = new ServletRequestAttributes(request, response);
+
+ when(request.getScheme()).thenReturn("http");
+ when(request.getServerName()).thenReturn("localhost");
+ when(request.getServerPort()).thenReturn(8080);
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/testRequestURL"));
+ when(response.getStatus()).thenReturn(200);
+
+ arguments = new Object[] {request, response};
+ argumentType = new Class[] {request.getClass(), response.getClass()};
+
+
+ }
+
+ @Test
+ public void testWithoutSerializedContextData() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RequestMappingClass1 mappingClass1 = new RequestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("testRequestURL");
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0));
+ }
+
+ @Test
+ public void testWithOccurException() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RequestMappingClass1 mappingClass1 = new RequestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("testRequestURL");
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.handleMethodException(enhancedInstance, m, arguments, argumentType, new RuntimeException());
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0));
+ List logDataEntities = SpanHelper.getLogs(spans.get(0));
+ assertThat(logDataEntities.size(), is(1));
+ assertException(logDataEntities.get(0), RuntimeException.class);
+ }
+
+ private void assertTraceSegmentRef(TraceSegmentRef ref) {
+ assertThat(SegmentRefHelper.getEntryApplicationInstanceId(ref), is(1));
+ assertThat(SegmentRefHelper.getSpanId(ref), is(3));
+ assertThat(SegmentRefHelper.getTraceSegmentId(ref).toString(), is("1.444.555"));
+ }
+
+ private void assertHttpSpan(AbstractTracingSpan span) {
+ assertThat(span.getOperationName(), is("/test/testRequestURL"));
+ assertComponent(span, ComponentsDefine.SPRING_MVC_ANNOTATION);
+ assertTag(span, 0, "http://localhost:8080/test/testRequestURL");
+ assertThat(span.isEntry(), is(true));
+ assertLayer(span, SpanLayer.HTTP);
+ }
+
+ @RequestMapping(value = "/test")
+ private class MockEnhancedInstance1 implements EnhancedInstance {
+ private Object value;
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return value;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.value = value;
+ }
+ }
+
+ private class RequestMappingClass1 {
+ @RequestMapping("/testRequestURL")
+ public void testRequestURL() {
+
+ }
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptorTest.java
new file mode 100644
index 000000000..21ded57d7
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptorTest.java
@@ -0,0 +1,277 @@
+package org.skywalking.apm.plugin.spring.mvc;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.core.context.trace.*;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.skywalking.apm.agent.test.helper.SegmentRefHelper;
+import org.skywalking.apm.agent.test.helper.SpanHelper;
+import org.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+import static org.skywalking.apm.agent.test.tools.SpanAssert.*;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class RestMappingMethodInterceptorTest {
+ private RestMappingMethodInterceptor interceptor;
+
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+ @Mock
+ private MethodInterceptResult methodInterceptResult;
+
+ private Object[] arguments;
+ private Class[] argumentType;
+
+ private EnhancedInstance enhancedInstance;
+
+ private ControllerConstructorInterceptor controllerConstructorInterceptor;
+
+ @Before
+ public void setUp() throws Exception {
+ interceptor = new RestMappingMethodInterceptor();
+ enhancedInstance = new RestMappingMethodInterceptorTest.MockEnhancedInstance1();
+ controllerConstructorInterceptor = new ControllerConstructorInterceptor();
+
+ when(request.getScheme()).thenReturn("http");
+ when(request.getServerName()).thenReturn("localhost");
+ when(request.getServerPort()).thenReturn(8080);
+ when(response.getStatus()).thenReturn(200);
+
+ arguments = new Object[] {request, response};
+ argumentType = new Class[] {request.getClass(), response.getClass()};
+
+
+ }
+
+ @Test
+ public void testGetMapping() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("getRequestURL");
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/getRequestURL"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "/getRequestURL");
+ }
+
+ @Test
+ public void testPostMapping() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("postRequestURL");
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/postRequestURL"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "/postRequestURL");
+ }
+
+
+ @Test
+ public void testPutMapping() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("putRequestURL");
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/putRequestURL"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "/putRequestURL");
+ }
+
+ @Test
+ public void testDeleteMapping() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("deleteRequestURL");
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/deleteRequestURL"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "/deleteRequestURL");
+ }
+
+ @Test
+ public void testPatchMapping() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("patchRequestURL");
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/patchRequestURL"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "/patchRequestURL");
+ }
+
+ @Test
+ public void testDummy() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("dummy");
+ when(request.getRequestURI()).thenReturn("/test");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "");
+ }
+
+ @Test
+ public void testWithOccurException() throws Throwable {
+ controllerConstructorInterceptor.onConstruct(enhancedInstance, null);
+ RestMappingClass1 mappingClass1 = new RestMappingClass1();
+ Method m = mappingClass1.getClass().getMethod("getRequestURL");
+ when(request.getRequestURI()).thenReturn("/test/testRequestURL");
+ when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/getRequestURL"));
+ ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response);
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult);
+ interceptor.handleMethodException(enhancedInstance, m, arguments, argumentType, new RuntimeException());
+ interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null);
+
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+
+ assertHttpSpan(spans.get(0), "/getRequestURL");
+ List logDataEntities = SpanHelper.getLogs(spans.get(0));
+ assertThat(logDataEntities.size(), is(1));
+ assertException(logDataEntities.get(0), RuntimeException.class);
+ }
+
+ private void assertTraceSegmentRef(TraceSegmentRef ref) {
+ assertThat(SegmentRefHelper.getEntryApplicationInstanceId(ref), is(1));
+ assertThat(SegmentRefHelper.getSpanId(ref), is(3));
+ assertThat(SegmentRefHelper.getTraceSegmentId(ref).toString(), is("1.444.555"));
+ }
+
+ private void assertHttpSpan(AbstractTracingSpan span, String suffix) {
+ assertThat(span.getOperationName(), is("/test" + suffix));
+ assertComponent(span, ComponentsDefine.SPRING_MVC_ANNOTATION);
+ assertTag(span, 0, "http://localhost:8080/test" + suffix);
+ assertThat(span.isEntry(), is(true));
+ assertLayer(span, SpanLayer.HTTP);
+ }
+
+ @RequestMapping(value = "/test")
+ private class MockEnhancedInstance1 implements EnhancedInstance {
+ private Object value;
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return value;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.value = value;
+ }
+ }
+
+ private class RestMappingClass1 {
+ @GetMapping("/getRequestURL")
+ public void getRequestURL() {
+
+ }
+ @PostMapping("/postRequestURL")
+ public void postRequestURL() {
+
+ }
+ @PutMapping("/putRequestURL")
+ public void putRequestURL() {
+
+ }
+ @DeleteMapping("/deleteRequestURL")
+ public void deleteRequestURL() {
+
+ }
+ @PatchMapping("/patchRequestURL")
+ public void patchRequestURL() {
+
+ }
+ public void dummy() {
+
+ }
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/ControllerInstrumentationTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/ControllerInstrumentationTest.java
new file mode 100644
index 000000000..198d359c2
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/ControllerInstrumentationTest.java
@@ -0,0 +1,62 @@
+package org.skywalking.apm.plugin.spring.mvc.define;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatchers;
+
+import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class ControllerInstrumentationTest {
+ private ControllerInstrumentation controllerInstrumentation;
+
+ @Before
+ public void setUp() throws Exception {
+ controllerInstrumentation = new ControllerInstrumentation();
+ }
+
+ @Test
+ public void testGetEnhanceAnnotations() throws Throwable {
+ Assert.assertArrayEquals(new String[] {ControllerInstrumentation.ENHANCE_ANNOTATION},
+ controllerInstrumentation.getEnhanceAnnotations());
+ }
+
+ @Test
+ public void testGetInstanceMethodsInterceptPoints() throws Throwable {
+ InstanceMethodsInterceptPoint[] methodPoints = controllerInstrumentation.getInstanceMethodsInterceptPoints();
+ assertThat(methodPoints.length, is(2));
+ assertThat(methodPoints[0].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor"));
+ assertThat(methodPoints[1].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RestMappingMethodInterceptor"));
+
+ Assert.assertFalse(methodPoints[0].isOverrideArgs());
+ Assert.assertFalse(methodPoints[1].isOverrideArgs());
+
+ Assert.assertNotNull(methodPoints[0].getMethodsMatcher());
+ Assert.assertNotNull(methodPoints[1].getMethodsMatcher());
+
+ }
+
+ @Test
+ public void testGetConstructorsInterceptPoints() throws Throwable {
+ ConstructorInterceptPoint[] cips = controllerInstrumentation.getConstructorsInterceptPoints();
+ Assert.assertEquals(cips.length, 1);
+ ConstructorInterceptPoint cip = cips[0];
+ Assert.assertNotNull(cip);
+
+ Assert.assertEquals(cip.getConstructorInterceptor(), "org.skywalking.apm.plugin.spring.mvc.ControllerConstructorInterceptor");
+ Assert.assertTrue(cip.getConstructorMatcher().equals(ElementMatchers.any()));
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/RestControllerInstrumentationTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/RestControllerInstrumentationTest.java
new file mode 100644
index 000000000..f2b8d16df
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/RestControllerInstrumentationTest.java
@@ -0,0 +1,45 @@
+package org.skywalking.apm.plugin.spring.mvc.define;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class RestControllerInstrumentationTest {
+ private RestControllerInstrumentation restControllerInstrumentation;
+
+ @Before
+ public void setUp() throws Exception {
+ restControllerInstrumentation = new RestControllerInstrumentation();
+ }
+
+ @Test
+ public void testGetEnhanceAnnotations() throws Throwable {
+ Assert.assertArrayEquals(new String[] {restControllerInstrumentation.ENHANCE_ANNOTATION},
+ restControllerInstrumentation.getEnhanceAnnotations());
+ }
+
+ @Test
+ public void testGetInstanceMethodsInterceptPoints() throws Throwable {
+ InstanceMethodsInterceptPoint[] methodPoints = restControllerInstrumentation.getInstanceMethodsInterceptPoints();
+ assertThat(methodPoints.length, is(2));
+ assertThat(methodPoints[0].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor"));
+ assertThat(methodPoints[1].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RestMappingMethodInterceptor"));
+
+ Assert.assertFalse(methodPoints[0].isOverrideArgs());
+ Assert.assertFalse(methodPoints[1].isOverrideArgs());
+
+ Assert.assertNotNull(methodPoints[0].getMethodsMatcher());
+ Assert.assertNotNull(methodPoints[1].getMethodsMatcher());
+
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml
new file mode 100644
index 000000000..784547130
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+ org.skywalking
+ apm-sdk-plugin
+ 3.2.3-2017
+
+
+ apm-spymemcached-2.x-plugin
+ spymemcached-2.x-plugin
+ http://maven.apache.org
+
+
+ UTF-8
+ 2.11.1
+
+
+
+
+ net.spy
+ spymemcached
+ ${spymemcached.version}
+ provided
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.4.1
+ test
+
+
+
\ No newline at end of file
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptor.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptor.java
new file mode 100644
index 000000000..f7c9d1716
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptor.java
@@ -0,0 +1,26 @@
+package org.skywalking.apm.plugin.spymemcached.v2;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+
+public class MemcachedConstructorWithInetSocketAddressListArgInterceptor implements InstanceConstructorInterceptor {
+
+ @Override
+ public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
+ StringBuilder memcachConnInfo = new StringBuilder();
+ @SuppressWarnings("unchecked")
+ List inetSocketAddressList = (List)allArguments[1];
+ for (InetSocketAddress inetSocketAddress : inetSocketAddressList) {
+ String host = inetSocketAddress.getAddress().getHostAddress();
+ int port = inetSocketAddress.getPort();
+ memcachConnInfo.append(host).append(":").append(port).append(";");
+ }
+ if (memcachConnInfo.length() > 1) {
+ memcachConnInfo = new StringBuilder(memcachConnInfo.substring(0, memcachConnInfo.length() - 1));
+ }
+ objInst.setSkyWalkingDynamicField(memcachConnInfo.toString());
+ }
+}
\ No newline at end of file
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptor.java
new file mode 100644
index 000000000..89a864dab
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptor.java
@@ -0,0 +1,42 @@
+package org.skywalking.apm.plugin.spymemcached.v2;
+
+import java.lang.reflect.Method;
+
+import org.skywalking.apm.agent.core.context.ContextManager;
+import org.skywalking.apm.agent.core.context.tag.Tags;
+import org.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.skywalking.apm.network.trace.component.ComponentsDefine;
+
+public class MemcachedMethodInterceptor implements InstanceMethodsAroundInterceptor {
+
+ private static final String SPY_MEMCACHE = "SpyMemcached/";
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
+ String peer = String.valueOf(objInst.getSkyWalkingDynamicField());
+ AbstractSpan span = ContextManager.createExitSpan(SPY_MEMCACHE + method.getName(), peer);
+ span.setComponent(ComponentsDefine.MEMCACHE);
+ Tags.DB_TYPE.set(span, ComponentsDefine.MEMCACHE.getName());
+ SpanLayer.asDB(span);
+ Tags.DB_STATEMENT.set(span, method.getName() + " " + allArguments[0]);
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Object ret) throws Throwable {
+ ContextManager.stopSpan();
+ return ret;
+ }
+
+ @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ AbstractSpan span = ContextManager.activeSpan();
+ span.errorOccurred();
+ span.log(t);
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/define/MemcachedInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/define/MemcachedInstrumentation.java
new file mode 100644
index 000000000..bbb18baab
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/define/MemcachedInstrumentation.java
@@ -0,0 +1,82 @@
+package org.skywalking.apm.plugin.spymemcached.v2.define;
+
+import static org.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+import java.util.List;
+
+import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+/**
+ *
+ * {@link MemcachedInstrumentation} presents that skywalking intercept all constructors and methods of
+ * {@link net.spy.memcached.MemcachedClient}.
+ * {@link XMemcachedConstructorWithInetSocketAddressListArgInterceptor} intercepts the constructor with
+ * argument {@link java.net.InetSocketAddress}.
+ *
+ * @author IluckySi
+ *
+ */
+public class MemcachedInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS = "net.spy.memcached.MemcachedClient";
+ private static final String CONSTRUCTOR_WITH_INETSOCKETADDRESS_LIST_ARG_INTERCEPT_CLASS = "org.skywalking.apm.plugin.spymemcached.v2.MemcachedConstructorWithInetSocketAddressListArgInterceptor";
+ private static final String METHOD_INTERCEPT_CLASS = "org.skywalking.apm.plugin.spymemcached.v2.MemcachedMethodInterceptor";
+
+ @Override
+ public ClassMatch enhanceClass() {
+ return byName(ENHANCE_CLASS);
+ }
+
+ @Override
+ protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[] {
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher getConstructorMatcher() {
+ return takesArgument(1, List.class);
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return CONSTRUCTOR_WITH_INETSOCKETADDRESS_LIST_ARG_INTERCEPT_CLASS;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[] {
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named("touch").or(named("append")) .or(named("prepend")).or(named("asyncCAS"))
+ .or(named("cas")) .or(named("add")).or(named("set")).or(named("replace"))
+ .or(named("asyncGet")).or(named("asyncGets")).or(named("gets")).or(named("getAndTouch"))
+ .or(named("get")).or(named("asyncGetBulk")) .or(named("asyncGetAndTouch"))
+ .or(named("getBulk")).or(named("getStats")) .or(named("incr"))
+ .or(named("decr")).or(named("asyncIncr")) .or(named("asyncDecr"))
+ .or(named("delete"));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return METHOD_INTERCEPT_CLASS;
+ }
+
+ @Override public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/resources/skywalking-plugin.def
new file mode 100644
index 000000000..f552f1602
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/resources/skywalking-plugin.def
@@ -0,0 +1 @@
+spymemcached-2.x=org.skywalking.apm.plugin.spymemcached.v2.define.MemcachedInstrumentation
\ No newline at end of file
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptorTest.java
new file mode 100644
index 000000000..d4238a200
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptorTest.java
@@ -0,0 +1,39 @@
+package org.skywalking.apm.plugin.spymemcached.v2;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MemcachedConstructorWithInetSocketAddressListArgInterceptorTest {
+
+ private MemcachedConstructorWithInetSocketAddressListArgInterceptor interceptor;
+
+ @Mock
+ private EnhancedInstance enhancedInstance;
+
+ @Before
+ public void setUp() throws Exception {
+ interceptor = new MemcachedConstructorWithInetSocketAddressListArgInterceptor();
+ }
+
+ @Test
+ public void onConstructWithInetSocketAddressList() {
+ List inetSocketAddressList = new ArrayList();
+ inetSocketAddressList.add(new InetSocketAddress("127.0.0.1", 11211));
+ inetSocketAddressList.add(new InetSocketAddress("127.0.0.2", 11211));
+ interceptor.onConstruct(enhancedInstance, new Object[]{null, inetSocketAddressList});
+
+ verify(enhancedInstance, times(1)).setSkyWalkingDynamicField("127.0.0.1:11211;127.0.0.2:11211");
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptorTest.java
new file mode 100644
index 000000000..3ddc3be8c
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptorTest.java
@@ -0,0 +1,121 @@
+package org.skywalking.apm.plugin.spymemcached.v2;
+
+import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
+import org.skywalking.apm.agent.core.context.trace.LogDataEntity;
+import org.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.skywalking.apm.agent.core.context.trace.TraceSegment;
+import org.skywalking.apm.agent.core.context.util.KeyValuePair;
+import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.skywalking.apm.agent.test.helper.SpanHelper;
+import org.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+
+import net.spy.memcached.MemcachedClient;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(TracingSegmentRunner.class)
+public class MemcachedMethodInterceptorTest {
+
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+ @Mock
+ private EnhancedInstance enhancedInstance;
+ private MemcachedMethodInterceptor interceptor;
+
+ private Object[] allArgument;
+ private Class[] argumentType;
+
+ @Before
+ public void setUp() throws Exception {
+ allArgument = new Object[] {"OperationKey", "OperationValue"};
+ argumentType = new Class[] {String.class, String.class};
+
+ interceptor = new MemcachedMethodInterceptor();
+ when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn("127.0.0.1:11211");
+ }
+
+ @Test
+ public void testIntercept() throws Throwable {
+ interceptor.beforeMethod(enhancedInstance, getMockSetMethod(), allArgument, argumentType, null);
+ interceptor.afterMethod(enhancedInstance, getMockGetMethod(), allArgument, argumentType, null);
+
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+ assertThat(spans.size(), is(1));
+ assertMemcacheSpan(spans.get(0));
+ }
+
+ @Test
+ public void testInterceptWithException() throws Throwable {
+ interceptor.beforeMethod(enhancedInstance, getMockSetMethod(), allArgument, argumentType, null);
+ interceptor.handleMethodException(enhancedInstance, getMockSetMethod(), allArgument, argumentType, new RuntimeException());
+ interceptor.afterMethod(enhancedInstance, getMockSetMethod(), allArgument, argumentType, null);
+
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+ List spans = SegmentHelper.getSpans(traceSegment);
+ assertThat(spans.size(), is(1));
+ assertMemcacheSpan(spans.get(0));
+
+ assertLogData(SpanHelper.getLogs(spans.get(0)));
+ }
+
+ private void assertLogData(List logDataEntities) {
+ assertThat(logDataEntities.size(), is(1));
+ LogDataEntity logData = logDataEntities.get(0);
+ Assert.assertThat(logData.getLogs().size(), is(4));
+ Assert.assertThat(logData.getLogs().get(0).getValue(), CoreMatchers.