Skip to content

Commit 81f6a3a

Browse files
authored
Faster type matching (open-telemetry#8525)
1 parent fa5d174 commit 81f6a3a

File tree

14 files changed

+560
-33
lines changed

14 files changed

+560
-33
lines changed

instrumentation/internal/internal-lambda/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/lambda/InnerClassLambdaMetafactoryInstrumentation.java

+51
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import static net.bytebuddy.matcher.ElementMatchers.named;
99

1010
import com.google.errorprone.annotations.CanIgnoreReturnValue;
11+
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper;
12+
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler.DefineClassContext;
1113
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1214
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import net.bytebuddy.asm.Advice;
1316
import net.bytebuddy.asm.AsmVisitorWrapper;
1417
import net.bytebuddy.description.field.FieldDescription;
1518
import net.bytebuddy.description.field.FieldList;
@@ -62,6 +65,24 @@ public ClassVisitor wrap(
6265
classVisitor, instrumentedType.getInternalName());
6366
}
6467
}));
68+
69+
transformer.applyAdviceToMethod(
70+
named("spinInnerClass"),
71+
InnerClassLambdaMetafactoryInstrumentation.class.getName()
72+
+ (hasInterfaceClassField() ? "$LambdaJdk17Advice" : "$LambdaAdvice"));
73+
}
74+
75+
@SuppressWarnings("ReturnValueIgnored")
76+
private static boolean hasInterfaceClassField() {
77+
try {
78+
Class<?> clazz = Class.forName("java.lang.invoke.AbstractValidatingLambdaMetafactory");
79+
clazz.getDeclaredField("interfaceClass");
80+
return true;
81+
} catch (NoSuchFieldException exception) {
82+
return false;
83+
} catch (ClassNotFoundException exception) {
84+
throw new IllegalStateException(exception);
85+
}
6586
}
6687

6788
private static class MetaFactoryClassVisitor extends ClassVisitor {
@@ -117,4 +138,34 @@ public void visitMethodInsn(
117138
return mv;
118139
}
119140
}
141+
142+
@SuppressWarnings("unused")
143+
public static class LambdaAdvice {
144+
145+
@Advice.OnMethodEnter
146+
public static DefineClassContext onEnter(
147+
@Advice.FieldValue("samBase") Class<?> lambdaInterface) {
148+
return DefineClassHelper.beforeDefineLambdaClass(lambdaInterface);
149+
}
150+
151+
@Advice.OnMethodExit(onThrowable = Throwable.class)
152+
public static void onExit(@Advice.Enter DefineClassContext context) {
153+
DefineClassHelper.afterDefineClass(context);
154+
}
155+
}
156+
157+
@SuppressWarnings("unused")
158+
public static class LambdaJdk17Advice {
159+
160+
@Advice.OnMethodEnter
161+
public static DefineClassContext onEnter(
162+
@Advice.FieldValue("interfaceClass") Class<?> lambdaInterface) {
163+
return DefineClassHelper.beforeDefineLambdaClass(lambdaInterface);
164+
}
165+
166+
@Advice.OnMethodExit(onThrowable = Throwable.class)
167+
public static void onExit(@Advice.Enter DefineClassContext context) {
168+
DefineClassHelper.afterDefineClass(context);
169+
}
170+
}
120171
}

javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/DefineClassHelper.java

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public interface Handler {
1414
DefineClassContext beforeDefineClass(
1515
ClassLoader classLoader, String className, byte[] classBytes, int offset, int length);
1616

17+
DefineClassContext beforeDefineLambdaClass(Class<?> lambdaInterface);
18+
1719
void afterDefineClass(DefineClassContext context);
1820

1921
/** Context returned from {@code beforeDefineClass} and passed to {@code afterDefineClass}. */
@@ -48,6 +50,10 @@ public static Handler.DefineClassContext beforeDefineClass(
4850
}
4951
}
5052

53+
public static Handler.DefineClassContext beforeDefineLambdaClass(Class<?> lambdaInterface) {
54+
return handler.beforeDefineLambdaClass(lambdaInterface);
55+
}
56+
5157
public static void afterDefineClass(Handler.DefineClassContext context) {
5258
handler.afterDefineClass(context);
5359
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java

+34-14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.List;
1313
import java.util.concurrent.CopyOnWriteArrayList;
1414
import java.util.concurrent.atomic.AtomicInteger;
15+
import java.util.concurrent.locks.Lock;
16+
import java.util.concurrent.locks.ReentrantReadWriteLock;
1517
import net.bytebuddy.matcher.ElementMatcher;
1618

1719
class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
@@ -62,12 +64,14 @@ private static boolean hasResources(ClassLoader cl, String... resources) {
6264
}
6365

6466
private static class Manager {
65-
private static final BitSet EMPTY = new BitSet(0);
6667
static final Manager INSTANCE = new Manager();
6768
private final List<ClassLoaderHasClassesNamedMatcher> matchers = new CopyOnWriteArrayList<>();
68-
// each matcher gets a bit in BitSet, that bit indicates whether current matcher matched or not
69-
// for given class loader
69+
// each matcher gets a two bits in BitSet, that first bit indicates whether current matcher has
70+
// been run for given class loader and the second whether it matched or not
7071
private final Cache<ClassLoader, BitSet> enabled = Cache.weak();
72+
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
73+
private final Lock readLock = lock.readLock();
74+
private final Lock writeLock = lock.writeLock();
7175
private volatile boolean matchCalled = false;
7276

7377
Manager() {
@@ -83,20 +87,36 @@ void add(ClassLoaderHasClassesNamedMatcher matcher) {
8387

8488
boolean match(ClassLoaderHasClassesNamedMatcher matcher, ClassLoader cl) {
8589
matchCalled = true;
86-
BitSet set = enabled.get(cl);
87-
if (set == null) {
88-
set = new BitSet(counter.get());
89-
for (ClassLoaderHasClassesNamedMatcher m : matchers) {
90-
if (hasResources(cl, m.resources)) {
91-
// set the bit corresponding to the matcher when it matched
92-
set.set(m.index);
90+
BitSet set = enabled.computeIfAbsent(cl, (unused) -> new BitSet(counter.get() * 2));
91+
int matcherRunBit = 2 * matcher.index;
92+
int matchedBit = matcherRunBit + 1;
93+
readLock.lock();
94+
try {
95+
if (!set.get(matcherRunBit)) {
96+
// read lock needs to be released before upgrading to write lock
97+
readLock.unlock();
98+
// we do the resource presence check outside the lock to keep the time we need to hold
99+
// the write lock minimal
100+
boolean matches = hasResources(cl, matcher.resources);
101+
writeLock.lock();
102+
try {
103+
if (!set.get(matcherRunBit)) {
104+
if (matches) {
105+
set.set(matchedBit);
106+
}
107+
set.set(matcherRunBit);
108+
}
109+
} finally {
110+
// downgrading the write lock to the read lock
111+
readLock.lock();
112+
writeLock.unlock();
93113
}
94114
}
95-
enabled.put(cl, set.isEmpty() ? EMPTY : set);
96-
} else if (set.isEmpty()) {
97-
return false;
115+
116+
return set.get(matchedBit);
117+
} finally {
118+
readLock.unlock();
98119
}
99-
return set.get(matcher.index);
100120
}
101121
}
102122
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/SafeErasureMatcher.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static io.opentelemetry.javaagent.extension.matcher.Utils.safeTypeDefinitionName;
99
import static java.util.logging.Level.FINE;
1010

11+
import io.opentelemetry.javaagent.extension.matcher.internal.DelegatingMatcher;
1112
import java.util.logging.Logger;
1213
import javax.annotation.Nullable;
1314
import net.bytebuddy.description.type.TypeDefinition;
@@ -24,7 +25,8 @@
2425
* @param <T> The type of the matched entity.
2526
* @see net.bytebuddy.matcher.ErasureMatcher
2627
*/
27-
class SafeErasureMatcher<T extends TypeDefinition> extends ElementMatcher.Junction.AbstractBase<T> {
28+
class SafeErasureMatcher<T extends TypeDefinition> extends ElementMatcher.Junction.AbstractBase<T>
29+
implements DelegatingMatcher {
2830

2931
private static final Logger logger = Logger.getLogger(SafeErasureMatcher.class.getName());
3032

@@ -88,4 +90,9 @@ public boolean equals(@Nullable Object obj) {
8890
public int hashCode() {
8991
return matcher.hashCode();
9092
}
93+
94+
@Override
95+
public ElementMatcher<?> getDelegate() {
96+
return matcher;
97+
}
9198
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/SafeExtendsClassMatcher.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
import static io.opentelemetry.javaagent.extension.matcher.SafeHasSuperTypeMatcher.safeGetSuperClass;
99

10+
import io.opentelemetry.javaagent.extension.matcher.internal.DelegatingSuperTypeMatcher;
1011
import javax.annotation.Nullable;
1112
import net.bytebuddy.description.type.TypeDefinition;
1213
import net.bytebuddy.description.type.TypeDescription;
1314
import net.bytebuddy.matcher.ElementMatcher;
1415

15-
class SafeExtendsClassMatcher extends ElementMatcher.Junction.AbstractBase<TypeDescription> {
16+
class SafeExtendsClassMatcher extends ElementMatcher.Junction.AbstractBase<TypeDescription>
17+
implements DelegatingSuperTypeMatcher {
1618

1719
private final ElementMatcher<TypeDescription.Generic> matcher;
1820

@@ -55,4 +57,9 @@ public boolean equals(@Nullable Object obj) {
5557
public int hashCode() {
5658
return matcher.hashCode();
5759
}
60+
61+
@Override
62+
public ElementMatcher<?> getDelegate() {
63+
return matcher;
64+
}
5865
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/SafeHasSuperTypeMatcher.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static io.opentelemetry.javaagent.extension.matcher.Utils.safeTypeDefinitionName;
1010
import static java.util.logging.Level.FINE;
1111

12+
import io.opentelemetry.javaagent.extension.matcher.internal.DelegatingSuperTypeMatcher;
1213
import java.util.HashSet;
1314
import java.util.Iterator;
1415
import java.util.Set;
@@ -35,7 +36,8 @@
3536
*
3637
* @see net.bytebuddy.matcher.HasSuperTypeMatcher
3738
*/
38-
class SafeHasSuperTypeMatcher extends ElementMatcher.Junction.AbstractBase<TypeDescription> {
39+
class SafeHasSuperTypeMatcher extends ElementMatcher.Junction.AbstractBase<TypeDescription>
40+
implements DelegatingSuperTypeMatcher {
3941

4042
private static final Logger logger = Logger.getLogger(SafeHasSuperTypeMatcher.class.getName());
4143

@@ -136,6 +138,11 @@ public int hashCode() {
136138
return matcher.hashCode();
137139
}
138140

141+
@Override
142+
public ElementMatcher<?> getDelegate() {
143+
return matcher;
144+
}
145+
139146
/**
140147
* TypeDefinition#getInterfaces() produces an iterator which may throw an exception during
141148
* iteration if an interface is absent from the classpath.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.matcher.internal;
7+
8+
import net.bytebuddy.matcher.ElementMatcher;
9+
10+
/**
11+
* Interface for extracting the matcher that the given matcher delegates to.
12+
*
13+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
14+
* at any time.
15+
*/
16+
public interface DelegatingMatcher {
17+
18+
/** Returns the matcher that the current matcher delegates to. */
19+
ElementMatcher<?> getDelegate();
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.matcher.internal;
7+
8+
/**
9+
* Marker interface for delegating matchers that match based on the type hierarchy.
10+
*
11+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
12+
* at any time.
13+
*/
14+
public interface DelegatingSuperTypeMatcher extends DelegatingMatcher {}

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

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import javax.annotation.Nullable;
5555
import net.bytebuddy.ByteBuddy;
5656
import net.bytebuddy.agent.builder.AgentBuilder;
57+
import net.bytebuddy.agent.builder.AgentBuilderUtil;
5758
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
5859
import net.bytebuddy.description.type.TypeDefinition;
5960
import net.bytebuddy.description.type.TypeDescription;
@@ -184,6 +185,7 @@ private static void installBytebuddyAgent(
184185
}
185186
logger.log(FINE, "Installed {0} extension(s)", numberOfLoadedExtensions);
186187

188+
agentBuilder = AgentBuilderUtil.optimize(agentBuilder);
187189
ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
188190
ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer);
189191

0 commit comments

Comments
 (0)