How to get arrgument of a method using bytebuddy - java

I am trying to use metrics with a java application to find out performance. I used java agent with bytebuddy of get metrics.In my testing program the method that i want to check is running several time. only i need to get metrics when it passing a parameter contain name 'connector'. So i want to get this using bytebuddy and i used #AllArguments Object[] args for this . But i try to used this my TimerAdvice class not running.
This is my code
class Agent {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Premain");
new AgentBuilder.Default()
.with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
.type((ElementMatchers.nameContains("ConnectorCallback")))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.include(MethodListner.class.getClassLoader())
.advice(ElementMatchers.any(), MethodListner.class.getName())
).installOn(instrumentation);}}
This my TimerAdvice class
public class TimerAdvice {
#Advice.OnMethodEnter
static void enter(#Advice.Origin String method , #AllArguments Object[] args)throws Exception {
if (changeMethodName(method).equals("BalConnectorCallback")) {
//Metrics works
}
}
#Advice.OnMethodExit
static void exit(#Advice.Origin String method, #AllArguments Object[] args) throws Exception {
if (changeMethodName(method).equals("done")) {
//Metrics works
}
}
public static String changeMethodName(String method) {
String newMethod = method.substring(0, method.lastIndexOf('('));
newMethod = newMethod.substring(newMethod.lastIndexOf('.') + 1);
//newMethod = newMethod.replace(".", " ");
return newMethod;
}}
When i am using #AllArguments Object[] args this only TimerAdvice not working without it its work perfectly.Is this problem in my code ?
Any Help..

You are probably importing the wrong annotation. The annotation that you are looking for is #Advice.AllArguments.
This naming collission is unfortunate but it is too late to change that. All advice-comatible annotations are prefixed. Tge others are meant to be used with method delegation.

Related

Mockito - verify a method with Callable<> as parameter [duplicate]

I have a simple scenario in which am trying to verify some behavior when a method is called (i.e. that a certain method was called with given parameter, a function pointer in this scenario). Below are my classes:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
AppBootStrapper bootStrapper = context.getBean(AppBootStrapper.class);
bootStrapper.start();
}
}
#Component
public class AppBootStrapper {
private NetworkScanner networkScanner;
private PacketConsumer packetConsumer;
public AppBootStrapper(NetworkScanner networkScanner, PacketConsumer packetConsumer) {
this.networkScanner = networkScanner;
this.packetConsumer = packetConsumer;
}
public void start() {
networkScanner.addConsumer(packetConsumer::consumePacket);
networkScanner.startScan();
}
}
#Component
public class NetworkScanner {
private List<Consumer<String>> consumers = new ArrayList<>();
public void startScan(){
Executors.newSingleThreadExecutor().submit(() -> {
while(true) {
// do some scanning and get/parse packets
consumers.forEach(consumer -> consumer.accept("Package Data"));
}
});
}
public void addConsumer(Consumer<String> consumer) {
this.consumers.add(consumer);
}
}
#Component
public class PacketConsumer {
public void consumePacket(String packet) {
System.out.println("Packet received: " + packet);
}
}
#RunWith(JUnit4.class)
public class AppBootStrapperTest {
#Test
public void start() throws Exception {
NetworkScanner networkScanner = mock(NetworkScanner.class);
PacketConsumer packetConsumer = mock(PacketConsumer.class);
AppBootStrapper appBootStrapper = new AppBootStrapper(networkScanner, packetConsumer);
appBootStrapper.start();
verify(networkScanner).addConsumer(packetConsumer::consumePacket);
verify(networkScanner, times(1)).startScan();
}
}
I want to verify that bootStrapper did in fact do proper setup by registering the packet consumer(there might be other consumers registered later on, but this one is mandatory) and then called startScan. I get the following error message when I execute the test case:
Argument(s) are different! Wanted:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapperTest$$Lambda$8/438123546#282308c3
);
-> at com.spring.starter.AppBootStrapperTest.start(AppBootStrapperTest.java:24)
Actual invocation has different arguments:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapper$$Lambda$7/920446957#5dda14d0
);
-> at com.spring.starter.AppBootStrapper.start(AppBootStrapper.java:12)
From the exception, clearly the function pointers aren't the same.
Am I approaching this the right way? Is there something basic I am missing? I played around and had a consumer injected into PacketConsumer just to see if it made a different and that was OK, but I know that's certainly not the right way to go.
Any help, perspectives on this would be greatly appreciated.
Java doesn't have any concept of "function pointers"; when you see:
networkScanner.addConsumer(packetConsumer::consumePacket);
What Java actually compiles is (the equivalent of):
networkScanner.addConsumer(new Consumer<String>() {
#Override void accept(String packet) {
packetConsumer.consumePacket(packet);
}
});
This anonymous inner class happens to be called AppBootStrapper$$Lambda$7. Because it doesn't (and shouldn't) define an equals method, it will never be equal to the anonymous inner class that the compiler generates in your test, which happens to be called AppBootStrapperTest$$Lambda$8. This is regardless of the fact that the method bodies are the same, and are built in the same way from the same method reference.
If you generate the Consumer explicitly in your test and save it as a static final Consumer<String> field, then you can pass that reference in the test and compare it; at that point, reference equality should hold. This should work with a lambda expression or method reference just fine.
A more apt test would probably verify(packetConsumer, atLeastOnce()).consumePacket(...), as the contents of the lambda are an implementation detail and you're really more concerned about how your component collaborates with other components. The abstraction here should be at the consumePacket level, not at the addConsumer level.
See the comments and answer on this SO question.

