Add an option to mark indy converted instrumentations (#13665)

This commit is contained in:
Lauri Tulmin 2025-04-10 06:03:45 +03:00 committed by GitHub
parent 4855626d53
commit 818f73f5fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 251 additions and 5 deletions

View File

@ -10,10 +10,12 @@ import static java.util.Arrays.asList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import java.util.List;
@AutoService(InstrumentationModule.class)
public class PekkoHttpClientInstrumentationModule extends InstrumentationModule {
public class PekkoHttpClientInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public PekkoHttpClientInstrumentationModule() {
super("pekko-http", "pekko-http-1.0", "pekko-http-client");
}
@ -22,4 +24,9 @@ public class PekkoHttpClientInstrumentationModule extends InstrumentationModule
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new HttpExtClientInstrumentation(), new PoolMasterActorInstrumentation());
}
@Override
public boolean isIndyReady() {
return true;
}
}

View File

@ -75,4 +75,14 @@ public interface ExperimentalInstrumentationModule {
default Map<JavaModule, List<String>> jpmsModulesToOpen() {
return Collections.emptyMap();
}
/**
* Signals that the advice in this module is ready to be used with indy instrumentation and the
* automatic advice conversion doesn't need to be applied.
*
* @return true if module is ready to be used with indy instrumentation.
*/
default boolean isIndyReady() {
return false;
}
}

View File

@ -0,0 +1,219 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
import static io.opentelemetry.javaagent.tooling.instrumentation.indy.ForceDynamicallyTypedAssignReturnedFactory.replaceAnnotationValue;
import javax.annotation.Nonnull;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.annotation.AnnotationValue;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.method.ParameterList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.pool.TypePool;
import org.jetbrains.annotations.NotNull;
/**
* Pool strategy that sets "inline" attribute to false on {@link Advice.OnMethodEnter} and {@link
* Advice.OnMethodExit} annotations.
*/
class AdviceUninliningPoolStrategy implements AgentBuilder.PoolStrategy {
private final AgentBuilder.PoolStrategy poolStrategy;
public AdviceUninliningPoolStrategy(AgentBuilder.PoolStrategy poolStrategy) {
this.poolStrategy = poolStrategy;
}
@NotNull
@Override
public TypePool typePool(@NotNull ClassFileLocator classFileLocator, ClassLoader classLoader) {
TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader);
return new TypePoolWrapper(typePool);
}
@NotNull
@Override
public TypePool typePool(
@NotNull ClassFileLocator classFileLocator, ClassLoader classLoader, @NotNull String name) {
TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader, name);
return new TypePoolWrapper(typePool);
}
private static class TypePoolWrapper implements TypePool {
private final TypePool typePool;
public TypePoolWrapper(TypePool typePool) {
this.typePool = typePool;
}
@NotNull
@Override
public Resolution describe(@NotNull String name) {
Resolution resolution = typePool.describe(name);
return new Resolution() {
@Override
public boolean isResolved() {
return resolution.isResolved();
}
@NotNull
@Override
public TypeDescription resolve() {
TypeDescription typeDescription = resolution.resolve();
return new TypeDescription.AbstractBase.OfSimpleType.WithDelegation() {
@NotNull
@Override
public String getName() {
return typeDescription.getName();
}
@NotNull
@Override
protected TypeDescription delegate() {
return typeDescription;
}
@NotNull
@Override
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
MethodList<MethodDescription.InDefinedShape> methods = super.getDeclaredMethods();
class MethodListWrapper
extends MethodList.AbstractBase<MethodDescription.InDefinedShape> {
@Override
public MethodDescription.InDefinedShape get(int index) {
return new MethodDescriptionWrapper(methods.get(index));
}
@Override
public int size() {
return methods.size();
}
}
return new MethodListWrapper();
}
};
}
};
}
@Override
public void clear() {
typePool.clear();
}
}
private static class MethodDescriptionWrapper extends DelegatingMethodDescription {
MethodDescriptionWrapper(MethodDescription.InDefinedShape method) {
super(method);
}
@NotNull
@Override
public AnnotationList getDeclaredAnnotations() {
AnnotationList annotations = method.getDeclaredAnnotations();
class AnnotationListWrapper extends AnnotationList.AbstractBase {
@Override
public AnnotationDescription get(int index) {
AnnotationDescription annotation = annotations.get(index);
String annotationTypeName = annotation.getAnnotationType().getActualName();
// we are only interested in OnMethodEnter and OnMethodExit annotations
if (!Advice.OnMethodEnter.class.getName().equals(annotationTypeName)
&& !Advice.OnMethodExit.class.getName().equals(annotationTypeName)) {
return annotation;
}
// replace value for "inline" attribute with false
return replaceAnnotationValue(
annotation, "inline", oldVal -> AnnotationValue.ForConstant.of(false));
}
@Override
public int size() {
return annotations.size();
}
}
return new AnnotationListWrapper();
}
}
private static class DelegatingMethodDescription
extends MethodDescription.InDefinedShape.AbstractBase {
protected final MethodDescription.InDefinedShape method;
DelegatingMethodDescription(MethodDescription.InDefinedShape method) {
this.method = method;
}
@Nonnull
@Override
public TypeDescription getDeclaringType() {
return method.getDeclaringType();
}
@NotNull
@Override
public TypeDescription.Generic getReturnType() {
return method.getReturnType();
}
@NotNull
@Override
public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
return method.getParameters();
}
@NotNull
@Override
public TypeList.Generic getExceptionTypes() {
return method.getExceptionTypes();
}
@Override
public AnnotationValue<?, ?> getDefaultValue() {
return method.getDefaultValue();
}
@NotNull
@Override
public String getInternalName() {
return method.getInternalName();
}
@NotNull
@Override
public TypeList.Generic getTypeVariables() {
return method.getTypeVariables();
}
@Override
public int getModifiers() {
return method.getModifiers();
}
@NotNull
@Override
public AnnotationList getDeclaredAnnotations() {
return method.getDeclaredAnnotations();
}
}
}

