DX Application Performance Management

 View Only

CA APM Java Agent using ASM as its BCI Engine - Part 1(A Mini ASM Java Agent tracing methods)

By Vashistha Kumar Singh posted Feb 29, 2016 05:52 PM

  

Disclaimer: The views represented on this blog series are my individual views and cannot be associated with any person, company or entity I know or do not know. The view represented here are my own views in individual capacity and do not represent views of CA Technologies.

 

Introduction:

The APM Java agent relies on the marvelous decade old byte code instrumentation code. Since then we revisited this piece of code when we needed to support the Java 7 split verifier followed by Java 8 support. In this blog series with some experiments we will explore if we can make the Java agent take advantage of Open Source Byte code instrumentation tool ASM(http://asm.ow2.org/).

It is high performance class transformation engine. It has less memory overhead to achieve the class transformation. Hang on this does not mean I am saying to replace the core BCI engine of our agent, in essence what I mean is can we get the open source BCI tool ASM to augment the core BCI engine of CA APM Java agent ? In this blog series I will explore this possibility with ASM.

 

What is Byte Code Instrumentation

          Before I get into much of details, it is important for you to understand a little bit of what is Byte Code instrumentation. It is transforming the class bytes at the time the class is about to be loaded by the class loader to define the class. At present Java 5 onwards it is done using Java Agent (java.lang.instrument (Java Platform SE 8 ) ). The Java agent as mentioned in this document when registered will transform the class bytes. This has mostly full control on what needs to be part of the class. It can take the following high level steps to achieve the same:

1.      JVM at start up calls agent premain method

2.      The agent register the transformer

3.      Before the class definition the class bytes are passed to the transformer

4.      The transformer returns the modified bytes

5.      The modified class bytes are used for loading and defining the class.

The steps 3 to 5 happens every time new class is being defined or a class is being redefined.

Class File Format

You should be familiar with basics of class file format, before you can take advantage of ASM's power. As par the ASM's documentation the following is the class file format

ClassFileFormat.png

Source: ASM Documentation(http://download.forge.objectweb.org/asm/asm4-guide.pdf )

As the table suggest there could be 0 or more methods, fileds, annotations, attributes. Whatever resulting class bytes being generated by the tool should follow this format. Ofcourse there are more sophisticated verification rules. However that is not the point here for now.

 

Let's Talk about ASM Now

               As I said earlier this series we will explore ASM, on how it can augment the CA APM Java agent along side its own BCI engine. The ASM has both event based and tree based API to manipulate class bytes. In this series I will discuss about the event based APIs. The event based API is based on the fact that as the class bytes are read and the class reader encounters the specific components from the above table it generate corresponding events to act upon. For example the ClassVisitor class has the following method which represents those events:

ClassVisitor.png

A Simple Mini Java Agent Using ASM

     Let me show you a simple mini java agent based on java.lang.instrument package powered by ASM library. This elementary agent traces the start and finish of any method. It injects the logger calls to start of the method and exit of the method.

This mini agent adds the following transformation into the method of a class. Let’s say the following is a method:

######################################

public void method1(){
// Method implementation Code
}

Transformed method will be:

#####################################

public void method1(){
System.out.println(“Method Started-->” + “method1”);
// Method implementation Code
System.out.println(“Method finished-->” + “method1”);
}

#######################################

You can find the attached MiniJavaAgentUsingASM.jar.

How to Use It

Add the following JVM argument:

-javaagent:<path to MiniAgentJarfile>\MiniJavaAgentUsingASM.jar -Dcom.ca.asm.compiler.classes=<comma separated regex based class names which needs to be modified> -Dcom.ca.asm.compiler.dump.path=<path where the modified class can be dumped>

For example the following JVM argument traces all the method of all the classes and dumps those modified classes under the folder C:\debugclasses\analyse\

-javaagent:C:\apm-dev\ASMDev\MiniJavaAgentUsingASM\target\MiniJavaAgentUsingASM.jar -Dcom.ca.asm.compiler.classes=com/samples.* -Dcom.ca.asm.compiler.dump.path=C:\debugclasses\analyse\

The Event Chain Flow under the hood

EventChain.png

The above flow is the high level event flow for the ASM. The input class bytes are read by the ClassReader and it passed to the chain of the visitors. Finally the last visitor in the chain should be the ClassWriter itself which will generate the final class bytes. This means you need to build the chain of transformations which you want to achieve.

 

The MiniJavaAgentUsingASM has the following Classes:

JavaAgent – This class is implementing the premain and agentmain methods for java.lang.instrument package

ClassFileTransformerImpl is the implementation of ClassFileTransformer

GenericClassAdapter is ClassVisitor which is following the ClassReader. It registers EntryExitMethodAdapter as a MethodVisitor while visitMethod event is generated.

ClassReader and ClassWriter are provided by ASM itself for generating the events and finally writing the class bytes respectively.

   

Deep Dive into the implementation

  • ClassFileTransformerImpl - This has the method compileOneClass which will generate the resultant class bytes.
public byte[] compileOneClass(String className, byte[] classBytes) { // line 1
 try { // line 2
 byte[] b1 = classBytes; // line 3
 ClassReader cr = new ClassReader(b1); // line 4
 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
 | ClassWriter.COMPUTE_FRAMES); // line 5
 GenericClassAdapter cv = new GenericClassAdapter(Opcodes.ASM5, cw); // line 6
 cr.accept(cv, ClassReader.SKIP_FRAMES); // line 7
 byte[] b2 = cw.toByteArray(); // line 8
 return b2; // line 9
 } catch (Throwable t) {
 t.printStackTrace();
 return classBytes;
 }
 }

In the above code line 4 initializes the ClassReader with the input bytes. Line 5 creates ClassWriter object with Stack map frames computation. Line 6 creates the event consumer as GenericClassAdapter which holds ClassWriter as the successor to consume the event. Line 7 start generating the events and feed to the GenericClassAdapter and GenericClassAdapter feeds the same to ClassWriter. After line 7 the ClassWriter will have the modified bytes.

 

More details on GenericClassAdapter

The GenericClassAdapter has the following visitMethod:

 

public MethodVisitor visitMethod(int access, String name, String desc,
 String signature, String[] exceptions) {
 // following method visitor is writer of the method code. It must be chained at the last
 MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
 exceptions);
 mv = new EntryExitMethodAdapter(Opcodes.ASM5, mv, access, name, desc, className);
 return mv;
 }

In the above method you can see that the new method visitor has been created to handle the method transformation and it sends the corresponding event to the MethodVisitor from the ClassWriter or any following method transformer. We can build a chain of transformers to achieve any type of transformation.

More details on EntryExitMethodAdapter

This is MethodVisitor which is doing the transformation of the method code and handles all the events which are generated for method. The EntryExitMethodAdapter extends AdviceAdapter class. The AdviceAdapter class provides the onMethodEnter and onMethodExit events. We insert our instructions to these two method calls.

You can find the attached source in MiniJavaAgentUsingASM.zip.

Known Issue: You may find the exception on getSuperClass API in the ASM. This is because I have not customized it and it is default implementation.

What Next

In the next post I will talk about the challenges and opportunities for CA APM Java agent and how can ASM play a role in augmenting the Byte code instrumentation engine of the CA APM Java agent. Till then try this mini agent and find some issues. You may find some issues !!! Feel free to drop your questions!!!

0 comments
1 view