Skip to content

Commit db54c65

Browse files
authored
Optimize bytebuddy type description performance (#637)
### Improve the performance of type description of byte-buddy The goal is to get the original class description at re-transform, so as to generate consistent results when the Skywalking agent is enhanced again (including implementing the EnhancedInstance interface, auxiliary fields and methods, etc.) The previous type description used the `AgentBuilder.DescriptionStrategy.Default.POOL_FIRST` policy to get origin type description, which slows down the application startup, due to heavy I/O operations and parsing bytecode. New way is to remove dynamic fields, methods and interfaces generated by SkyWalking Agent from `TypeDescription`, and **make it as origin type descripton**. **Key feature** : * No need to cache `TypeDescription` objects, less memory used. * It only applies to the re-transform class processing flow and does not affect the startup process. **Process flow:** 1. Find `TypeDescription` from commonly used type cache, such as primitive class. 2. Delegate to `AgentBuilder.DescriptionStrategy.Default.HYBRID` 3. Wrap `TypeDescription` by `SWTypeDescriptionWrapper` , remove fields, methods, interface generated by SkyWalking. **Relative Issue:** apache/skywalking#11460
1 parent 2721438 commit db54c65

File tree

11 files changed

+494
-20
lines changed

11 files changed

+494
-20
lines changed

CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Release Notes.
1919
* Upgrade netty-codec-http2 to 4.1.100.Final
2020
* Add a netty-http 4.1.x plugin to trace HTTP requests.
2121
* Fix Impala Jdbc URL (including schema without properties) parsing exception.
22-
22+
* Optimize byte-buddy type description performance.
2323

2424
#### Documentation
2525

apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ public class Constants {
3636
/**
3737
* The name trait for auxiliary type names, field names and method names which generated by ByteBuddy.
3838
*/
39-
public static final String NAME_TRAIT = "sw$";
39+
public static final String NAME_TRAIT = "$sw$";
4040

4141
}

apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import java.util.Map;
26+
2627
import net.bytebuddy.ByteBuddy;
2728
import net.bytebuddy.agent.builder.AgentBuilder;
2829
import net.bytebuddy.agent.builder.SWAgentBuilderDefault;
30+
import net.bytebuddy.agent.builder.SWDescriptionStrategy;
2931
import net.bytebuddy.agent.builder.SWNativeMethodStrategy;
3032
import net.bytebuddy.description.NamedElement;
3133
import net.bytebuddy.description.type.TypeDescription;
@@ -156,9 +158,8 @@ private static AgentBuilder newAgentBuilder() {
156158
.with(new SWAuxiliaryTypeNamingStrategy(NAME_TRAIT))
157159
.with(new SWImplementationContextFactory(NAME_TRAIT));
158160

159-
SWNativeMethodStrategy nativeMethodStrategy = new SWNativeMethodStrategy(NAME_TRAIT);
160-
return new SWAgentBuilderDefault(byteBuddy, nativeMethodStrategy)
161-
.with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST);
161+
return new SWAgentBuilderDefault(byteBuddy, new SWNativeMethodStrategy(NAME_TRAIT))
162+
.with(new SWDescriptionStrategy(NAME_TRAIT));
162163
}
163164

164165
private static class Transformer implements AgentBuilder.Transformer {
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
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 net.bytebuddy.agent.builder;
20+
21+
import net.bytebuddy.description.annotation.AnnotationList;
22+
import net.bytebuddy.description.field.FieldDescription;
23+
import net.bytebuddy.description.field.FieldList;
24+
import net.bytebuddy.description.method.MethodDescription;
25+
import net.bytebuddy.description.method.MethodList;
26+
import net.bytebuddy.description.type.*;
27+
import net.bytebuddy.dynamic.TargetType;
28+
import net.bytebuddy.implementation.bytecode.StackSize;
29+
import net.bytebuddy.pool.TypePool;
30+
import net.bytebuddy.utility.JavaModule;
31+
import net.bytebuddy.utility.nullability.MaybeNull;
32+
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
33+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
34+
35+
import java.io.Serializable;
36+
import java.lang.annotation.Annotation;
37+
import java.util.Arrays;
38+
import java.util.HashMap;
39+
import java.util.List;
40+
import java.util.Map;
41+
import java.util.stream.Collectors;
42+
43+
/**
44+
* A DescriptionStrategy to get the original class description by removing dynamic field and method tokens
45+
* generated by SkyWalking.
46+
*/
47+
public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
48+
49+
/**
50+
* A cache of type descriptions for commonly used types to avoid unnecessary allocations.
51+
*/
52+
private static final Map<Class<?>, TypeDescription> TYPE_CACHE;
53+
54+
/*
55+
* Initializes the type cache.
56+
*/
57+
static {
58+
TYPE_CACHE = new HashMap<Class<?>, TypeDescription>();
59+
TYPE_CACHE.put(TargetType.class, new TypeDescription.ForLoadedType(TargetType.class));
60+
TYPE_CACHE.put(Class.class, new TypeDescription.ForLoadedType(Class.class));
61+
TYPE_CACHE.put(Throwable.class, new TypeDescription.ForLoadedType(Throwable.class));
62+
TYPE_CACHE.put(Annotation.class, new TypeDescription.ForLoadedType(Annotation.class));
63+
TYPE_CACHE.put(Object.class, new TypeDescription.ForLoadedType(Object.class));
64+
TYPE_CACHE.put(String.class, new TypeDescription.ForLoadedType(String.class));
65+
TYPE_CACHE.put(Boolean.class, new TypeDescription.ForLoadedType(Boolean.class));
66+
TYPE_CACHE.put(Byte.class, new TypeDescription.ForLoadedType(Byte.class));
67+
TYPE_CACHE.put(Short.class, new TypeDescription.ForLoadedType(Short.class));
68+
TYPE_CACHE.put(Character.class, new TypeDescription.ForLoadedType(Character.class));
69+
TYPE_CACHE.put(Integer.class, new TypeDescription.ForLoadedType(Integer.class));
70+
TYPE_CACHE.put(Long.class, new TypeDescription.ForLoadedType(Long.class));
71+
TYPE_CACHE.put(Float.class, new TypeDescription.ForLoadedType(Float.class));
72+
TYPE_CACHE.put(Double.class, new TypeDescription.ForLoadedType(Double.class));
73+
TYPE_CACHE.put(void.class, new TypeDescription.ForLoadedType(void.class));
74+
TYPE_CACHE.put(boolean.class, new TypeDescription.ForLoadedType(boolean.class));
75+
TYPE_CACHE.put(byte.class, new TypeDescription.ForLoadedType(byte.class));
76+
TYPE_CACHE.put(short.class, new TypeDescription.ForLoadedType(short.class));
77+
TYPE_CACHE.put(char.class, new TypeDescription.ForLoadedType(char.class));
78+
TYPE_CACHE.put(int.class, new TypeDescription.ForLoadedType(int.class));
79+
TYPE_CACHE.put(long.class, new TypeDescription.ForLoadedType(long.class));
80+
TYPE_CACHE.put(float.class, new TypeDescription.ForLoadedType(float.class));
81+
TYPE_CACHE.put(double.class, new TypeDescription.ForLoadedType(double.class));
82+
}
83+
84+
private AgentBuilder.DescriptionStrategy delegate = Default.HYBRID;
85+
86+
private String nameTrait;
87+
88+
public SWDescriptionStrategy(String nameTrait) {
89+
this.nameTrait = nameTrait;
90+
}
91+
92+
@Override
93+
public boolean isLoadedFirst() {
94+
return true;
95+
}
96+
97+
@Override
98+
public TypeDescription apply(String name,
99+
@MaybeNull Class<?> type,
100+
TypePool typePool,
101+
AgentBuilder.CircularityLock circularityLock,
102+
@MaybeNull ClassLoader classLoader,
103+
@MaybeNull JavaModule module) {
104+
// find from type cache
105+
if (type != null) {
106+
TypeDescription typeDescription = TYPE_CACHE.get(type);
107+
if (typeDescription != null) {
108+
return typeDescription;
109+
}
110+
}
111+
// wrap result
112+
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait);
113+
}
114+
115+
/**
116+
* A TypeDescription wrapper to remove fields, methods, interface generated by SkyWalking.
117+
*/
118+
static class SWTypeDescriptionWrapper extends TypeDescription.AbstractBase implements Serializable {
119+
120+
/**
121+
* The class's serial version UID.
122+
*/
123+
private static final long serialVersionUID = 1L;
124+
125+
private static final List<String> IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName());
126+
127+
private static final List<String> IGNORED_FIELDS = Arrays.asList(AbstractClassEnhancePluginDefine.CONTEXT_ATTR_NAME);
128+
129+
private static final List<String> IGNORED_METHODS = Arrays.asList("getSkyWalkingDynamicField", "setSkyWalkingDynamicField");
130+
131+
private MethodList<MethodDescription.InDefinedShape> methods;
132+
133+
private FieldList<FieldDescription.InDefinedShape> fields;
134+
135+
private final String nameTrait;
136+
137+
private TypeList.Generic interfaces;
138+
139+
private TypeDescription delegate;
140+
141+
public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait) {
142+
this.delegate = delegate;
143+
this.nameTrait = nameTrait;
144+
}
145+
146+
@Override
147+
public TypeList.Generic getInterfaces() {
148+
if (this.interfaces == null) {
149+
TypeList.Generic allInterfaces = delegate.getInterfaces();
150+
if (allInterfaces.stream().anyMatch(s -> IGNORED_INTERFACES.contains(s.getTypeName()))) {
151+
// remove interfaces added by SkyWalking
152+
List<Generic> list = allInterfaces.stream()
153+
.filter(s -> !IGNORED_INTERFACES.contains(s.getTypeName()))
154+
.collect(Collectors.toList());
155+
this.interfaces = new TypeList.Generic.Explicit(list);
156+
} else {
157+
this.interfaces = allInterfaces;
158+
}
159+
}
160+
return this.interfaces;
161+
}
162+
163+
@Override
164+
public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
165+
if (this.fields == null) {
166+
FieldList<FieldDescription.InDefinedShape> declaredFields = delegate.getDeclaredFields();
167+
if (declaredFields.stream()
168+
.anyMatch(f -> f.getName().contains(nameTrait) || IGNORED_FIELDS.contains(f.getName()))) {
169+
// Remove dynamic field tokens generated by SkyWalking
170+
fields = new FieldList.Explicit<>(declaredFields.stream()
171+
.filter(f -> !f.getName().contains(nameTrait) && !IGNORED_FIELDS.contains(f.getName()))
172+
.collect(Collectors.toList()));
173+
} else {
174+
fields = declaredFields;
175+
}
176+
}
177+
return fields;
178+
}
179+
180+
@Override
181+
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
182+
if (this.methods == null) {
183+
MethodList<MethodDescription.InDefinedShape> declaredMethods = delegate.getDeclaredMethods();
184+
if (declaredMethods.stream()
185+
.anyMatch(m -> m.getName().contains(nameTrait) || IGNORED_METHODS.contains(m.getName()))) {
186+
// Remove dynamic method tokens generated by SkyWalking
187+
methods = new MethodList.Explicit<>(declaredMethods.stream()
188+
.filter(m -> !m.getName().contains(nameTrait) && !IGNORED_METHODS.contains(m.getName()))
189+
.collect(Collectors.toList()));
190+
} else {
191+
methods = declaredMethods;
192+
}
193+
}
194+
return methods;
195+
}
196+
197+
@Override
198+
public boolean isAssignableTo(Class<?> type) {
199+
// ignore interface added by SkyWalking
200+
if (IGNORED_INTERFACES.contains(type.getName())) {
201+
return false;
202+
}
203+
return delegate.isAssignableTo(type);
204+
}
205+
206+
@Override
207+
public boolean isAccessibleTo(TypeDescription typeDescription) {
208+
// ignore interface added by SkyWalking
209+
if (IGNORED_INTERFACES.contains(typeDescription.getName())) {
210+
return false;
211+
}
212+
return delegate.isAccessibleTo(typeDescription);
213+
}
214+
215+
@Override
216+
public RecordComponentList<RecordComponentDescription.InDefinedShape> getRecordComponents() {
217+
return delegate.getRecordComponents();
218+
}
219+
220+
@Override
221+
public TypeDescription getComponentType() {
222+
return delegate.getComponentType();
223+
}
224+
225+
@Override
226+
public TypeDescription getDeclaringType() {
227+
return delegate.getDeclaringType();
228+
}
229+
230+
@Override
231+
public TypeList getDeclaredTypes() {
232+
return delegate.getDeclaredTypes();
233+
}
234+
235+
@Override
236+
public MethodDescription.InDefinedShape getEnclosingMethod() {
237+
return delegate.getEnclosingMethod();
238+
}
239+
240+
@Override
241+
public TypeDescription getEnclosingType() {
242+
return delegate.getEnclosingType();
243+
}
244+
245+
@Override
246+
public String getSimpleName() {
247+
return delegate.getSimpleName();
248+
}
249+
250+
@Override
251+
public String getCanonicalName() {
252+
return delegate.getCanonicalName();
253+
}
254+
255+
@Override
256+
public boolean isAnonymousType() {
257+
return delegate.isAnonymousType();
258+
}
259+
260+
@Override
261+
public boolean isLocalType() {
262+
return delegate.isLocalType();
263+
}
264+
265+
@Override
266+
public PackageDescription getPackage() {
267+
return delegate.getPackage();
268+
}
269+
270+
@Override
271+
public TypeDescription getNestHost() {
272+
return delegate.getNestHost();
273+
}
274+
275+
@Override
276+
public TypeList getNestMembers() {
277+
return delegate.getNestMembers();
278+
}
279+
280+
@Override
281+
public TypeList getPermittedSubtypes() {
282+
return delegate.getPermittedSubtypes();
283+
}
284+
285+
@Override
286+
public String getDescriptor() {
287+
return delegate.getDescriptor();
288+
}
289+
290+
@Override
291+
public String getName() {
292+
return delegate.getName();
293+
}
294+
295+
@Override
296+
public TypeList.Generic getTypeVariables() {
297+
return delegate.getTypeVariables();
298+
}
299+
300+
@Override
301+
public AnnotationList getDeclaredAnnotations() {
302+
return delegate.getDeclaredAnnotations();
303+
}
304+
305+
@Override
306+
public Generic getSuperClass() {
307+
return delegate.getSuperClass();
308+
}
309+
310+
@Override
311+
public StackSize getStackSize() {
312+
return delegate.getStackSize();
313+
}
314+
315+
@Override
316+
public boolean isArray() {
317+
return delegate.isArray();
318+
}
319+
320+
@Override
321+
public boolean isRecord() {
322+
return delegate.isRecord();
323+
}
324+
325+
@Override
326+
public boolean isPrimitive() {
327+
return delegate.isPrimitive();
328+
}
329+
330+
@Override
331+
public int getModifiers() {
332+
return delegate.getModifiers();
333+
}
334+
}
335+
}

apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/SWClassFileLocator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
*/
3636
public class SWClassFileLocator implements ClassFileLocator {
3737
private static final ILog LOGGER = LogManager.getLogger(SWClassFileLocator.class);
38-
private static final String[] TYPE_NAME_TRAITS = {"auxiliary$", "ByteBuddy$", "sw$"};
38+
private static final String[] TYPE_NAME_TRAITS = {"auxiliary$", "ByteBuddy$", "$sw$"};
3939
private static final int DEFAULT_TIMEOUT_SECONDS = 2;
4040

4141
private final ForInstrumentation.ClassLoadingDelegate classLoadingDelegate;

apm-sniffer/bytebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/biz/BizFoo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
package org.apache.skywalking.apm.agent.bytebuddy.biz;
2020

21-
public class BizFoo {
21+
public class BizFoo implements BizInterface {
2222

2323
private String name;
2424

0 commit comments

Comments
 (0)