Skip to content

Commit

Permalink
Optimize bytebuddy type description performance (#637)
Browse files Browse the repository at this point in the history
### 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
  • Loading branch information
kylixs authored Nov 3, 2023
1 parent 2721438 commit db54c65
Show file tree
Hide file tree
Showing 11 changed files with 494 additions and 20 deletions.
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Release Notes.
* Upgrade netty-codec-http2 to 4.1.100.Final
* Add a netty-http 4.1.x plugin to trace HTTP requests.
* Fix Impala Jdbc URL (including schema without properties) parsing exception.

* Optimize byte-buddy type description performance.

#### Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ public class Constants {
/**
* The name trait for auxiliary type names, field names and method names which generated by ByteBuddy.
*/
public static final String NAME_TRAIT = "sw$";
public static final String NAME_TRAIT = "$sw$";

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.SWAgentBuilderDefault;
import net.bytebuddy.agent.builder.SWDescriptionStrategy;
import net.bytebuddy.agent.builder.SWNativeMethodStrategy;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
Expand Down Expand Up @@ -156,9 +158,8 @@ private static AgentBuilder newAgentBuilder() {
.with(new SWAuxiliaryTypeNamingStrategy(NAME_TRAIT))
.with(new SWImplementationContextFactory(NAME_TRAIT));

SWNativeMethodStrategy nativeMethodStrategy = new SWNativeMethodStrategy(NAME_TRAIT);
return new SWAgentBuilderDefault(byteBuddy, nativeMethodStrategy)
.with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST);
return new SWAgentBuilderDefault(byteBuddy, new SWNativeMethodStrategy(NAME_TRAIT))
.with(new SWDescriptionStrategy(NAME_TRAIT));
}

private static class Transformer implements AgentBuilder.Transformer {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
/*
* 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 net.bytebuddy.agent.builder;

import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.*;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* A DescriptionStrategy to get the original class description by removing dynamic field and method tokens
* generated by SkyWalking.
*/
public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {

/**
* A cache of type descriptions for commonly used types to avoid unnecessary allocations.
*/
private static final Map<Class<?>, TypeDescription> TYPE_CACHE;

/*
* Initializes the type cache.
*/
static {
TYPE_CACHE = new HashMap<Class<?>, TypeDescription>();
TYPE_CACHE.put(TargetType.class, new TypeDescription.ForLoadedType(TargetType.class));
TYPE_CACHE.put(Class.class, new TypeDescription.ForLoadedType(Class.class));
TYPE_CACHE.put(Throwable.class, new TypeDescription.ForLoadedType(Throwable.class));
TYPE_CACHE.put(Annotation.class, new TypeDescription.ForLoadedType(Annotation.class));
TYPE_CACHE.put(Object.class, new TypeDescription.ForLoadedType(Object.class));
TYPE_CACHE.put(String.class, new TypeDescription.ForLoadedType(String.class));
TYPE_CACHE.put(Boolean.class, new TypeDescription.ForLoadedType(Boolean.class));
TYPE_CACHE.put(Byte.class, new TypeDescription.ForLoadedType(Byte.class));
TYPE_CACHE.put(Short.class, new TypeDescription.ForLoadedType(Short.class));
TYPE_CACHE.put(Character.class, new TypeDescription.ForLoadedType(Character.class));
TYPE_CACHE.put(Integer.class, new TypeDescription.ForLoadedType(Integer.class));
TYPE_CACHE.put(Long.class, new TypeDescription.ForLoadedType(Long.class));
TYPE_CACHE.put(Float.class, new TypeDescription.ForLoadedType(Float.class));
TYPE_CACHE.put(Double.class, new TypeDescription.ForLoadedType(Double.class));
TYPE_CACHE.put(void.class, new TypeDescription.ForLoadedType(void.class));
TYPE_CACHE.put(boolean.class, new TypeDescription.ForLoadedType(boolean.class));
TYPE_CACHE.put(byte.class, new TypeDescription.ForLoadedType(byte.class));
TYPE_CACHE.put(short.class, new TypeDescription.ForLoadedType(short.class));
TYPE_CACHE.put(char.class, new TypeDescription.ForLoadedType(char.class));
TYPE_CACHE.put(int.class, new TypeDescription.ForLoadedType(int.class));
TYPE_CACHE.put(long.class, new TypeDescription.ForLoadedType(long.class));
TYPE_CACHE.put(float.class, new TypeDescription.ForLoadedType(float.class));
TYPE_CACHE.put(double.class, new TypeDescription.ForLoadedType(double.class));
}

private AgentBuilder.DescriptionStrategy delegate = Default.HYBRID;

private String nameTrait;

public SWDescriptionStrategy(String nameTrait) {
this.nameTrait = nameTrait;
}

@Override
public boolean isLoadedFirst() {
return true;
}

@Override
public TypeDescription apply(String name,
@MaybeNull Class<?> type,
TypePool typePool,
AgentBuilder.CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
// find from type cache
if (type != null) {
TypeDescription typeDescription = TYPE_CACHE.get(type);
if (typeDescription != null) {
return typeDescription;
}
}
// wrap result
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait);
}

/**
* A TypeDescription wrapper to remove fields, methods, interface generated by SkyWalking.
*/
static class SWTypeDescriptionWrapper extends TypeDescription.AbstractBase implements Serializable {

/**
* The class's serial version UID.
*/
private static final long serialVersionUID = 1L;

private static final List<String> IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName());

private static final List<String> IGNORED_FIELDS = Arrays.asList(AbstractClassEnhancePluginDefine.CONTEXT_ATTR_NAME);

private static final List<String> IGNORED_METHODS = Arrays.asList("getSkyWalkingDynamicField", "setSkyWalkingDynamicField");

private MethodList<MethodDescription.InDefinedShape> methods;

private FieldList<FieldDescription.InDefinedShape> fields;

private final String nameTrait;

private TypeList.Generic interfaces;

private TypeDescription delegate;

public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait) {
this.delegate = delegate;
this.nameTrait = nameTrait;
}

@Override
public TypeList.Generic getInterfaces() {
if (this.interfaces == null) {
TypeList.Generic allInterfaces = delegate.getInterfaces();
if (allInterfaces.stream().anyMatch(s -> IGNORED_INTERFACES.contains(s.getTypeName()))) {
// remove interfaces added by SkyWalking
List<Generic> list = allInterfaces.stream()
.filter(s -> !IGNORED_INTERFACES.contains(s.getTypeName()))
.collect(Collectors.toList());
this.interfaces = new TypeList.Generic.Explicit(list);
} else {
this.interfaces = allInterfaces;
}
}
return this.interfaces;
}

@Override
public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
if (this.fields == null) {
FieldList<FieldDescription.InDefinedShape> declaredFields = delegate.getDeclaredFields();
if (declaredFields.stream()
.anyMatch(f -> f.getName().contains(nameTrait) || IGNORED_FIELDS.contains(f.getName()))) {
// Remove dynamic field tokens generated by SkyWalking
fields = new FieldList.Explicit<>(declaredFields.stream()
.filter(f -> !f.getName().contains(nameTrait) && !IGNORED_FIELDS.contains(f.getName()))
.collect(Collectors.toList()));
} else {
fields = declaredFields;
}
}
return fields;
}

@Override
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
if (this.methods == null) {
MethodList<MethodDescription.InDefinedShape> declaredMethods = delegate.getDeclaredMethods();
if (declaredMethods.stream()
.anyMatch(m -> m.getName().contains(nameTrait) || IGNORED_METHODS.contains(m.getName()))) {
// Remove dynamic method tokens generated by SkyWalking
methods = new MethodList.Explicit<>(declaredMethods.stream()
.filter(m -> !m.getName().contains(nameTrait) && !IGNORED_METHODS.contains(m.getName()))
.collect(Collectors.toList()));
} else {
methods = declaredMethods;
}
}
return methods;
}

@Override
public boolean isAssignableTo(Class<?> type) {
// ignore interface added by SkyWalking
if (IGNORED_INTERFACES.contains(type.getName())) {
return false;
}
return delegate.isAssignableTo(type);
}

@Override
public boolean isAccessibleTo(TypeDescription typeDescription) {
// ignore interface added by SkyWalking
if (IGNORED_INTERFACES.contains(typeDescription.getName())) {
return false;
}
return delegate.isAccessibleTo(typeDescription);
}

@Override
public RecordComponentList<RecordComponentDescription.InDefinedShape> getRecordComponents() {
return delegate.getRecordComponents();
}

@Override
public TypeDescription getComponentType() {
return delegate.getComponentType();
}

@Override
public TypeDescription getDeclaringType() {
return delegate.getDeclaringType();
}

@Override
public TypeList getDeclaredTypes() {
return delegate.getDeclaredTypes();
}

@Override
public MethodDescription.InDefinedShape getEnclosingMethod() {
return delegate.getEnclosingMethod();
}

@Override
public TypeDescription getEnclosingType() {
return delegate.getEnclosingType();
}

@Override
public String getSimpleName() {
return delegate.getSimpleName();
}

@Override
public String getCanonicalName() {
return delegate.getCanonicalName();
}

@Override
public boolean isAnonymousType() {
return delegate.isAnonymousType();
}

@Override
public boolean isLocalType() {
return delegate.isLocalType();
}

@Override
public PackageDescription getPackage() {
return delegate.getPackage();
}

@Override
public TypeDescription getNestHost() {
return delegate.getNestHost();
}

@Override
public TypeList getNestMembers() {
return delegate.getNestMembers();
}

@Override
public TypeList getPermittedSubtypes() {
return delegate.getPermittedSubtypes();
}

@Override
public String getDescriptor() {
return delegate.getDescriptor();
}

@Override
public String getName() {
return delegate.getName();
}

@Override
public TypeList.Generic getTypeVariables() {
return delegate.getTypeVariables();
}

@Override
public AnnotationList getDeclaredAnnotations() {
return delegate.getDeclaredAnnotations();
}

@Override
public Generic getSuperClass() {
return delegate.getSuperClass();
}

@Override
public StackSize getStackSize() {
return delegate.getStackSize();
}

@Override
public boolean isArray() {
return delegate.isArray();
}

@Override
public boolean isRecord() {
return delegate.isRecord();
}

@Override
public boolean isPrimitive() {
return delegate.isPrimitive();
}

@Override
public int getModifiers() {
return delegate.getModifiers();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*/
public class SWClassFileLocator implements ClassFileLocator {
private static final ILog LOGGER = LogManager.getLogger(SWClassFileLocator.class);
private static final String[] TYPE_NAME_TRAITS = {"auxiliary$", "ByteBuddy$", "sw$"};
private static final String[] TYPE_NAME_TRAITS = {"auxiliary$", "ByteBuddy$", "$sw$"};
private static final int DEFAULT_TIMEOUT_SECONDS = 2;

private final ForInstrumentation.ClassLoadingDelegate classLoadingDelegate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

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

public class BizFoo {
public class BizFoo implements BizInterface {

private String name;

Expand Down
Loading

0 comments on commit db54c65

Please sign in to comment.