View File

@ -68,7 +68,6 @@ public class ForceDynamicallyTypedAssignReturnedFactory implements Advice.PostPr
}
private static AnnotationDescription forceDynamicTyping(AnnotationDescription anno) {
String name = anno.getAnnotationType().getName();
if (name.equals(TO_FIELD_TYPENAME)
|| name.equals(TO_ARGUMENT_TYPENAME)
@ -101,7 +100,7 @@ public class ForceDynamicallyTypedAssignReturnedFactory implements Advice.PostPr
return anno;
}
private static AnnotationDescription replaceAnnotationValue(
static AnnotationDescription replaceAnnotationValue(
AnnotationDescription anno,
String propertyName,
Function<AnnotationValue<?, ?>, AnnotationValue<?, ?>> valueMapper) {

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.tooling.instrumentation.indy;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.tooling.bytebuddy.ExceptionHandlers;
import java.io.FileOutputStream;
import java.io.IOException;
@ -25,11 +26,15 @@ public final class IndyTypeTransformerImpl implements TypeTransformer {
private final Advice.WithCustomMapping adviceMapping;
private AgentBuilder.Identified.Extendable agentBuilder;
private final InstrumentationModule instrumentationModule;
private final boolean transformAdvice;
public IndyTypeTransformerImpl(
AgentBuilder.Identified.Extendable agentBuilder, InstrumentationModule module) {
this.agentBuilder = agentBuilder;
this.instrumentationModule = module;
this.transformAdvice =
!(instrumentationModule instanceof ExperimentalInstrumentationModule)
|| !((ExperimentalInstrumentationModule) instrumentationModule).isIndyReady();
this.adviceMapping =
Advice.withCustomMapping()
.with(
@ -44,17 +49,23 @@ public final class IndyTypeTransformerImpl implements TypeTransformer {
@Override
public void applyAdviceToMethod(
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName) {
// default strategy used by AgentBuilder.Transformer.ForAdvice
AgentBuilder.PoolStrategy poolStrategy = AgentBuilder.PoolStrategy.Default.FAST;
agentBuilder =
agentBuilder.transform(
new AgentBuilder.Transformer.ForAdvice(adviceMapping)
.advice(methodMatcher, adviceClassName)
// advice transformation already performs uninlining
.with(
transformAdvice ? poolStrategy : new AdviceUninliningPoolStrategy(poolStrategy))
.include(getAdviceLocator(instrumentationModule.getClass().getClassLoader()))
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler()));
}
private static ClassFileLocator getAdviceLocator(ClassLoader classLoader) {
private ClassFileLocator getAdviceLocator(ClassLoader classLoader) {
ClassFileLocator classFileLocator = ClassFileLocator.ForClassLoader.of(classLoader);
return new AdviceLocator(classFileLocator);
return transformAdvice ? new AdviceLocator(classFileLocator) : classFileLocator;
}
@Override