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

java.lang.NoSuchMethodError when using the agent for method instrumentation #1637

Open
kavehshahedi opened this issue May 10, 2024 · 2 comments
Assignees
Labels
Milestone

Comments

@kavehshahedi
Copy link

Hello,

As shown in the following code, I have created an agent in order to instrument the methods of a project with a specific class name and capture their execution times:

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

public class FunctionEntryAgent {

    public static void premain(String args, Instrumentation inst) {
        new AgentBuilder.Default()
                .type(ElementMatchers.nameContains("org.HdrHistogram"))
                .transform(
                        (builder, type, classLoader, module, protectionDomain) -> 
                        builder.method(ElementMatchers.any())
                                .intercept(MethodDelegation.to(MethodExecutionTime.class)))
                .installOn(inst);
    }

    public static class MethodExecutionTime {
        private static final String LOG_FILE = "method_logs.log";

        @RuntimeType
        public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception{
            long start = System.nanoTime();
            try {
                return callable.call();
            } finally {
                String message = "[" + (System.nanoTime() - start) + "] "
                        + method.getDeclaringClass().getName() + "." + method.getName() + " "
                        + "(" + Arrays.toString(method.getParameterTypes()) + ") "
                        + method.getReturnType().getName();

                CompletableFuture.runAsync(() -> logMethodExecutionTime(message));
            }
        }

        private static void logMethodExecutionTime(String message) {
            try (Writer writer = new BufferedWriter(new FileWriter(LOG_FILE, true))) {
                writer.write(message + "\n");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

The agent works in most cases, but in some projects, it throws java.lang.NoSuchMethodError error during the execution. For instance, the following error comes from this line of the code return callable.call();:

java.lang.NoSuchMethodError: 'boolean org.HdrHistogram.RecordedValuesIterator.hasNext$original$NZclujSK()'

Based on one of the suggestions in this this issue, I formatted the code in way that it uses Advice instead of MethodDelegation, as follows:

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

public class FunctionEntryAgent {

    public static void premain(String args, Instrumentation inst) {
        new AgentBuilder.Default()
                .type(ElementMatchers.nameStartsWith("org.HdrHistogram"))
                .transform(new AgentBuilder.Transformer.ForAdvice()
                        .advice(ElementMatchers.isMethod(),
                                MethodExecutionTime.class.getName()))
                .installOn(inst);
    }

    public static class MethodExecutionTime {
        private static final String LOG_FILE = "method_logs.log";

        @Advice.OnMethodEnter
        static long enter(@Advice.Origin Method method) {
            return System.nanoTime();
        }

        @Advice.OnMethodExit
        static void exit(@Advice.Origin Method method, @Advice.Enter long start) {
            long duration = System.nanoTime() - start;
            String message = "[" + duration + "] "
                    + method.getDeclaringClass().getName() + "." + method.getName() + " "
                    + "(" + Arrays.toString(method.getParameterTypes()) + ") "
                    + method.getReturnType().getName();

            CompletableFuture.supplyAsync(new LoggerSupplier(message));
        }

        public static class LoggerSupplier implements Supplier<Void> {
            private final String message;

            public LoggerSupplier(String message) {
                this.message = message;
            }

            @Override
            public Void get() {
                try (Writer writer = new BufferedWriter(new FileWriter(LOG_FILE, true))) {
                    writer.write(message + "\n");
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return null;
            }
        }
    }
}

These modifications fix the error, but the other problem is that the agent is not able to instrument all the classes and subclasses under the target package name. For instance, the agent attaches to a jar, which is a JMH benchmark that is benchmarking a baseline project (both of them have the same package names). The agent instruments the methods within the jmh project correctly (i.e., methods that are targeting the baseline project), but it can't capture the methods from the baseline project. However, when using MethodDelegation instead of Advice, the agent instruments all the methods (but as said, it throws the mentioned error).

I would be glad if you could inform me how to overcome this issue. Thanks!

@raphw
Copy link
Owner

raphw commented May 10, 2024

This does not quite make sense to me. Could you investigate how the methods get instrumented? Register an AgentBuilder.Listener, possibly some of the instrumentations are failing

@raphw raphw self-assigned this May 10, 2024
@raphw raphw added the question label May 10, 2024
@raphw raphw modified the milestones: 0.4.1, 1.14.15 May 10, 2024
@kavehshahedi
Copy link
Author

Thanks for the suggestion. After attaching a listener to the agent, this error popped up:
Cannot write invoke dynamic instruction for class file version Java 6 (50)

As I investigated the previous issues, it turned out that the lambda dynamic invocation was causing the problem. I re-compiled the agent with Java 7, and the problem seems to be fixed.

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants