-
Notifications
You must be signed in to change notification settings - Fork 871
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
Reduce memory usage for ClassLoaderHasClassesNamedMatcher #7866
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -8,20 +8,29 @@ | |||||||||||||||||||||||||
import io.opentelemetry.instrumentation.api.internal.cache.Cache; | ||||||||||||||||||||||||||
import io.opentelemetry.javaagent.bootstrap.internal.ClassLoaderMatcherCacheHolder; | ||||||||||||||||||||||||||
import io.opentelemetry.javaagent.bootstrap.internal.InClassLoaderMatcher; | ||||||||||||||||||||||||||
import java.util.BitSet; | ||||||||||||||||||||||||||
import java.util.List; | ||||||||||||||||||||||||||
import java.util.concurrent.CopyOnWriteArrayList; | ||||||||||||||||||||||||||
import java.util.concurrent.atomic.AtomicInteger; | ||||||||||||||||||||||||||
import net.bytebuddy.matcher.ElementMatcher; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> { | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private final Cache<ClassLoader, Boolean> cache = Cache.weak(); | ||||||||||||||||||||||||||
// caching is disabled for build time muzzle checks | ||||||||||||||||||||||||||
// this field is set via reflection from ClassLoaderMatcher | ||||||||||||||||||||||||||
static boolean useCache = true; | ||||||||||||||||||||||||||
private static final AtomicInteger counter = new AtomicInteger(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private final String[] resources; | ||||||||||||||||||||||||||
private final int index = counter.getAndIncrement(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
ClassLoaderHasClassesNamedMatcher(String... classNames) { | ||||||||||||||||||||||||||
resources = classNames; | ||||||||||||||||||||||||||
for (int i = 0; i < resources.length; i++) { | ||||||||||||||||||||||||||
resources[i] = resources[i].replace(".", "/") + ".class"; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
ClassLoaderMatcherCacheHolder.addCache(cache); | ||||||||||||||||||||||||||
if (useCache) { | ||||||||||||||||||||||||||
Manager.INSTANCE.add(this); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||
|
@@ -30,10 +39,14 @@ public boolean matches(ClassLoader cl) { | |||||||||||||||||||||||||
// Can't match the bootstrap class loader. | ||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return cache.computeIfAbsent(cl, this::hasResources); | ||||||||||||||||||||||||||
if (useCache) { | ||||||||||||||||||||||||||
return Manager.INSTANCE.match(this, cl); | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
return hasResources(cl, resources); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private boolean hasResources(ClassLoader cl) { | ||||||||||||||||||||||||||
private static boolean hasResources(ClassLoader cl, String... resources) { | ||||||||||||||||||||||||||
boolean priorValue = InClassLoaderMatcher.getAndSet(true); | ||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||
for (String resource : resources) { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is a bug in this for loop, if there is a single resource class that is not managed by the particular ClassLoader, then this will return marking that ClassLoader as never containing any of the instrumentation classes. It may be worth it to think about what if an instrumentation exposes two classes and one of those is managed by the particular ClassLoader, wouldn't you want to mark that ClassLoader as matching? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The intent is to match only when all the resources are present. See javadoc in Lines 83 to 94 in 4621e74
If you need to matched based on either class A or B being present you can write something like hasClassesNamed(A).or(hasClassesNamed(B)) .
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @laurit, the all-or-nothing matching makes sense |
||||||||||||||||||||||||||
|
@@ -46,4 +59,41 @@ private boolean hasResources(ClassLoader cl) { | |||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a step in the right direction. My interpretation: (Please confirm if I'm interpreting this correctly) My past situation I ended up with 5Mil weak keys and I had about 112 instrumentations active. Previous Now (Based my BitSet calculation on internals of a bit set modeled with array of Long[]. 5Mil/64 = 78k Longs) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion you analysis is flawed. It is based on the need to keep track of 5M class loaders, but that situation only arose when cache keys were not removed due to a bug. Given such bug the code in this pr will eventually also fail, it will just take a bit longer.
The size of the bit set is based on the count of the matchers not class loaders. This size is deterministic, I think it was a bit over 200. There is a bit set for each class loader.
A new |
||||||||||||||||||||||||||
private static class Manager { | ||||||||||||||||||||||||||
private static final BitSet EMPTY = new BitSet(0); | ||||||||||||||||||||||||||
static final Manager INSTANCE = new Manager(); | ||||||||||||||||||||||||||
private final List<ClassLoaderHasClassesNamedMatcher> matchers = new CopyOnWriteArrayList<>(); | ||||||||||||||||||||||||||
private final Cache<ClassLoader, BitSet> enabled = Cache.weak(); | ||||||||||||||||||||||||||
private volatile boolean matchCalled = false; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Manager() { | ||||||||||||||||||||||||||
ClassLoaderMatcherCacheHolder.addCache(enabled); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void add(ClassLoaderHasClassesNamedMatcher matcher) { | ||||||||||||||||||||||||||
if (matchCalled) { | ||||||||||||||||||||||||||
throw new IllegalStateException("All matchers should be create before match is called"); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
matchers.add(matcher); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
boolean match(ClassLoaderHasClassesNamedMatcher matcher, ClassLoader cl) { | ||||||||||||||||||||||||||
matchCalled = true; | ||||||||||||||||||||||||||
BitSet set = enabled.get(cl); | ||||||||||||||||||||||||||
if (set == null) { | ||||||||||||||||||||||||||
set = new BitSet(counter.get()); | ||||||||||||||||||||||||||
for (ClassLoaderHasClassesNamedMatcher m : matchers) { | ||||||||||||||||||||||||||
if (hasResources(cl, m.resources)) { | ||||||||||||||||||||||||||
set.set(m.index); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
enabled.put(cl, set.isEmpty() ? EMPTY : set); | ||||||||||||||||||||||||||
enabled.put(cl, set); | ||||||||||||||||||||||||||
laurit marked this conversation as resolved.
Show resolved
Hide resolved
laurit marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
} else if (set.isEmpty()) { | ||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return set.get(matcher.index); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@laurit can I suggest you add a bit more documentation in this class? I think the interaction between the index and the bitset are interesting, but not obvious.