Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

indy instrumentation - leftovers migration #13074

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ plugins {

dependencies {
compileOnly(project(":javaagent-bootstrap"))
implementation(project(":instrumentation:internal:internal-lambda-java9:javaagent"))

testImplementation(project(":javaagent-bootstrap"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
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 io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.utility.JavaModule;

@AutoService(InstrumentationModule.class)
public class LambdaInstrumentationModule extends InstrumentationModule {
public class LambdaInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public LambdaInstrumentationModule() {
super("internal-lambda");
}
Expand All @@ -28,21 +29,16 @@ public boolean defaultEnabled(ConfigProperties config) {
}

@Override
public boolean isIndyModule() {
return false;
public List<String> injectedClassNames() {
return Collections.singletonList(
"io.opentelemetry.javaagent.instrumentation.internal.lambda.LambdaTransformer");
}

@Override
public List<String> getAdditionalHelperClassNames() {
// this instrumentation uses ASM not ByteBuddy so muzzle doesn't automatically add helper
// classes
List<String> classNames = new ArrayList<>();
classNames.add("io.opentelemetry.javaagent.instrumentation.internal.lambda.LambdaTransformer");
if (JavaModule.isSupported()) {
classNames.add(
"io.opentelemetry.javaagent.instrumentation.internal.lambda.Java9LambdaTransformer");
}
return classNames;
return injectedClassNames();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,11 @@

/** Helper class for transforming lambda class bytes. */
public final class LambdaTransformer {
private static final boolean IS_JAVA_9 = isJava9();

private LambdaTransformer() {}

private static boolean isJava9() {
try {
Class.forName("java.lang.Module", false, null);
return true;
} catch (ClassNotFoundException exception) {
return false;
}
}

/**
* Called from {@code java.lang.invoke.InnerClassLambdaMetafactory} to transform lambda class
* Called from {@code java.lang.invoke.InnerClassLambdaMetaFactory} to transform lambda class
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
* bytes.
*/
@SuppressWarnings("unused")
Expand All @@ -34,29 +24,25 @@ public static byte[] transform(byte[] classBytes, String slashClassName, Class<?
if (InjectedClassHelper.isHelperClass(targetClass)) {
return classBytes;
}
ClassFileTransformer transformer = ClassFileTransformerHolder.getLambdaClassFileTransformer();
if (transformer == null) {
return classBytes;
}

ClassFileTransformer transformer = ClassFileTransformerHolder.getClassFileTransformer();
if (transformer != null) {
try {
byte[] result;
if (IS_JAVA_9) {
result =
Java9LambdaTransformer.transform(
transformer, classBytes, slashClassName, targetClass);
} else {
result =
transformer.transform(
targetClass.getClassLoader(), slashClassName, null, null, classBytes);
}
if (result != null) {
return result;
}
} catch (Throwable throwable) {
// sun.instrument.TransformerManager catches Throwable from ClassFileTransformer and ignores
// it, we do the same.
try {
// The 'targetClass' needs to be non-null as it is used in Java9LambdaClassFileTransformer
// to get a reference to the jpms module. However, a null value must be passed to the
// underlying transformer as this code is called when the lambda is defined.
byte[] result =
transformer.transform(
targetClass.getClassLoader(), slashClassName, targetClass, null, classBytes);
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
if (result != null) {
classBytes = result;
}
} catch (Throwable throwable) {
// sun.instrument.TransformerManager catches Throwable from ClassFileTransformer and ignores
// it, we do the same.
}

return classBytes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
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.Arrays;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

/** Instrumentation for methods annotated with {@code WithSpan} annotation. */
@AutoService(InstrumentationModule.class)
public class AnnotationInstrumentationModule extends InstrumentationModule {
public class AnnotationInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {

public AnnotationInstrumentationModule() {
super(
Expand All @@ -26,12 +29,6 @@ public AnnotationInstrumentationModule() {
"opentelemetry-instrumentation-annotations");
}

@Override
public boolean isIndyModule() {
// needs helper classes in the same class loader
return false;
}

@Override
public int order() {
// Run first to ensure other automatic instrumentation is added after and therefore is executed
Expand All @@ -50,4 +47,13 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new WithSpanInstrumentation());
}

@Override
public List<String> injectedClassNames() {
return Arrays.asList(
"io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations.AnnotationSingletons",
"io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations.AnnotationInstrumentationHelper",
"io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations.MethodRequest",
"io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations.MethodRequestCodeAttributesGetter");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of injecting all the helper did you consider adding a proxy for AnnotationInstrumentationHelper similarly to

public void injectClasses(ClassInjector injector) {
// we do not use ByteBuddy Advice dispatching in this instrumentation
// Instead, we manually call GraphqlSingletons via ASM
// Easiest solution to work with indy is to inject an indy-proxy to be invoked
injector
.proxyBuilder("io.opentelemetry.javaagent.instrumentation.graphql.v20_0.GraphqlSingletons")
.inject(InjectionMode.CLASS_ONLY);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we have the choice here, as MethodRequest is being used directly used in WithSpanInstrumentation which uses ASM, also if MethodRequest is being injected then it's also pulling AnnotationSingletons which is using it to be injected as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could replace MethodRequest with Object in method signatures. We had similar issue in indy instrumentation where advice methods could not have helper classes in the signature, see #11873 What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that could work, I will try to see if we can easily do it.

If I understand what you aim to achieve here is only exposing the AnnotationInstrumentationHelper class to the instrumented code through an injected proxy, and then leave other classes called by the real AnnotationInstrumentationHelper class implementation as implementation details.

One of the downsides of this approach is that we loose the type-safety of the method signatures and need to introduce explicit casts to make it work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented it in 03aa810 and there aren't that many compromises on the code, so I think it was again a great suggestion @laurit !

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,21 @@ public final class ClassFileTransformerHolder {

private static volatile ClassFileTransformer classFileTransformer;

public static ClassFileTransformer getClassFileTransformer() {
/**
* get class transformer for defining lambdas
*
* @return class transformer for defining lambdas
*/
public static ClassFileTransformer getLambdaClassFileTransformer() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] this is only used for lambda, and the ClassFileTransformer instances that are used here are very specific to lambda instrumentation, so renaming helps keep it clear.

return classFileTransformer;
}

public static void setClassFileTransformer(ClassFileTransformer transformer) {
/**
* set class transformer for defining lambdas
*
* @param transformer transformer
*/
public static void setLambdaClassFileTransformer(ClassFileTransformer transformer) {
classFileTransformer = transformer;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.tooling;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/** {@link ClassFileTransformer} for lambda instrumentation with java9 jpms module compatibility */
public class Java9LambdaClassFileTransformer implements ClassFileTransformer {

private final ClassFileTransformer delegate;

public Java9LambdaClassFileTransformer(ClassFileTransformer delegate) {
this.delegate = delegate;
}

@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> targetClass,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {

if (targetClass == null) {
// we expect the target class argument to be non-null, however it's only used to resolve
// the JPMS module
throw new IllegalStateException("can't resolve module without class");
}
Module module = targetClass.getModule();

// lambda instrumentation happens only when the lambda is defined, thus the classBeingRedefined
// must be null otherwise we get a partial instrumentation, for example virtual fields are not
// properly applied
return delegate.transform(module, loader, className, null, null, classfileBuffer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.SdkAutoconfigureAccess;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -63,7 +64,6 @@
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilderUtil;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
Expand Down Expand Up @@ -199,9 +199,17 @@ private static void installBytebuddyAgent(
logger.log(FINE, "Installed {0} extension(s)", numberOfLoadedExtensions);

agentBuilder = AgentBuilderUtil.optimize(agentBuilder);
ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
ClassFileTransformer transformer = agentBuilder.installOn(inst);
if (JavaModule.isSupported()) {
// wrapping in a JPMS compliant implementation
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] wrapping at registration time helps to make the consumer transparently use it with java 8 API.

transformer = new Java9LambdaClassFileTransformer(transformer);
} else {
// wrapping in a java 8 compliant transformer
transformer = new LambdaClassFileTransformer(transformer);
}
ClassFileTransformerHolder.setLambdaClassFileTransformer(transformer);

instrumentationInstalled = true;
ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer);

addHttpServerResponseCustomizers(extensionClassLoader);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.tooling;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/** {@link ClassFileTransformer} for lambda instrumentation without jpms modules */
public class LambdaClassFileTransformer implements ClassFileTransformer {

private final ClassFileTransformer delegate;

public LambdaClassFileTransformer(ClassFileTransformer delegate) {
this.delegate = delegate;
}

@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {

// lambda instrumentation happens only when the lambda is defined, thus the classBeingRedefined
// must be null otherwise we get a partial instrumentation, for example virtual fields are not
// properly applied.
return delegate.transform(loader, className, null, null, classfileBuffer);
}
}
1 change: 0 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ include(":instrumentation:internal:internal-class-loader:javaagent")
include(":instrumentation:internal:internal-class-loader:javaagent-integration-tests")
include(":instrumentation:internal:internal-eclipse-osgi-3.6:javaagent")
include(":instrumentation:internal:internal-lambda:javaagent")
include(":instrumentation:internal:internal-lambda-java9:javaagent")
include(":instrumentation:internal:internal-reflection:javaagent")
include(":instrumentation:internal:internal-reflection:javaagent-integration-tests")
include(":instrumentation:internal:internal-url-class-loader:javaagent")
Expand Down
2 changes: 0 additions & 2 deletions testing-common/integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,13 @@ tasks {
includeTestsMatching("InstrumentOldBytecode")
}
include("**/InstrumentOldBytecode.*")
jvmArgs("-Dotel.instrumentation.inline-ibm-resource-level.enabled=false")
}

val testInlineModuleOldBytecodeInstrumentation by registering(Test::class) {
filter {
includeTestsMatching("InstrumentOldBytecode")
}
include("**/InstrumentOldBytecode.*")
jvmArgs("-Dotel.instrumentation.indy-ibm-resource-level.enabled=false")
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
import java.util.List;

@AutoService(InstrumentationModule.class)
public class InlineIbmResourceLevelInstrumentationModule extends InstrumentationModule {
public InlineIbmResourceLevelInstrumentationModule() {
super("inline-ibm-resource-level");
public class IbmResourceLevelInstrumentationModule extends InstrumentationModule {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] there is no need to maintain an inline and indy variants of this instrumentation, so we only need one that is compatible with both.

public IbmResourceLevelInstrumentationModule() {
super("ibm-resource-level");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new InlineResourceLevelInstrumentation());
return singletonList(new ResourceLevelInstrumentation());
}
}
Loading
Loading