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

Faster type matching #5724

Merged
merged 14 commits into from
Apr 8, 2022
Merged

Faster type matching #5724

merged 14 commits into from
Apr 8, 2022

Conversation

laurit
Copy link
Contributor

@laurit laurit commented Mar 31, 2022

Nice matchers match based on the class that is being loaded, very nice matchers just match the class name.
Naughty matcher match based on the class hierarch (does this class extend X, does it implement Y).
Evil matchers match based on the complex criteria that involves other classes (does this class contain a method that overrides a method annotated with A in super class or interfaces). We have 4 such evil matchers: jax-rs 1, jax-rs 2, jax-ws and external annotations. The last one can hopefully be fixed by removing hasSuperType (I can't see a reason why it is there in the first place), the rest should be disable by default, deleted or written in some other way.
The problem with naughty matchers is that they need to find bytes for super types, parse them and cache them to avoid repeating this. If there are many classes this can be quite costly. This PR introduces an alternative strategy. When a class is loaded, first ensure its super types are loaded. During matching find super type with ClassLoder.findLoadedClass and use the already loaded class for navigating type hierarchy. If just the name or the modifiers of the super type are needed then there is no need to find the actual bytes for the class. Additionally using this strategy fixes a shortcoming of current strategy. Based on just the class loader of class being loaded it is not always possible to find bytes for all of its super types as in OSGI environments super type of super type need not be visible to current class. There should be less stack traces for net.bytebuddy.pool.TypePool$Resolution$NoSuchTypeException in smoke tests for server thats use OSGI.

@laurit laurit force-pushed the faster-type-matching branch 2 times, most recently from dd252f8 to bf1c29f Compare April 1, 2022 10:11
@mateuszrzeszutek
Copy link
Member

We have 4 such evil matchers: jax-rs 1, jax-rs 2, jax-ws and external annotations. The last one can hopefully be fixed by removing hasSuperType (I can't see a reason why it is there in the first place), the rest should be disable by default, deleted or written in some other way.

I think we could probably disable them by default and explicitly state in our docs that they're very expensive to apply, if you use JAX-RS then you have to enable it manually.

@laurit
Copy link
Contributor Author

laurit commented Apr 1, 2022

I think we could probably disable them by default and explicitly state in our docs that they're very expensive to apply, if you use JAX-RS then you have to enable it manually.

These expensive instrumentations are only used to create a span for the annotated method. We already have other spans for both JAX-RS and JAX-WS create by framework specific instrumentations that are probably more useful than the expensive one.

@trask
Copy link
Member

trask commented Apr 1, 2022

💯

@anuraaga
Copy link
Contributor

anuraaga commented Apr 5, 2022

If the jax instrumentations are missing classloader matchers, can we add them and still keep the instrumentation enabled by default?

@laurit
Copy link
Contributor Author

laurit commented Apr 5, 2022

If the jax instrumentations are missing classloader matchers, can we add them and still keep the instrumentation enabled by default?

They have the class loader matchers. The problem is that these apis are present in a lot application servers which will enable these instrumentation even when they aren't really used. I have enable them for now, we can deal with this later.

@laurit laurit marked this pull request as ready for review April 5, 2022 18:49
@laurit laurit requested a review from a team as a code owner April 5, 2022 18:49
Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

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

❤️❤️❤️

These expensive instrumentations are only used to create a span for the annotated method. We already have other spans for both JAX-RS and JAX-WS create by framework specific instrumentations that are probably more useful than the expensive one.

I'd be interested in seeing the impact to the tests of disabling these expensive ones in a follow-up. I think it would depend on what exactly we lose by having these off-by-default, but I'm very open to it, as startup performance is a big deal.

Comment on lines +37 to +45
ClassReader cr = new ClassReader(classBytes, offset, length);
String superName = cr.getSuperName();
if (superName != null) {
Class.forName(superName.replace('/', '.'), false, classLoader);
}
String[] interfaces = cr.getInterfaces();
for (String interfaceName : interfaces) {
Class.forName(interfaceName.replace('/', '.'), false, classLoader);
}
Copy link
Member

Choose a reason for hiding this comment

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

oh! for some reason I didn't think it was so cheap to get the superclass and interfaces!

I had a similar optimization to preload super types in glowroot: https://github.com/glowroot/glowroot/blob/main/agent/core/src/main/java/org/glowroot/agent/impl/PreloadSomeSuperTypesCache.java

but it stored the superclass/interfaces into a file cache during loading, and then used that to speed up subsequent startups

(this is so much better 🤩)

@laurit
Copy link
Contributor Author

laurit commented Apr 6, 2022

I'd be interested in seeing the impact to the tests of disabling these expensive ones in a follow-up. I think it would depend on what exactly we lose by having these off-by-default, but I'm very open to it, as startup performance is a big deal.

I did some rough measurements starting liferay. I measured invocation count & time spent for 2 point

This pr.

match count=5409259, time=5247ms
locate count=10559, time=3333ms

Class loader optimization matcher disabled, this ensures that the jax-rs/ws annotation matchers have something to match.

match count=10154036, time=16974ms
locate count=32589, time=9313ms

Class loader optimization matcher disabled, jax-rs/ws annotation matchers disabled.

match count=9965431, time=6738ms
locate count=9279, time=2094ms

Class loader optimization matcher disabled, jax-rs/ws annotation matchers disabled, matchers that match based on annotation (external annotations, withspan, spring-ws) disabled.

match count=9368871, time=4852ms
locate count=3258, time=1008ms

Byte-buddy locates bytes for all annotations on all methods if you have a matcher that matches based on annotation. I hope I can convince byte-buddy author to change this or give us some way to just match based on the name without locating the annotation type.

In retrospect I realized that I probably should have counted only the classes located during matching. Then the last test would give

locate count=45, time=19ms

which looks good.

Comment on lines 38 to 39
private static final AgentBuilder.PoolStrategy POOL_STRATEGY =
new AgentCachingPoolStrategy(locationStrategy(bootstrapProxy));
Copy link
Member

Choose a reason for hiding this comment

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

I know that this will currently work, but the combination of volatile globals and static class initializers makes me worried - WDYT about constructing the PoolStrategy in the AgentInstaller and passing it along as constructor parameter? (this'd require making InstrumentationLoader a "normal" class, not an SPI 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.

This is also used from muzzle. WDYT if I add an SPI for getting the bootstrap proxy?

Copy link
Member

Choose a reason for hiding this comment

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

That'd probably solve the problem, but wouldn't just creating a bootstrap proxy CL in the muzzle module be a bit simpler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It depends on whether muzzle code needs the same bootstrap proxy as agent (as agent jar is appended to bootclasspath getting access to resources in agent needs a bit of work). Having multiple pool strategy instances could complicate this a bit as then they wouldn't share the cache unless some stuff is made static.

Comment on lines +350 to +354
// unlike byte-buddy implementation we cache the descriptor to avoid having to find
// super class and interfaces multiple times
Copy link
Member

Choose a reason for hiding this comment

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

👍

@trask
Copy link
Member

trask commented Apr 7, 2022

jax-rs/ws annotation matchers disabled

wow, those matchers are slow!! (esp. now that the inheritance hierarchy matchers have been optimized in this PR 👍)

@trask trask merged commit 4ad4490 into open-telemetry:main Apr 8, 2022
@trask
Copy link
Member

trask commented Apr 8, 2022

🎉

This was referenced Apr 10, 2022
RashmiRam pushed a commit to RashmiRam/opentelemetry-auto-instr-java that referenced this pull request May 23, 2022
* 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 <trask.stalnaker@gmail.com>

* 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 <trask.stalnaker@gmail.com>
@laurit laurit deleted the faster-type-matching branch June 1, 2023 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants