I'm trying to implement a Profiler with ByteBuddy. I'm currently struggling with efficiently creating a proper signature of the methods I'm profiling.
Here is a gist of my current implementation: https://gist.github.com/felixbarny/e0c64819c59368a28200
There are two implementations for the ProfilingInterceptor.profile method. Each one has its own flaw.
The first one uses @Origin String signature
as the signature. This is very efficient as ByteBuddy seems to cache that. The problem is, that I'm not happy with the format of the signature. For example method2(I)I
.
In the second implementation I'm injecting @Origin(cacheMethod = true) Method method
and manually constructing a nicer looking signature: int org.stagemonitor.benchmark.profiler.ClassJavassistProfiled.method2(int)
. The obvious problem is that the signature is re-created on every invocation - not performant (my jmh benchmarks say it's slower by a factor of 4).
Is there a way to cache the signature for example by creating a string constant for each signature?
Thanks in advance
The easiest way to accomplish what you want is to implement your own annotation and create a binding. Any such String
value is then added to the class's constant pool and simply returned from there.
You can find an example for creating a custom annotation at the bottom of the Byte Buddy tutorial. For implementing a custom @Signature
annotation, you would do the following:
enum SignatureBinder
implements TargetMethodAnnotationDrivenBinder.ParameterBinder<StringValue> {
INSTANCE; // singleton
@Override
public Class<Signature> getHandledType() {
return Signature.class;
}
@Override
public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loaded<StringValue> annotation,
MethodDescription source,
ParameterDescription target,
Instrumentation.Target instrumentationTarget,
Assigner assigner) {
if (!target.getTypeDescription().represents(String.class)) {
throw new IllegalStateException(target + " makes illegal use of @Signature");
}
StackManipulation constant = new TextConstant("<signature goes here>");
return new MethodDelegationBinder.ParameterBinding.Anonymous(constant);
}
}
The MethodDescription
object named source
describes the intercepted method and offers an interface that resembles the Method
class. You would then register this binder with the MethodDelegation
and you could use the annotation in the delegant thereafter.
On a side note: I would avoid using canonical names. Those name could be conflicting, e.g. the classes
foo.Bar$Qux
foo.Bar.Qux
with Bar
being once a class with an inner class Qux
and once being a package with a class Qux
have the same name. I know it is unlikely, but you never know how user code is going to look like.