How to get the MethodInfo of a Java 14 method reference?

I'm essentially asking the same as this old question, but for Java 14 instead of Java 8. To spare answerers the trouble of navigating to the old question, I'll rephrase it here.
I want to get the name of a function from a referenced method. The following Java code should give you the idea:
public class Main
{
public static void main(String[] args)
{
printMethodName(Main::main);
}
private static void printMethodName(Consumer<String[]> theFunc)
{
String funcName = // somehow get name from theFunc
System.out.println(funcName)
}
}
The equivalent in C# would be:
public class Main
{
public static void Main()
{
var method = Main.Main;
PrintMethodName(method)
}
private static void PrintMethodName(Action action)
{
Console.WriteLine(action.GetMethodInfo().Name);
}
}
According to the accepted answer of the old question, this was not possible in Java 8 without considerable work, such as this solution. Is there a more elegant solution in Java 14?
Getting a method info from a method reference never was a goal on the JDK developer’s side, so no effort was made to change the situation.
However, the approach shown in your link can be simplified. Instead of serializing the information, patching the serialized data, and restoring the information using a replacement object, you can simply intercept the original SerializedLambda object while serializing.
E.g.
public class GetSerializedLambda extends ObjectOutputStream {
public static void main(String[] args) { // example case
var lambda = (Consumer<String[]>&Serializable)GetSerializedLambda::main;
SerializedLambda sl = GetSerializedLambda.get(lambda);
System.out.println(sl.getImplClass() + " " + sl.getImplMethodName());
}
private SerializedLambda info;
GetSerializedLambda() throws IOException {
super(OutputStream.nullOutputStream());
super.enableReplaceObject(true);
}
#Override protected Object replaceObject(Object obj) throws IOException {
if(obj instanceof SerializedLambda) {
info = (SerializedLambda)obj;
obj = null;
}
return obj;
}
public static SerializedLambda get(Object obj) {
try {
GetSerializedLambda getter = new GetSerializedLambda();
getter.writeObject(obj);
return getter.info;
} catch(IOException ex) {
throw new IllegalArgumentException("not a serializable lambda", ex);
}
}
}
which will print GetSerializedLambda main. The only newer feature used here, is the OutputStream.nullOutputStream() to drop the written information immediately. Prior to JDK 11, you could write into a ByteArrayOutputStream and drop the information after the operation which is only slightly less efficient. The example also using var, but this is irrelevant to the actual operation of getting the method information.
The limitations are the same as in JDK 8. It requires a serializable method reference. Further, there is no guaranty that the implementation will map to a method directly. E.g., if you change the example’s declaration to public static void main(String... args), it will print something like lambda$1 when being compiled with Eclipse. When also changing the next line to var lambda = (Consumer<String>&Serializable)GetSerializedLambda::main;, the code will always print a synthetic method name, as using a helper method is unavoidable. But in case of javac, the name is rather something like lambda$main$f23f6912$1 instead of Eclipse’s lambda$1.
In other words, you can expect encountering surprising implementation details. Do not write applications relying on the availability of such information.

PowerMockito / Mockito argument matcher call location issue

