Skip to content

Commit 4ad4490

Browse files
laurittrask
andauthored
Faster type matching (#5724)
* Faster type matching * make findLoadedClass accessible on java17 * enable jaxrs instrumentation for quarkus test * fix websphere * fix muzzle * javadoc formating * ignore classes that are know to fail to load for virtual field transforms * add back jaxrs and jaxws annotation instrumentations * Apply suggestions from code review Co-authored-by: Trask Stalnaker <[email protected]> * fix compile error * comments * replace deprecated method usage * add comment * add an spi to get access to bootstrap proxy from muzzle module Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 496c6cf commit 4ad4490

File tree

18 files changed

+741
-125
lines changed

18 files changed

+741
-125
lines changed

instrumentation/external-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extannotations/ExternalAnnotationInstrumentation.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package io.opentelemetry.javaagent.instrumentation.extannotations;
77

88
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9-
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
109
import static io.opentelemetry.javaagent.instrumentation.extannotations.ExternalAnnotationSingletons.instrumenter;
1110
import static java.util.logging.Level.WARNING;
1211
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
@@ -100,7 +99,7 @@ public ElementMatcher<ClassLoader> classLoaderOptimization() {
10099

101100
@Override
102101
public ElementMatcher<TypeDescription> typeMatcher() {
103-
return hasSuperType(declaresMethod(isAnnotatedWith(traceAnnotationMatcher)));
102+
return declaresMethod(isAnnotatedWith(traceAnnotationMatcher));
104103
}
105104

106105
@Override

instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public List<TypeInstrumentation> typeInstrumentations() {
3434
return asList(
3535
new BootDelegationInstrumentation(),
3636
new LoadInjectedClassInstrumentation(),
37-
new ResourceInjectionInstrumentation());
37+
new ResourceInjectionInstrumentation(),
38+
new DefineClassInstrumentation());
3839
}
3940
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.internal.classloader;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
10+
11+
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper;
12+
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler.DefineClassContext;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import java.nio.ByteBuffer;
16+
import java.security.ProtectionDomain;
17+
import net.bytebuddy.asm.Advice;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
21+
public class DefineClassInstrumentation implements TypeInstrumentation {
22+
23+
@Override
24+
public ElementMatcher<TypeDescription> typeMatcher() {
25+
return named("java.lang.ClassLoader");
26+
}
27+
28+
@Override
29+
public void transform(TypeTransformer transformer) {
30+
transformer.applyAdviceToMethod(
31+
named("defineClass")
32+
.and(
33+
takesArguments(
34+
String.class, byte[].class, int.class, int.class, ProtectionDomain.class)),
35+
DefineClassInstrumentation.class.getName() + "$DefineClassAdvice");
36+
transformer.applyAdviceToMethod(
37+
named("defineClass")
38+
.and(takesArguments(String.class, ByteBuffer.class, ProtectionDomain.class)),
39+
DefineClassInstrumentation.class.getName() + "$DefineClassAdvice2");
40+
}
41+
42+
@SuppressWarnings("unused")
43+
public static class DefineClassAdvice {
44+
@Advice.OnMethodEnter(suppress = Throwable.class)
45+
public static DefineClassContext onEnter(
46+
@Advice.This ClassLoader classLoader,
47+
@Advice.Argument(0) String className,
48+
@Advice.Argument(1) byte[] classBytes,
49+
@Advice.Argument(2) int offset,
50+
@Advice.Argument(3) int length) {
51+
return DefineClassHelper.beforeDefineClass(
52+
classLoader, className, classBytes, offset, length);
53+
}
54+
55+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
56+
public static void onExit(@Advice.Enter DefineClassContext context) {
57+
DefineClassHelper.afterDefineClass(context);
58+
}
59+
}
60+
61+
@SuppressWarnings("unused")
62+
public static class DefineClassAdvice2 {
63+
@Advice.OnMethodEnter(suppress = Throwable.class)
64+
public static DefineClassContext onEnter(
65+
@Advice.This ClassLoader classLoader,
66+
@Advice.Argument(0) String className,
67+
@Advice.Argument(1) ByteBuffer classBytes) {
68+
return DefineClassHelper.beforeDefineClass(classLoader, className, classBytes);
69+
}
70+
71+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
72+
public static void onExit(@Advice.Enter DefineClassContext context) {
73+
DefineClassHelper.afterDefineClass(context);
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.bootstrap;
7+
8+
import java.nio.ByteBuffer;
9+
10+
public class DefineClassHelper {
11+
12+
/** Helper class for {@code ClassLoader.defineClass} callbacks. */
13+
public interface Handler {
14+
DefineClassContext beforeDefineClass(
15+
ClassLoader classLoader, String className, byte[] classBytes, int offset, int length);
16+
17+
void afterDefineClass(DefineClassContext context);
18+
19+
/** Context returned from {@code beforeDefineClass} and passed to {@code afterDefineClass}. */
20+
interface DefineClassContext {
21+
void exit();
22+
}
23+
}
24+
25+
private static volatile Handler handler;
26+
27+
public static Handler.DefineClassContext beforeDefineClass(
28+
ClassLoader classLoader, String className, byte[] classBytes, int offset, int length) {
29+
return handler.beforeDefineClass(classLoader, className, classBytes, offset, length);
30+
}
31+
32+
public static Handler.DefineClassContext beforeDefineClass(
33+
ClassLoader classLoader, String className, ByteBuffer byteBuffer) {
34+
// see how ClassLoader handles ByteBuffer
35+
// https://github.com/openjdk/jdk11u/blob/487c3344fee3502b4843e7e11acceb77ad16100c/src/java.base/share/classes/java/lang/ClassLoader.java#L1095
36+
int length = byteBuffer.remaining();
37+
if (byteBuffer.hasArray()) {
38+
return beforeDefineClass(
39+
classLoader,
40+
className,
41+
byteBuffer.array(),
42+
byteBuffer.position() + byteBuffer.arrayOffset(),
43+
length);
44+
} else {
45+
byte[] classBytes = new byte[length];
46+
byteBuffer.duplicate().get(classBytes);
47+
return beforeDefineClass(classLoader, className, classBytes, 0, length);
48+
}
49+
}
50+
51+
public static void afterDefineClass(Handler.DefineClassContext context) {
52+
handler.afterDefineClass(context);
53+
}
54+
55+
/**
56+
* Sets the {@link Handler} with callbacks to execute when {@code ClassLoader.defineClass} is
57+
* called.
58+
*/
59+
public static void internalSetHandler(Handler handler) {
60+
if (DefineClassHelper.handler != null) {
61+
// Only possible by misuse of this API, just ignore.
62+
return;
63+
}
64+
DefineClassHelper.handler = handler;
65+
}
66+
67+
private DefineClassHelper() {}
68+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
2525
import io.opentelemetry.javaagent.bootstrap.BootstrapPackagePrefixesHolder;
2626
import io.opentelemetry.javaagent.bootstrap.ClassFileTransformerHolder;
27+
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper;
2728
import io.opentelemetry.javaagent.extension.AgentListener;
2829
import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
2930
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
@@ -120,6 +121,7 @@ public static ResettableClassFileTransformer installBytebuddyAgent(
120121
Config config = Config.get();
121122

122123
setBootstrapPackages(config);
124+
setDefineClassHandler();
123125

124126
// If noop OpenTelemetry is enabled, autoConfiguredSdk will be null and AgentListeners are not
125127
// called
@@ -209,6 +211,10 @@ private static void setBootstrapPackages(Config config) {
209211
BootstrapPackagePrefixesHolder.setBoostrapPackagePrefixes(builder.build());
210212
}
211213

214+
private static void setDefineClassHandler() {
215+
DefineClassHelper.internalSetHandler(DefineClassHandler.INSTANCE);
216+
}
217+
212218
private static void runBeforeAgentListeners(
213219
Iterable<AgentListener> agentListeners,
214220
Config config,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling;
7+
8+
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler;
9+
import java.nio.charset.StandardCharsets;
10+
import org.objectweb.asm.ClassReader;
11+
12+
public class DefineClassHandler implements Handler {
13+
public static final DefineClassHandler INSTANCE = new DefineClassHandler();
14+
private static final ThreadLocal<DefineClassContextImpl> defineClassContext =
15+
ThreadLocal.withInitial(() -> DefineClassContextImpl.NOP);
16+
17+
private DefineClassHandler() {}
18+
19+
@Override
20+
public DefineClassContext beforeDefineClass(
21+
ClassLoader classLoader, String className, byte[] classBytes, int offset, int length) {
22+
// with OpenJ9 class data sharing we don't get real class bytes
23+
if (classBytes == null
24+
|| (classBytes.length == 40
25+
&& new String(classBytes, StandardCharsets.ISO_8859_1)
26+
.startsWith("J9ROMCLASSCOOKIE"))) {
27+
return null;
28+
}
29+
30+
DefineClassContextImpl context = null;
31+
// attempt to load super types of currently loaded class
32+
// for a class to be loaded all of its super types must be loaded, here we just change the order
33+
// of operations and load super types before transforming the bytes for current class so that
34+
// we could use these super types for resolving the advice that needs to be applied to current
35+
// class
36+
try {
37+
ClassReader cr = new ClassReader(classBytes, offset, length);
38+
String superName = cr.getSuperName();
39+
if (superName != null) {
40+
Class.forName(superName.replace('/', '.'), false, classLoader);
41+
}
42+
String[] interfaces = cr.getInterfaces();
43+
for (String interfaceName : interfaces) {
44+
Class.forName(interfaceName.replace('/', '.'), false, classLoader);
45+
}
46+
} catch (Throwable throwable) {
47+
// loading of super class or interface failed
48+
// mark current class as failed to skip matching and transforming it
49+
// we'll let defining the class proceed as usual so that it would throw the same exception as
50+
// it does when running without the agent
51+
context = DefineClassContextImpl.enter(className);
52+
}
53+
return context;
54+
}
55+
56+
@Override
57+
public void afterDefineClass(DefineClassContext context) {
58+
if (context != null) {
59+
context.exit();
60+
}
61+
}
62+
63+
/**
64+
* Detect whether loading the specified class is known to fail.
65+
*
66+
* @param className class being loaded
67+
* @return true if it is known that loading class with given name will fail
68+
*/
69+
public static boolean isFailedClass(String className) {
70+
DefineClassContextImpl context = defineClassContext.get();
71+
return context.failedClassName != null && context.failedClassName.equals(className);
72+
}
73+
74+
private static class DefineClassContextImpl implements DefineClassContext {
75+
private static final DefineClassContextImpl NOP = new DefineClassContextImpl();
76+
77+
private final DefineClassContextImpl previous;
78+
private final String failedClassName;
79+
80+
private DefineClassContextImpl() {
81+
previous = null;
82+
failedClassName = null;
83+
}
84+
85+
private DefineClassContextImpl(DefineClassContextImpl previous, String failedClassName) {
86+
this.previous = previous;
87+
this.failedClassName = failedClassName;
88+
}
89+
90+
static DefineClassContextImpl enter(String failedClassName) {
91+
DefineClassContextImpl previous = defineClassContext.get();
92+
DefineClassContextImpl context = new DefineClassContextImpl(previous, failedClassName);
93+
defineClassContext.set(context);
94+
return context;
95+
}
96+
97+
@Override
98+
public void exit() {
99+
defineClassContext.set(previous);
100+
}
101+
}
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.bootstrap;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.javaagent.tooling.Utils;
10+
import io.opentelemetry.javaagent.tooling.muzzle.BootstrapProxyProvider;
11+
12+
@AutoService(BootstrapProxyProvider.class)
13+
public class BootstrapProxyProviderImpl implements BootstrapProxyProvider {
14+
15+
@Override
16+
public ClassLoader getBootstrapProxy() {
17+
return Utils.getBootstrapProxy();
18+
}
19+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/FieldBackedImplementationInstaller.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
1919
import io.opentelemetry.javaagent.tooling.instrumentation.InstrumentationModuleInstaller;
2020
import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
21+
import io.opentelemetry.javaagent.tooling.util.IgnoreFailedTypeMatcher;
22+
import io.opentelemetry.javaagent.tooling.util.NamedMatcher;
2123
import java.lang.instrument.Instrumentation;
2224
import java.util.Collection;
2325
import java.util.HashSet;
@@ -28,6 +30,7 @@
2830
import net.bytebuddy.asm.AsmVisitorWrapper;
2931
import net.bytebuddy.description.type.TypeDescription;
3032
import net.bytebuddy.dynamic.DynamicType;
33+
import net.bytebuddy.matcher.ElementMatcher;
3134
import net.bytebuddy.utility.JavaModule;
3235

3336
/**
@@ -196,9 +199,15 @@ public AgentBuilder.Identified.Extendable injectFields(
196199
* For each virtual field defined in a current instrumentation we create an agent builder
197200
* that injects necessary fields.
198201
*/
202+
ElementMatcher<TypeDescription> typeMatcher =
203+
new NamedMatcher<>(
204+
"VirtualField",
205+
new IgnoreFailedTypeMatcher(
206+
not(isAbstract()).and(hasSuperType(named(entry.getKey())))));
207+
199208
builder =
200209
builder
201-
.type(not(isAbstract()).and(hasSuperType(named(entry.getKey()))))
210+
.type(typeMatcher)
202211
.and(safeToInjectFieldsMatcher())
203212
.and(InstrumentationModuleInstaller.NOT_DECORATOR_MATCHER)
204213
.transform(NoOpTransformer.INSTANCE);

0 commit comments

Comments
 (0)