Search code examples
javabytecodebyte-buddy

Performant way of creating a custom method signature with Byte Buddy


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


Solution

  • 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.