diff --git a/apm-application-toolkit/apm-toolkit-trace/src/main/java/org/apache/skywalking/apm/toolkit/trace/CallableWrapper.java b/apm-application-toolkit/apm-toolkit-trace/src/main/java/org/apache/skywalking/apm/toolkit/trace/CallableWrapper.java new file mode 100644 index 000000000..32c8f31c9 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-trace/src/main/java/org/apache/skywalking/apm/toolkit/trace/CallableWrapper.java @@ -0,0 +1,41 @@ +/* + * 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.toolkit.trace; + +import java.util.concurrent.Callable; + +/** + * @author carlvine500 + */ +@TraceCrossThread +public class CallableWrapper implements Callable { + final Callable callable; + + public static CallableWrapper of(Callable r) { + return new CallableWrapper(r); + } + + public CallableWrapper(Callable callable) { + this.callable = callable; + } + + @Override + public V call() throws Exception { + return callable.call(); + } +} \ No newline at end of file diff --git a/apm-application-toolkit/apm-toolkit-trace/src/main/java/org/apache/skywalking/apm/toolkit/trace/TraceCrossThread.java b/apm-application-toolkit/apm-toolkit-trace/src/main/java/org/apache/skywalking/apm/toolkit/trace/TraceCrossThread.java new file mode 100644 index 000000000..1eaf87a3a --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-trace/src/main/java/org/apache/skywalking/apm/toolkit/trace/TraceCrossThread.java @@ -0,0 +1,32 @@ +/* + * 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.toolkit.trace; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author carlvine500 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TraceCrossThread { + +} \ No newline at end of file diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java index b2d3a995a..f4f50a02f 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java @@ -204,4 +204,5 @@ public class ContextManager implements TracingContextListener, BootService, Igno public static boolean isActive() { return CONTEXT.get() != null; } + } diff --git a/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/pom.xml new file mode 100644 index 000000000..91354b934 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/pom.xml @@ -0,0 +1,59 @@ + + + + + + apm-sdk-plugin + org.apache.skywalking + 5.0.0-beta-SNAPSHOT + + 4.0.0 + + apm-jdk-cross-thread-plugin + jdk-cross-thread-plugin + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + attach-sources + + jar + + + + + + + diff --git a/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableConstructInterceptor.java b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableConstructInterceptor.java new file mode 100644 index 000000000..3c96f60f1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableConstructInterceptor.java @@ -0,0 +1,35 @@ +/* + * 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.jdk.thread; + +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.InstanceConstructorInterceptor; + +/** + * @author carlvine500 + */ +public class CallableOrRunnableConstructInterceptor implements InstanceConstructorInterceptor { + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + if (ContextManager.isActive()) { + objInst.setSkyWalkingDynamicField(ContextManager.capture()); + } + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableInvokeInterceptor.java b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableInvokeInterceptor.java new file mode 100644 index 000000000..8be7075c5 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableInvokeInterceptor.java @@ -0,0 +1,54 @@ +/* + * 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.jdk.thread; + +import java.lang.reflect.Method; +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.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; + +/** + * @author carlvine500 + */ +public class CallableOrRunnableInvokeInterceptor implements InstanceMethodsAroundInterceptor { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + ContextManager.createLocalSpan("Thread/" + objInst.getClass().getName() + "/" + method.getName()); + ContextSnapshot cachedObjects = (ContextSnapshot)objInst.getSkyWalkingDynamicField(); + if (cachedObjects != null) { + ContextManager.continued(cachedObjects); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + ContextManager.stopSpan(); + // clear ContextSnapshot + objInst.setSkyWalkingDynamicField(null); + return ret; + } + + @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/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/define/CallableOrRunnableInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/define/CallableOrRunnableInstrumentation.java new file mode 100644 index 000000000..4a4730e3d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/java/org/apache/skywalking/apm/plugin/jdk/thread/define/CallableOrRunnableInstrumentation.java @@ -0,0 +1,85 @@ +/* + * 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.jdk.thread.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.any; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.skywalking.apm.agent.core.plugin.match.ClassAnnotationMatch.byClassAnnotationMatch; + +/** + * {@link CallableOrRunnableInstrumentation} presents that skywalking intercepts all Class with annotation + * "org.skywalking.apm.toolkit.trace.TraceCrossThread" and method named "call" or "run". + * + * @author carlvine500 + */ +public class CallableOrRunnableInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + public static final String ANNOTATION_NAME = "org.apache.skywalking.apm.toolkit.trace.TraceCrossThread"; + private static final String INIT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jdk.thread.CallableOrRunnableConstructInterceptor"; + private static final String CALL_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jdk.thread.CallableOrRunnableInvokeInterceptor"; + private static final String CALL_METHOD_NAME = "call"; + private static final String RUN_METHOD_NAME = "run"; + + @Override protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override public ElementMatcher getConstructorMatcher() { + return any(); + } + + @Override public String getConstructorInterceptor() { + return INIT_METHOD_INTERCEPTOR; + } + } + }; + } + + @Override + protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override public ElementMatcher getMethodsMatcher() { + return (named(CALL_METHOD_NAME).and(takesArguments(0))) + .or(named(RUN_METHOD_NAME).and(takesArguments(0))); + } + + @Override public String getMethodsInterceptor() { + return CALL_METHOD_INTERCEPTOR; + } + + @Override public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override protected ClassMatch enhanceClass() { + return byClassAnnotationMatch(new String[] {ANNOTATION_NAME}); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..9323cf3cf --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-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-cross-thread=org.apache.skywalking.apm.plugin.jdk.thread.define.CallableOrRunnableInstrumentation + diff --git a/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableInterceptorTest.java new file mode 100644 index 000000000..c76d65f5a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jdk-cross-thread-plugin/src/test/java/org/apache/skywalking/apm/plugin/jdk/thread/CallableOrRunnableInterceptorTest.java @@ -0,0 +1,112 @@ +/* + * 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.jdk.thread; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.Callable; +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.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class CallableOrRunnableInterceptorTest { + + private CallableOrRunnableConstructInterceptor constructorInterceptor; + + private CallableOrRunnableInvokeInterceptor callableCallInterceptor; + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + private EnhancedInstance enhancedInstance = new EnhancedInstance() { + + private Object object; + + @Override + public Object getSkyWalkingDynamicField() { + return object; + } + + @Override public void setSkyWalkingDynamicField(Object value) { + this.object = value; + } + }; + + @Mock + private ContextSnapshot contextSnapshot; + + private Object[] arguments; + + private Method callMethod; + + @Before + public void setUp() throws NoSuchMethodException { + constructorInterceptor = new CallableOrRunnableConstructInterceptor(); + callableCallInterceptor = new CallableOrRunnableInvokeInterceptor(); + + Callable call = new Callable() { + @Override public String call() throws Exception { + return null; + } + }; + callMethod = call.getClass().getMethod("call"); + arguments = new Object[0]; + } + + @Test + public void testOnConstructor() { + constructorInterceptor.onConstruct(enhancedInstance, null); + Assert.assertNull(enhancedInstance.getSkyWalkingDynamicField()); + } + + @Test + public void testCall() throws Throwable { + + enhancedInstance.setSkyWalkingDynamicField(contextSnapshot); + callableCallInterceptor.beforeMethod(enhancedInstance, callMethod, arguments, null, null); + callableCallInterceptor.afterMethod(enhancedInstance, callMethod, arguments, null, "result"); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertThat(spans.size(), is(1)); + + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 5f780d72e..f31c86977 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -56,6 +56,7 @@ kafka-v1-plugin servicecomb-plugin hystrix-1.x-plugin + jdk-cross-thread-plugin pom