In short, I have a set of generated source code that I need to be able to dynamically mock based on external, non-Java configuration - they follow no consistent pattern / implement any interfaces other than being static, meaning I can only know how to mock a method at runtime and need to use PowerMockito to do so.
Say that I have this class:
public class SomeClass {
public static void doSomething(Integer i) {
throw new RuntimeException();
}
}
And I simply want to mock doSomething / have it not throw exceptions. To do that simply / without any of the complexity I mention in my use case, I could do this:
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PrepareForTest(SomeClass.class)
public class TestSomeClass {
#Test
public void testDoSomethingSimple() throws Exception {
PowerMockito.spy(SomeClass.class);
PowerMockito.doNothing().when(SomeClass.class, "doSomething", any(Integer.class));
SomeClass.doSomething(5);
}
}
Which works fine.
This changes however when we step back and try to address my needs, and move complexity to something like this:
#Test
public void testDoSomething() throws Exception {
// Below showing how everything could be externally-driven
testDoSomething("SomeClass", "doSomething", "java.lang.Integer");
SomeClass.doSomething(5);
}
public void testDoSomething(
final String canonicalClassName, final String methodName, final String... canonicalParameterClassNames)
throws Exception {
final Class<?> clazz = Class.forName(canonicalClassName);
PowerMockito.spy(clazz);
final Object[] argumentMatchers = new Object[canonicalParameterClassNames.length];
for (int i = 0; i < canonicalParameterClassNames.length; i++) {
argumentMatchers[i] = any(Class.forName(canonicalParameterClassNames[i]));
}
PowerMockito.doNothing().when(clazz, methodName, argumentMatchers);
}
Which leads to this issue:
After much head scratching, managed to replicate this error much more succinctly:
#Test
public void testDoSomethingIssueIsolated() throws Exception {
PowerMockito.spy(SomeClass.class);
Object matcher = any(Integer.class);
PowerMockito.doNothing().when(SomeClass.class, "doSomething", matcher);
SomeClass.doSomething(5);
}
Seemingly indicating that what's causing this issue is where the calls to create the argument matchers are, which is rather odd.
Got it - this isn't a PowerMockito thing. This is a standard Mockito thing and is actually by design - the telling point being one word in the error - you cannot use argument matchers outside of verification or stubbing. While I am using them for stubbing, the outside implies more.
This led me to this answer to another question on how matchers work, with the comment of particular importance:
- Call order isn't just important, it's what makes this all work.
Extracting matchers to variables generally doesn't work, because it
usually changes the call order. Extracting matchers to methods,
however, works great.
int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// The helper method calls the matcher methods in the right order.
Basically have to be careful with the stack, which led me to this, which works and meets my requirements of being able to mock variable number of arguments determined at runtime (the Strings in testDoSomething could all have been pulled from a text file and the method call could be managed via reflection):
#Test
public void testDoSomething() throws Exception {
// Below showing how everything could be externally-driven
mockAnyMethod("SomeClass", "doSomething", "java.lang.Integer");
SomeClass.doSomething(5);
}
public void mockAnyMethod(
final String canonicalClassName,
final String methodName,
final String... canonicalParameterClassNames)
throws Exception {
final Class<?> clazz = Class.forName(canonicalClassName);
PowerMockito.spy(clazz);
PowerMockito.doNothing()
.when(clazz, methodName, getArgumentMatchers(canonicalParameterClassNames));
}
public Object[] getArgumentMatchers(final String... canonicalParameterClassNames)
throws ClassNotFoundException {
final Object[] argumentMatchers = new Object[canonicalParameterClassNames.length];
for (int i = 0; i < canonicalParameterClassNames.length; i++) {
argumentMatchers[i] = any(Class.forName(canonicalParameterClassNames[i]));
}
return argumentMatchers;
}
If you will read the failure trace carefully, you would have found your answer to this question
Misplaced or misused argument matcher detected here:
-> at mockito.TestSomeClass.testDoSomething(TestSomeClass.java:xx)
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
verify(mock).someMethod(contains("foo"))
you try to use any(...) within the for loop, which is outside of verification or stubbing (here: PowerMockito.doNothing().when(...)).
for (int i = 0; i < canonicalParameterClassNames.length; i++) {
argumentMatchers[i] = any(Class.forName(canonicalParameterClassNames[i]));
}
PowerMockito.doNothing().when(clazz, methodName, argumentMatchers);
For this reason your solution would not work.
I tried this alternative
for (int i = 0; i < canonicalParameterClass.length; i++) {
PowerMockito.doNothing().when(clazz, methodName, any(Class.forName(canonicalParameterClass[i])));
}
which worked for me.
you could simplify your method by using Class instead of String for your class names.
#Test
public void testDoSomething() throws Exception {
// Below showing how everything could be externally-driven
testDoSomething(SomeClass.class, "doSomething", Integer.class);
SomeClass.doSomething(5);
}
public void testDoSomething(final Class classToTest, final String methodName, final Class... parameterClasses)
throws Exception {
PowerMockito.spy(classToTest);
for (int i = 0; i < parameterClasses.length; i++) {
PowerMockito.doNothing().when(classToTest, methodName, any(parameterClasses[i]));
}
}
}

