diff --git a/.github/workflows/plugins-test.3.yaml b/.github/workflows/plugins-test.3.yaml index 1ef64b5da0..74419fcc4a 100644 --- a/.github/workflows/plugins-test.3.yaml +++ b/.github/workflows/plugins-test.3.yaml @@ -98,6 +98,7 @@ jobs: - tomcat-thread-pool-scenario - guava-eventbus-scenario - shenyu-2.4.x-scenario + - jdk-threadpool-scenario steps: - uses: actions/checkout@v2 with: diff --git a/CHANGES.md b/CHANGES.md index 835c8ca2bc..9d24594bc8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,7 @@ Release Notes. * Make sure the parent endpoint in tracing context from existing first ENTRY span, rather than first span only. * Fix the bug that maybe causing memory leak and repeated traceId when use gateway-2.1.x-plugin or gateway-3.x-plugin. * Fix Grpc 1.x plugin could leak context due to gRPC cancelled. +* Add JDK ThreadPoolExecutor Plugin. #### Documentation diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactory.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactory.java index e8adf48d2b..a835e8cb0d 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactory.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactory.java @@ -22,6 +22,7 @@ import org.apache.skywalking.apm.agent.core.boot.AgentPackagePath; import org.apache.skywalking.apm.agent.core.conf.Config; import org.apache.skywalking.apm.agent.core.conf.SnifferConfigInitializer; +import org.apache.skywalking.apm.agent.core.plugin.PluginFinder; import org.apache.skywalking.apm.util.StringUtil; public class WriterFactory { @@ -35,7 +36,9 @@ public static IWriter getLogWriter() { if (WRITER != null) { return WRITER; } - if (SnifferConfigInitializer.isInitCompleted() && AgentPackagePath.isPathFound()) { + if (SnifferConfigInitializer.isInitCompleted() + && PluginFinder.isPluginInitCompleted() + && AgentPackagePath.isPathFound()) { if (StringUtil.isEmpty(Config.Logging.DIR)) { try { Config.Logging.DIR = AgentPackagePath.getPath() + "/logs"; diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java index 6a2082ae3f..4ef608dccc 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/PluginFinder.java @@ -43,6 +43,7 @@ public class PluginFinder { private final Map> nameMatchDefine = new HashMap>(); private final List signatureMatchDefine = new ArrayList(); private final List bootstrapClassMatchDefine = new ArrayList(); + private static boolean IS_PLUGIN_INIT_COMPLETED = false; public PluginFinder(List plugins) { for (AbstractClassEnhancePluginDefine plugin : plugins) { @@ -107,4 +108,12 @@ public boolean matches(NamedElement target) { public List getBootstrapClassMatchDefine() { return bootstrapClassMatchDefine; } + + public static void pluginInitCompleted() { + IS_PLUGIN_INIT_COMPLETED = true; + } + + public static boolean isPluginInitCompleted() { + return IS_PLUGIN_INIT_COMPLETED; + } } diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactoryTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactoryTest.java index 0cfd717ad3..cc3620a6e3 100644 --- a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactoryTest.java +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/logging/core/WriterFactoryTest.java @@ -21,6 +21,7 @@ import org.apache.skywalking.apm.agent.core.boot.AgentPackagePath; import org.apache.skywalking.apm.agent.core.conf.Config; import org.apache.skywalking.apm.agent.core.conf.SnifferConfigInitializer; +import org.apache.skywalking.apm.agent.core.plugin.PluginFinder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; @@ -33,6 +34,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest(value = { SnifferConfigInitializer.class, + PluginFinder.class, AgentPackagePath.class }) public class WriterFactoryTest { @@ -41,11 +43,14 @@ public class WriterFactoryTest { public void alwaysReturnSystemLogWriteWithSetLoggingDir() { Config.Logging.OUTPUT = LogOutput.CONSOLE; PowerMockito.mockStatic(SnifferConfigInitializer.class); + PowerMockito.mockStatic(PluginFinder.class); PowerMockito.mockStatic(AgentPackagePath.class); BDDMockito.given(SnifferConfigInitializer.isInitCompleted()).willReturn(true); + BDDMockito.given(PluginFinder.isPluginInitCompleted()).willReturn(true); BDDMockito.given(AgentPackagePath.isPathFound()).willReturn(true); assertTrue(SnifferConfigInitializer.isInitCompleted()); + assertTrue(PluginFinder.isPluginInitCompleted()); assertTrue(AgentPackagePath.isPathFound()); IWriter logWriter = WriterFactory.getLogWriter(); @@ -56,11 +61,14 @@ public void alwaysReturnSystemLogWriteWithSetLoggingDir() { public void returnFileWriterWriteWithBlankLoggingDir() { Config.Logging.OUTPUT = LogOutput.FILE; PowerMockito.mockStatic(SnifferConfigInitializer.class); + PowerMockito.mockStatic(PluginFinder.class); PowerMockito.mockStatic(AgentPackagePath.class); BDDMockito.given(SnifferConfigInitializer.isInitCompleted()).willReturn(true); + BDDMockito.given(PluginFinder.isPluginInitCompleted()).willReturn(true); BDDMockito.given(AgentPackagePath.isPathFound()).willReturn(true); assertTrue(SnifferConfigInitializer.isInitCompleted()); + assertTrue(PluginFinder.isPluginInitCompleted()); assertTrue(AgentPackagePath.isPathFound()); IWriter logWriter = WriterFactory.getLogWriter(); diff --git a/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java b/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java index 31d586a685..57a11e9f3c 100644 --- a/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java +++ b/apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java @@ -130,6 +130,8 @@ public static void premain(String agentArgs, Instrumentation instrumentation) th .with(new Listener()) .installOn(instrumentation); + PluginFinder.pluginInitCompleted(); + try { ServiceManager.INSTANCE.boot(); } catch (Exception e) { diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml new file mode 100644 index 0000000000..d1e0cb3dbf --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/pom.xml @@ -0,0 +1,48 @@ + + + + + bootstrap-plugins + org.apache.skywalking + 8.10.0-SNAPSHOT + + 4.0.0 + + apm-jdk-threadpool-plugin + jar + + apm-jdk-threadpool-plugin + http://maven.apache.org + + + UTF-8 + + + + + + + maven-deploy-plugin + + + + + \ No newline at end of file diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java new file mode 100644 index 0000000000..c130c2f5b4 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/AbstractThreadingPoolInterceptor.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import java.lang.reflect.Method; + +public abstract class AbstractThreadingPoolInterceptor implements InstanceMethodsAroundInterceptor { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + if (!ContextManager.isActive()) { + return; + } + + if (allArguments == null || allArguments.length < 1) { + return; + } + + Object argument = allArguments[0]; + + Object wrappedObject = wrap(argument); + if (wrappedObject != null) { + allArguments[0] = wrappedObject; + } + } + + /** + * wrap the Callable or Runnable object if needed + * @param param Callable or Runnable object + * @return Wrapped object or null if not needed to wrap + */ + public abstract Object wrap(Object param); + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPoolExecuteMethodInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPoolExecuteMethodInterceptor.java new file mode 100644 index 0000000000..077d925bd6 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPoolExecuteMethodInterceptor.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.plugin; + +import org.apache.skywalking.apm.plugin.wrapper.SwRunnableWrapper; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import java.util.concurrent.RunnableFuture; + +public class ThreadPoolExecuteMethodInterceptor extends AbstractThreadingPoolInterceptor { + + @Override + public Object wrap(Object param) { + if (param instanceof SwRunnableWrapper) { + return null; + } + + if (param instanceof RunnableFuture) { + return null; + } + + if (!(param instanceof Runnable)) { + return null; + } + + Runnable runnable = (Runnable) param; + return new SwRunnableWrapper(runnable, ContextManager.capture()); + } + +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPoolSubmitMethodInterceptor.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPoolSubmitMethodInterceptor.java new file mode 100644 index 0000000000..bb938dddbf --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/ThreadPoolSubmitMethodInterceptor.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.plugin.wrapper.SwCallableWrapper; +import org.apache.skywalking.apm.plugin.wrapper.SwRunnableWrapper; + +import java.util.concurrent.Callable; + +public class ThreadPoolSubmitMethodInterceptor extends AbstractThreadingPoolInterceptor { + + @Override + public Object wrap(Object param) { + if (param instanceof SwRunnableWrapper || param instanceof SwCallableWrapper) { + return null; + } + + if (param instanceof Callable) { + Callable callable = (Callable) param; + return new SwCallableWrapper(callable, ContextManager.capture()); + } + + if (param instanceof Runnable) { + Runnable runnable = (Runnable) param; + return new SwRunnableWrapper(runnable, ContextManager.capture()); + } + + return null; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPoolExecutorInstrumentation.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPoolExecutorInstrumentation.java new file mode 100644 index 0000000000..d057a8f317 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/define/ThreadPoolExecutorInstrumentation.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.logical.LogicalMatchOperation; + +public class ThreadPoolExecutorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "java.util.concurrent.ThreadPoolExecutor"; + + private static final String INTERCEPT_EXECUTE_METHOD = "execute"; + + private static final String INTERCEPT_SUBMIT_METHOD = "submit"; + + private static final String INTERCEPT_EXECUTE_METHOD_HANDLE = "org.apache.skywalking.apm.plugin.ThreadPoolExecuteMethodInterceptor"; + + private static final String INTERCEPT_SUBMIT_METHOD_HANDLE = "org.apache.skywalking.apm.plugin.ThreadPoolSubmitMethodInterceptor"; + + @Override + public boolean isBootstrapInstrumentation() { + return true; + } + + @Override + protected ClassMatch enhanceClass() { + return LogicalMatchOperation.or(HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS), MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS)); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_EXECUTE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_EXECUTE_METHOD_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_SUBMIT_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_SUBMIT_METHOD_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/wrapper/SwCallableWrapper.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/wrapper/SwCallableWrapper.java new file mode 100644 index 0000000000..4f42962daf --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/wrapper/SwCallableWrapper.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +package org.apache.skywalking.apm.plugin.wrapper; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import java.util.concurrent.Callable; + +public class SwCallableWrapper implements Callable { + + private Callable callable; + + private ContextSnapshot contextSnapshot; + + public SwCallableWrapper(Callable callable, ContextSnapshot contextSnapshot) { + this.callable = callable; + this.contextSnapshot = contextSnapshot; + } + + @Override + public Object call() throws Exception { + AbstractSpan span = ContextManager.createLocalSpan(getOperationName()); + span.setComponent(ComponentsDefine.JDK_THREADING); + ContextManager.continued(contextSnapshot); + try { + return callable.call(); + } finally { + ContextManager.stopSpan(); + } + } + + private String getOperationName() { + return "SwCallableWrapper/" + Thread.currentThread().getName(); + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/wrapper/SwRunnableWrapper.java b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/wrapper/SwRunnableWrapper.java new file mode 100644 index 0000000000..d3bdb34d9a --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/java/org/apache/skywalking/apm/plugin/wrapper/SwRunnableWrapper.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +package org.apache.skywalking.apm.plugin.wrapper; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +public class SwRunnableWrapper implements Runnable { + + private Runnable runnable; + + private ContextSnapshot contextSnapshot; + + public SwRunnableWrapper(Runnable runnable, ContextSnapshot contextSnapshot) { + this.runnable = runnable; + this.contextSnapshot = contextSnapshot; + } + + @Override + public void run() { + AbstractSpan span = ContextManager.createLocalSpan(getOperationName()); + span.setComponent(ComponentsDefine.JDK_THREADING); + ContextManager.continued(contextSnapshot); + try { + runnable.run(); + } finally { + ContextManager.stopSpan(); + } + } + + private String getOperationName() { + return "SwRunnableWrapper/" + Thread.currentThread().getName(); + } +} diff --git a/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..2162787cd1 --- /dev/null +++ b/apm-sniffer/bootstrap-plugins/jdk-threadpool-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +jdk-threadpool-plugin=org.apache.skywalking.apm.plugin.define.ThreadPoolExecutorInstrumentation diff --git a/apm-sniffer/bootstrap-plugins/pom.xml b/apm-sniffer/bootstrap-plugins/pom.xml index 7034b62854..b4f7f0ef86 100644 --- a/apm-sniffer/bootstrap-plugins/pom.xml +++ b/apm-sniffer/bootstrap-plugins/pom.xml @@ -44,6 +44,7 @@ jdk-http-plugin jdk-threading-plugin + jdk-threadpool-plugin diff --git a/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md b/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md index 33fbe321ff..415ebcdb23 100644 --- a/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md +++ b/docs/en/setup/service-agent/java-agent/Bootstrap-plugins.md @@ -4,4 +4,5 @@ For using these plugins, you need to put the target plugin jar file into `/plugi Now, we have the following known bootstrap plugins. * Plugin of JDK HttpURLConnection. Agent is compatible with JDK 1.8+ -* Plugin of JDK Callable and Runnable. Agent is compatible with JDK 1.8+ \ No newline at end of file +* Plugin of JDK Callable and Runnable. Agent is compatible with JDK 1.8+ +* Plugin of JDK ThreadPoolExecutor. Agent is compatible with JDK 1.8+ \ No newline at end of file diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md index 213d86b896..442073ee66 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -137,3 +137,4 @@ - tomcat-thread-pool - guava-eventbus - shenyu-2.4.x +- jdk-threadpool-plugin diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/bin/startup.sh b/test/plugin/scenarios/jdk-threadpool-scenario/bin/startup.sh new file mode 100644 index 0000000000..811fbb4ea6 --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/jdk-threadpool-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/config/expectedData.yaml b/test/plugin/scenarios/jdk-threadpool-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..3a78cdd075 --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/config/expectedData.yaml @@ -0,0 +1,372 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +segmentItems: + - serviceName: jdk-threadpool-scenario + segmentSize: ge 14 + segments: + - segmentId: not null + spans: + - operationName: GET:/threadpool + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: SwRunnableWrapper/http-get-thread, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /threadpool + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + - operationName: SwRunnableWrapper/http-get-thread + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 80 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - {parentEndpoint: 'GET:/greet/{username}', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: GET:/threadpool + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: SwCallableWrapper/http-get-thread, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /threadpool + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + - operationName: SwCallableWrapper/http-get-thread + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 80 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - {parentEndpoint: 'GET:/greet/{username}', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: GET:/threadpool + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: SwRunnableWrapper/http-get-thread, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /threadpool + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + - operationName: SwRunnableWrapper/http-get-thread + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 80 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - {parentEndpoint: 'GET:/greet/{username}', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /threadpool + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + - operationName: SwRunnableWrapper/http-get-thread + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 80 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - {parentEndpoint: 'GET:/greet/{username}', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: GET:/threadpool + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: SwCallableWrapper/http-get-thread, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: GET:/threadpool + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: SwRunnableWrapper/http-get-thread, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /threadpool + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + - operationName: SwCallableWrapper/http-get-thread + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 80 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - {parentEndpoint: 'GET:/greet/{username}', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: /threadpool + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + - operationName: SwRunnableWrapper/http-get-thread + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 80 + isError: false + spanType: Local + peer: '' + skipAnalysis: false + refs: + - {parentEndpoint: 'GET:/greet/{username}', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} + - segmentId: not null + spans: + - operationName: GET:/greet/{username} + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/greet/skywalking'} + - {key: http.method, value: GET} + - segmentId: not null + spans: + - operationName: GET:/threadpool + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/threadpool'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: SwRunnableWrapper/http-get-thread, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: jdk-threadpool-scenario, + traceId: not null} \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/configuration.yml b/test/plugin/scenarios/jdk-threadpool-scenario/configuration.yml new file mode 100644 index 0000000000..1b369476aa --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/configuration.yml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +type: jvm +entryService: http://localhost:8080/greet/skywalking +healthCheck: http://localhost:8080/healthCheck +runningMode: with_bootstrap +withPlugins: apm-jdk-threadpool-plugin-*.jar +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/pom.xml b/test/plugin/scenarios/jdk-threadpool-scenario/pom.xml new file mode 100644 index 0000000000..1cb62527af --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + org.apache.skywalking + jdk-threadpool-scenario + 5.0.0 + + + UTF-8 + 1.8 + 2.1.6.RELEASE + + + skywalking-jdk-threadpool-scenario + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + + jdk-threadpool-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + maven-compiler-plugin + 3.8.1 + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/jdk-threadpool-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..36556b7813 --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ./target/jdk-threadpool-scenario.jar + ./libs + 0775 + + + \ No newline at end of file diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/threading/Application.java b/test/plugin/scenarios/jdk-threadpool-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/threading/Application.java new file mode 100644 index 0000000000..094c0aed0e --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/threading/Application.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.apache.skywalking.apm.testcase.jdk.threading; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @RestController + static class TestController { + private final RestTemplate restTemplate; + private final ExecutorService executorService; + private final ExecutorService executorService2; + + public TestController(final RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName("http-get-thread"); + return thread; + } + }); + this.executorService2 = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName("http-get-thread"); + return thread; + } + }); + } + + @GetMapping("/healthCheck") + public String healthCheck() { + return "Success"; + } + + @GetMapping("/greet/{username}") + public String testCase(@PathVariable final String username) throws ExecutionException, InterruptedException { + Runnable runnable = new Runnable() { + @Override + public void run() { + restTemplate.getForEntity("http://localhost:8080/threadpool", String.class); + } + }; + + Callable callable = new Callable() { + @Override + public String call() { + return restTemplate.getForEntity("http://localhost:8080/threadpool", String.class).getBody(); + } + }; + + executorService.execute(runnable); + executorService.submit(runnable); + executorService.submit(callable).get(); + + executorService2.execute(runnable); + executorService2.submit(runnable); + executorService2.submit(callable).get(); + + return username; + } + + @GetMapping("/threadpool") + public String threadpool() { + return "threadpool"; + } + + } +} diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/jdk-threadpool-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..6ff39eb8ea --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/src/main/resources/application.yaml @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +server: + port: 8080 + diff --git a/test/plugin/scenarios/jdk-threadpool-scenario/support-version.list b/test/plugin/scenarios/jdk-threadpool-scenario/support-version.list new file mode 100644 index 0000000000..feef03cdea --- /dev/null +++ b/test/plugin/scenarios/jdk-threadpool-scenario/support-version.list @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +all \ No newline at end of file