From e0a4c44036b33eadc0add7f91a2e5fd6caca7098 Mon Sep 17 00:00:00 2001 From: libinglong <38123153+libinglong@users.noreply.github.com> Date: Fri, 8 Jan 2021 09:01:35 +0800 Subject: [PATCH] new feature add witness method (#6103) https://lists.apache.org/thread.html/r4f67a7ed880a493e4e82bd2e9a0c26a6e43c37bbfcdaebe7b1a340dc%40%3Cdev.skywalking.apache.org%3E --- CHANGES.md | 1 + .../AbstractClassEnhancePluginDefine.java | 20 +++++- ...essClassFinder.java => WitnessFinder.java} | 41 ++++++++++-- .../apm/agent/core/plugin/WitnessMethod.java | 45 +++++++++++++ .../core/plugin/witness/WitnessTest.java | 67 +++++++++++++++++++ .../guides/Java-Plugin-Development-Guide.md | 25 +++++++ 6 files changed, 190 insertions(+), 9 deletions(-) rename apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/{WitnessClassFinder.java => WitnessFinder.java} (57%) create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessMethod.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java diff --git a/CHANGES.md b/CHANGES.md index 982c9bdf0f..509b50177c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ Release Notes. * Fix DataCarrier's `org.apache.skywalking.apm.commons.datacarrier.buffer.Buffer` implementation isn't activated in `IF_POSSIBLE` mode. * Fix ArrayBlockingQueueBuffer's useless `IF_POSSIBLE` mode list * Support building gRPC TLS channel but CA file is not required. +* Add witness method mechanism in the agent plugin core. * Add Dolphinscheduler plugin definition. * Make sampling still works when the trace ignores plug-in activation. * Fix mssql-plugin occur ClassCastException when call the method of return generate key. diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java index c6391ff273..88c980cf75 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java @@ -27,8 +27,11 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsIn import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.util.CollectionUtil; import org.apache.skywalking.apm.util.StringUtil; +import java.util.List; + /** * Basic abstract class of all sky-walking auto-instrumentation plugins. *

@@ -57,19 +60,28 @@ public abstract class AbstractClassEnhancePluginDefine { } LOGGER.debug("prepare to enhance class {} by {}.", transformClassName, interceptorDefineClassName); - + WitnessFinder finder = WitnessFinder.INSTANCE; /** * find witness classes for enhance class */ String[] witnessClasses = witnessClasses(); if (witnessClasses != null) { for (String witnessClass : witnessClasses) { - if (!WitnessClassFinder.INSTANCE.exist(witnessClass, classLoader)) { + if (!finder.exist(witnessClass, classLoader)) { LOGGER.warn("enhance class {} by plugin {} is not working. Because witness class {} is not existed.", transformClassName, interceptorDefineClassName, witnessClass); return null; } } } + List witnessMethods = witnessMethods(); + if (!CollectionUtil.isEmpty(witnessMethods)) { + for (WitnessMethod witnessMethod : witnessMethods) { + if (!finder.exist(witnessMethod, classLoader)) { + LOGGER.warn("enhance class {} by plugin {} is not working. Because witness method {} is not existed.", transformClassName, interceptorDefineClassName, witnessMethod); + return null; + } + } + } /** * find origin class source code for interceptor @@ -104,6 +116,10 @@ public abstract class AbstractClassEnhancePluginDefine { return new String[] {}; } + protected List witnessMethods() { + return null; + } + public boolean isBootstrapInstrumentation() { return false; } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessClassFinder.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessFinder.java similarity index 57% rename from apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessClassFinder.java rename to apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessFinder.java index 8211281f83..9f0b4d1368 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessClassFinder.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessFinder.java @@ -18,24 +18,36 @@ package org.apache.skywalking.apm.agent.core.plugin; +import net.bytebuddy.pool.TypePool; + import java.util.HashMap; import java.util.Map; -import net.bytebuddy.pool.TypePool; /** - * The WitnessClassFinder represents a pool of {@link TypePool}s, each {@link TypePool} matches a {@link - * ClassLoader}, which helps to find the class define existed or not. + * The WitnessFinder represents a pool of {@link TypePool}s, each {@link TypePool} matches a {@link + * ClassLoader}, which helps to find the class declaration existed or not. */ -public enum WitnessClassFinder { +public enum WitnessFinder { INSTANCE; - private Map poolMap = new HashMap(); + private final Map poolMap = new HashMap(); /** * @param classLoader for finding the witnessClass * @return true, if the given witnessClass exists, through the given classLoader. */ public boolean exist(String witnessClass, ClassLoader classLoader) { + return getResolution(witnessClass, classLoader) + .isResolved(); + } + + /** + * get TypePool.Resolution of the witness class + * @param witnessClass class name + * @param classLoader classLoader for finding the witnessClass + * @return TypePool.Resolution + */ + private TypePool.Resolution getResolution(String witnessClass, ClassLoader classLoader) { ClassLoader mappingKey = classLoader == null ? NullClassLoader.INSTANCE : classLoader; if (!poolMap.containsKey(mappingKey)) { synchronized (poolMap) { @@ -46,9 +58,24 @@ public enum WitnessClassFinder { } } TypePool typePool = poolMap.get(mappingKey); - TypePool.Resolution witnessClassResolution = typePool.describe(witnessClass); - return witnessClassResolution.isResolved(); + return typePool.describe(witnessClass); } + + /** + * @param classLoader for finding the witness method + * @return true, if the given witness method exists, through the given classLoader. + */ + public boolean exist(WitnessMethod witnessMethod, ClassLoader classLoader) { + TypePool.Resolution resolution = getResolution(witnessMethod.getDeclaringClassName(), classLoader); + if (!resolution.isResolved()) { + return false; + } + return !resolution.resolve() + .getDeclaredMethods() + .filter(witnessMethod.getElementMatcher()) + .isEmpty(); + } + } final class NullClassLoader extends ClassLoader { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessMethod.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessMethod.java new file mode 100644 index 0000000000..d1b508695c --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/WitnessMethod.java @@ -0,0 +1,45 @@ +/* + * 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.agent.core.plugin; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Witness Method for plugin activation + */ +@ToString +@RequiredArgsConstructor +public class WitnessMethod { + + /** + * the class or interface name where the witness method is declared. + */ + @Getter + private final String declaringClassName; + /** + * matcher to match the witness method + */ + @Getter + private final ElementMatcher elementMatcher; + +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java new file mode 100644 index 0000000000..6a5a751e77 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java @@ -0,0 +1,67 @@ +/* + * 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.agent.core.plugin.witness; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.WitnessFinder; +import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +/** + * unit test for WitnessFinder + */ +public class WitnessTest { + + private final String className = "org.apache.skywalking.apm.agent.core.plugin.witness.WitnessTest"; + + private final WitnessFinder finder = WitnessFinder.INSTANCE; + + @Test + public void testWitnessClass() { + Assert.assertTrue(finder.exist(className, this.getClass().getClassLoader())); + } + + @Test + public void testWitnessMethod() { + ElementMatcher.Junction junction = ElementMatchers.named("foo") + .and(ElementMatchers.returnsGeneric(target -> "java.util.List>".equals(target.getTypeName()))) + .and(ElementMatchers.takesGenericArgument(0, target -> "java.util.List>".equals(target.getTypeName()))) + .and(ElementMatchers.takesArgument(1, target -> "java.lang.String".equals(target.getName()))); + WitnessMethod witnessMethod = new WitnessMethod(className, junction); + Assert.assertTrue(finder.exist(witnessMethod, this.getClass().getClassLoader())); + } + + @Test + public void testWitnessMethodOnlyUsingName() { + ElementMatcher.Junction junction = ElementMatchers.named("foo"); + WitnessMethod witnessMethod = new WitnessMethod(className, junction); + Assert.assertTrue(finder.exist(witnessMethod, this.getClass().getClassLoader())); + } + + public List> foo(List> param, String s) { + return null; + } + +} diff --git a/docs/en/guides/Java-Plugin-Development-Guide.md b/docs/en/guides/Java-Plugin-Development-Guide.md index b70e424b61..e1b2b265c2 100644 --- a/docs/en/guides/Java-Plugin-Development-Guide.md +++ b/docs/en/guides/Java-Plugin-Development-Guide.md @@ -299,6 +299,31 @@ The following sections will tell you how to implement the interceptor. tomcat-7.x/8.x=TomcatInstrumentation ``` +4. Set up `witnessClasses` and/or `witnessMethods` if the instrumentation should be activated in specific versions. + + Example: + + ```java + // The plugin is activated only when the foo.Bar class exists. + @Override + protected String[] witnessClasses() { + return new String[] { + "foo.Bar" + }; + } + + // The plugin is activated only when the foo.Bar#hello method exists. + @Override + protected List witnessMethods() { + List witnessMethodList = new ArrayList<>(); + WitnessMethod witnessMethod = new WitnessMethod("foo.Bar", ElementMatchers.named("hello")); + witnessMethodList.add(witnessMethod); + return witnessMethodList; + } + ``` + For more example, see [WitnessTest.java](/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/witness/WitnessTest.java) + + ### Implement an interceptor As an interceptor for an instance method, the interceptor implements -- GitLab