Efficiently get caller in byte buddy's MethodDelegation

I am trying to build a call tree in my java agent with byte buddy library. To add elements to the tree I want to use method delegation. However, to make sure who is the parent of any leaf, I need to know who called the method.
I don't want to use:
sun.reflect.Reflection#getCallerClass(int)
Because it's deprecated and unavailable in Java 8+.
Also, I tried:
public class ThreadUtil {
public static StackTraceElement getCaller() {
Instant now = Instant.now();
StackTraceElement ste = Thread.currentThread().getStackTrace()[3];
String callerClass = ste.getClassName();
String callerMethod = ste.getMethodName();
Instant now2= Instant.now();
System.out.println(Duration.between(now, now2));
return ste;
}
}
But, it's extremely slow(~1 ms - too much if I have thousands of calls).
Is there a way to get caller efficiently at this point (maybe some byte buddy's tricks)?
P.S.
My agent:
private static void instrument(String agentOps, Instrumentation inst) {
System.out.println("Agent");
new AgentBuilder.Default().with(new Eager())
.ignore(ElementMatchers.nameContains("com.dvelopp.agenttest"))
.type((ElementMatchers.any()))
.transform((builder, typeDescription, classLoader, module) -> builder.method(ElementMatchers.any())
.intercept(MethodDelegation.to(Interceptor.class))).installOn(inst);
}
public static class Interceptor {
#RuntimeType
public static Object intercept(#SuperCall Callable<?> zuper, #Origin Method method,
#AllArguments Object[] args, #This(optional = true) Object me) throws Exception {
System.out.println("CURRENT: " + method.getDeclaringClass().getName());
System.out.println("CALLER: " + ThreadUtil.getCaller().getClassName());
return zuper.call();
}
}
ENV: Java 8
Byte code instrumentation only allows you to generate code that you could also write yourself. For your case, you would need to create a fairly intrusive instrumentation that I would not recommend:
Instrument your target method to accept a new parameter of type Class.
Instrument every caller to supply their type as an additional argument.
The better solution is surely what Holger suggested in the comments. Use StackWalker and if not available, fall back to sun.reflect.Reflection (which is present in all JVMs I know of).

ByteBuddy MethodDelegation not working in Java Agent

I have a premain() wherein all methods annotated with a certain annotation should be delegated to a certain class. In general, i looks like this:
public static void premain( final String agentArguments, final Instrumentation instrumentation ) {
CountingInterception ci = new CountingInterception();
new AgentBuilder.Default()
.type(ElementMatchers.isAnnotatedWith(com.codahale.metrics.annotation.Counted.class))
.transform((builder, type, classLoader, module) ->
builder.method(ElementMatchers.any())
.intercept(MethodDelegation.to(ci))
).installOn(instrumentation);
}
Using a debugger shows that this part is processed but if an annotated method is called, nothing happens.
The CountingInterception looks like this
public class CountingInterception {
#RuntimeType
public Object intercept(#DefaultCall final Callable<?> zuper, #Origin final Method method, #AllArguments final Object... args) throws Exception {
String name = method.getAnnotation(Counted.class).name();
if (name != null) {
// do something
}
return zuper.call();
}
}
Thanks for any hints!
Using ByteBuddy 1.6.9
To achieve what I wanted to do, the following changes were made:
In premain:
CountingInterception ci = new CountingInterception();
new AgentBuilder.Default()
.type(declaresMethod(isAnnotatedWith(Counted.class)))
.transform((builder, type, classLoader, module) -> builder
.method(isAnnotatedWith(Counted.class))
.intercept(MethodDelegation.to(ci).andThen(SuperMethodCall.INSTANCE))
).installOn(instrumentation);
and in CountingInterception:
public void interceptor(#Origin final Method method) throws Exception {
String name = method.getAnnotation(Counted.class).name();
if (name != null) {
// do something
}
}
I assume that you are trying to do something different than a Java 8 default method call. Did you mean to use #SuperCall which invokes a super method?
I would suggest you to:
1. Reduce your interceptor to do nothing. Create an interceptor that chains your MethodDelegation with a SuperMethodCall.
2. Register an AgentBuilder.Listener to write errors to the console.
I am sure Byte Buddy cannot bind your methods as your interceptor can only be applied to classes that offer a default method implementation.

Categories