Skip to content

Commit 35856b4

Browse files
authored
Add the virtual thread executor plugin (#751)
1 parent 534a80c commit 35856b4

File tree

22 files changed

+770
-1
lines changed

22 files changed

+770
-1
lines changed

.github/workflows/plugins-jdk21-test.0.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
matrix:
5757
case:
5858
- spring-6.x-scenario
59+
- jdk-virtual-thread-executor-scenario
5960
steps:
6061
- uses: actions/checkout@v2
6162
with:

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Release Notes.
55
9.5.0
66
------------------
77

8+
* Add the virtual thread executor plugin
89

910

1011
All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/236?closed=1)

apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,6 @@ public class ComponentsDefine {
260260
public static final OfficialComponent SOLON_MVC = new OfficialComponent(158, "SolonMVC");
261261

262262
public static final OfficialComponent CAFFEINE = new OfficialComponent(160, "Caffeine");
263+
264+
public static final OfficialComponent THREAD_PER_TASK_EXECUTOR = new OfficialComponent(161, "ThreadPerTask-executor");
263265
}

apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ public static final class HTTP {
155155
*/
156156
public static final StringTag THREAD_ID = new StringTag(23, "thread.id");
157157

158+
/**
159+
* THREAD_CARRIER represents the actual operating system thread that carries out the execution of the virtual thread.
160+
*/
161+
public static final StringTag THREAD_CARRIER = new StringTag(24, "thread.carrier");
162+
158163
/**
159164
* Creates a {@code StringTag} with the given key and cache it, if it's created before, simply return it without
160165
* creating a new one.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!--
2+
~ Licensed to the Apache Software Foundation (ASF) under one or more
3+
~ contributor license agreements. See the NOTICE file distributed with
4+
~ this work for additional information regarding copyright ownership.
5+
~ The ASF licenses this file to You under the Apache License, Version 2.0
6+
~ (the "License"); you may not use this file except in compliance with
7+
~ the License. You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
~
17+
-->
18+
19+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
20+
<parent>
21+
<artifactId>bootstrap-plugins</artifactId>
22+
<groupId>org.apache.skywalking</groupId>
23+
<version>9.5.0-SNAPSHOT</version>
24+
</parent>
25+
<modelVersion>4.0.0</modelVersion>
26+
27+
<artifactId>apm-jdk-virtual-thread-executor-plugin</artifactId>
28+
<packaging>jar</packaging>
29+
30+
<name>apm-jdk-virtual-thread-executor-plugin</name>
31+
<url>http://maven.apache.org</url>
32+
33+
<properties>
34+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
35+
</properties>
36+
37+
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<artifactId>maven-deploy-plugin</artifactId>
42+
</plugin>
43+
</plugins>
44+
</build>
45+
46+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin;
20+
21+
import org.apache.skywalking.apm.agent.core.context.ContextManager;
22+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
23+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
24+
25+
public class ThreadPerTaskExecutorConstructInterceptor implements InstanceConstructorInterceptor {
26+
27+
@Override
28+
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable {
29+
if (ContextManager.isActive()) {
30+
objInst.setSkyWalkingDynamicField(ContextManager.capture());
31+
}
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin;
20+
21+
import org.apache.skywalking.apm.agent.core.context.ContextManager;
22+
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
23+
import org.apache.skywalking.apm.agent.core.context.tag.Tags;
24+
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
26+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
27+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
28+
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
29+
30+
import java.lang.reflect.Method;
31+
32+
public class ThreadPerTaskExecutorRunInterceptor implements InstanceMethodsAroundInterceptor {
33+
34+
@Override
35+
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
36+
Object skyWalkingDynamicField = objInst.getSkyWalkingDynamicField();
37+
if (skyWalkingDynamicField == null) {
38+
return;
39+
}
40+
41+
if (!(skyWalkingDynamicField instanceof ContextSnapshot)) {
42+
return;
43+
}
44+
45+
AbstractSpan span = ContextManager.createLocalSpan(getOperationName(objInst, method));
46+
span.setComponent(ComponentsDefine.THREAD_PER_TASK_EXECUTOR);
47+
setCarrierThread(span);
48+
49+
ContextSnapshot contextSnapshot = (ContextSnapshot) skyWalkingDynamicField;
50+
ContextManager.continued(contextSnapshot);
51+
}
52+
53+
@Override
54+
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
55+
if (ContextManager.isActive()) {
56+
ContextManager.stopSpan();
57+
}
58+
return ret;
59+
}
60+
61+
@Override
62+
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
63+
if (ContextManager.isActive()) {
64+
ContextManager.activeSpan().log(t);
65+
}
66+
}
67+
68+
private String getOperationName(EnhancedInstance objInst, Method method) {
69+
return objInst.getClass().getName() + "." + method.getName();
70+
}
71+
72+
private void setCarrierThread(AbstractSpan span) {
73+
String threadInfo = Thread.currentThread().toString();
74+
if (threadInfo.startsWith("VirtualThread")) {
75+
String[] parts = threadInfo.split("@");
76+
if (parts.length >= 1) {
77+
Tags.THREAD_CARRIER.set(span, parts[1]);
78+
}
79+
}
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.define;
20+
21+
import net.bytebuddy.description.method.MethodDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import net.bytebuddy.matcher.ElementMatchers;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
26+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
27+
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
28+
import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch;
29+
30+
import static net.bytebuddy.matcher.ElementMatchers.any;
31+
32+
public class ThreadPerTaskExecutorFutureInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
33+
34+
private static final String ENHANCE_CLASS = "java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture";
35+
36+
private static final String INTERCEPT_RUN_METHOD = "run";
37+
38+
private static final String INTERCEPT_RUN_HANDLE = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorRunInterceptor";
39+
40+
private static final String TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorConstructInterceptor";
41+
42+
@Override
43+
public boolean isBootstrapInstrumentation() {
44+
return true;
45+
}
46+
47+
@Override
48+
protected ClassMatch enhanceClass() {
49+
return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS);
50+
}
51+
52+
@Override
53+
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
54+
return new ConstructorInterceptPoint[]{
55+
new ConstructorInterceptPoint() {
56+
@Override
57+
public ElementMatcher<MethodDescription> getConstructorMatcher() {
58+
return any();
59+
}
60+
61+
@Override
62+
public String getConstructorInterceptor() {
63+
return TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR;
64+
}
65+
}
66+
};
67+
}
68+
69+
@Override
70+
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
71+
return new InstanceMethodsInterceptPoint[]{
72+
new InstanceMethodsInterceptPoint() {
73+
@Override
74+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
75+
return ElementMatchers.named(INTERCEPT_RUN_METHOD);
76+
}
77+
78+
@Override
79+
public String getMethodsInterceptor() {
80+
return INTERCEPT_RUN_HANDLE;
81+
}
82+
83+
@Override
84+
public boolean isOverrideArgs() {
85+
return true;
86+
}
87+
}
88+
};
89+
}
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.define;
20+
21+
import net.bytebuddy.description.method.MethodDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import net.bytebuddy.matcher.ElementMatchers;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
26+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
27+
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
28+
import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch;
29+
30+
import static net.bytebuddy.matcher.ElementMatchers.any;
31+
32+
public class ThreadPerTaskExecutorTaskRunnerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
33+
34+
private static final String ENHANCE_CLASS = "java.util.concurrent.ThreadPerTaskExecutor$TaskRunner";
35+
36+
private static final String INTERCEPT_RUN_METHOD = "run";
37+
38+
private static final String INTERCEPT_RUN_HANDLE = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorRunInterceptor";
39+
40+
private static final String TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.ThreadPerTaskExecutorConstructInterceptor";
41+
42+
@Override
43+
public boolean isBootstrapInstrumentation() {
44+
return true;
45+
}
46+
47+
@Override
48+
protected ClassMatch enhanceClass() {
49+
return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS);
50+
}
51+
52+
@Override
53+
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
54+
return new ConstructorInterceptPoint[]{
55+
new ConstructorInterceptPoint() {
56+
@Override
57+
public ElementMatcher<MethodDescription> getConstructorMatcher() {
58+
return any();
59+
}
60+
61+
@Override
62+
public String getConstructorInterceptor() {
63+
return TASK_RUNNER_CONSTRUCT_METHOD_INTERCEPTOR;
64+
}
65+
}
66+
};
67+
}
68+
69+
@Override
70+
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
71+
return new InstanceMethodsInterceptPoint[]{
72+
new InstanceMethodsInterceptPoint() {
73+
@Override
74+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
75+
return ElementMatchers.named(INTERCEPT_RUN_METHOD);
76+
}
77+
78+
@Override
79+
public String getMethodsInterceptor() {
80+
return INTERCEPT_RUN_HANDLE;
81+
}
82+
83+
@Override
84+
public boolean isOverrideArgs() {
85+
return true;
86+
}
87+
}
88+
};
89+
}
90+
}

0 commit comments

Comments
 (0)