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

Merged
merged 26 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c911faa
lambda
SylvainJuge Jan 20, 2025
26e79f9
inject proxies for indy
SylvainJuge Jan 20, 2025
7f3ae3b
kotlinx + integration tests
SylvainJuge Jan 21, 2025
9183992
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Jan 21, 2025
29c99dd
inject missing classes for indy
SylvainJuge Jan 21, 2025
45ead47
do not inject proxies for lambda
SylvainJuge Jan 21, 2025
ba30ba8
move back comment in the expected location
SylvainJuge Jan 21, 2025
eb945b8
remove duplication
SylvainJuge Jan 22, 2025
eba7e20
add jpms compatible class file transformer
SylvainJuge Jan 22, 2025
7a1cbc7
remove lambda java9 module
SylvainJuge Jan 22, 2025
590f86e
fix pebkc
SylvainJuge Jan 22, 2025
a8cbb24
fix lambda instrumentation, must not redefine them
SylvainJuge Jan 22, 2025
403caaa
fix javadoc
SylvainJuge Jan 22, 2025
506cc62
fix things again
SylvainJuge Jan 22, 2025
da1c13d
fix it again
SylvainJuge Jan 22, 2025
c4350d0
make the comments a bit better
SylvainJuge Jan 23, 2025
1d54a14
fix typo
SylvainJuge Jan 23, 2025
03aa810
avoid injecting classes for kotlinxcoroutines
SylvainJuge Jan 24, 2025
2ed4e96
remove commented code
SylvainJuge Jan 24, 2025
77a4f00
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Jan 27, 2025
d9cd13f
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Jan 29, 2025
5e2ef89
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Jan 30, 2025
20856e3
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Jan 31, 2025
a38763f
revert commented code that was there for a reason
SylvainJuge Jan 31, 2025
b77428c
add LambdaTransformer interface + rename things
SylvainJuge Jan 31, 2025
bc785b9
fix typos and comments
SylvainJuge Jan 31, 2025
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
* 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(
Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't immediately get why you wanted to change this, but I guess it is to reduce the helper classes and push more logic into the agent code. I think this is good idea and getting rid for the extra module for java9 is nice. What I'd change is that I'd probably also rename ClassFileTransformerHolder as it is only for lambdas as you noted. Also I'd consider not using ClassFileTransformer here. Passing the targetClass argument to the transform method confused me a bit as transform should have the class there only when retransforming/redefining which we aren't doing here. You could have an interface in ClassFileTransformerHolder that takes the necessary arguments. 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 agree on the ClassFileTransformerHolder rename as it's definitely only used for lambdas.

I initially tried to simplify the design/API but that requires to introduce a new interface, so I thought maybe keep reusing ClassFileTransformer could be an acceptable compromise, but I agree that it might be a bit confusing as the implementations details leak into the callers as shown by the quantity of comments to use it the way it's intended.

I'll do another attempt by introducing a new interface to see if it helps improve this.

targetClass.getClassLoader(), slashClassName, targetClass, null, classBytes);
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");
}
}
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