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 super MethodDescription.InDefinedShape> 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