A Simple Java Profiling Tool

A Simple Java Profiling Tool

I have written a simple Java Profiling Tool which will instrument java byte code on class loading time and inject required code on each method. Here you go for step by step procedures.

Step 1: Write Agent Class

package com.javaagent;

import java.lang.instrument.Instrumentation;

public class ElamJavaAgent {
   public static void premain(String args, Instrumentation instrumentation){
    
    System.out.println("[Elam Java Agent] Elam Java Agent is instrumenting your application..");
    
     ClassInstrument transformer = new ClassInstrument();
     instrumentation.addTransformer(transformer);
   }
 }

Step 2: Class instrumenting class.

package com.elam.agent.profile;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Mnemonic;



public class ClassInstrument implements ClassFileTransformer {

  private static final StringBuilder WRAP_METHOD = new StringBuilder();

  static {

   WRAP_METHOD.append("{");
  WRAP_METHOD.append(" long startTimeAgent = System.currentTimeMillis(); ");
  WRAP_METHOD.append(" try { ");
  WRAP_METHOD.append(" {RETURN} {METHOD_NAME} ").append("({PARAMS});");
  WRAP_METHOD.append("}");
  WRAP_METHOD.append(" finally ");
  WRAP_METHOD.append(" { ");
  WRAP_METHOD.append(" long execTimeAgent = System.currentTimeMillis() - startTimeAgent; ");
  WRAP_METHOD.append(" System.out.println(").append("\"").append("[Elam Java Agent]");
  WRAP_METHOD.append(" {INSTRUCT_STMNT} ");
  WRAP_METHOD.append("\"").append("+");
  WRAP_METHOD.append("execTimeAgent").append("+");
  WRAP_METHOD.append("\"").append(" milliseconds.");
  WRAP_METHOD.append("\"").append("); ");
  WRAP_METHOD.append(" } }");

  }


  public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
   ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

   final String clazzName = className.replace("/", ".");
  final StringBuilder statmntBuilder = new StringBuilder();
  final StringBuilder clazzNameBuilder = new StringBuilder();
  final StringBuilder methodNameBuilder = new StringBuilder();


   ClassPool classPool = null;
  try {


    classPool = ClassPool.getDefault();


    if(clazzName.startsWith("com.elam.") ) {

     classPool.appendClassPath(new LoaderClassPath(loader));


     final CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

     clazzNameBuilder.append(clazzName).append(" ==> ");


     if (!ctClass.isInterface() && !ctClass.isFrozen())
     for (final CtMethod method : ctClass.getDeclaredMethods())
      if(!isGetterOrSetterMethod(ctClass.getClassFile(),method.getName()))
       if(!method.isEmpty()) {

         methodNameBuilder.append(method.getName()).append(" --> ");
        statmntBuilder.append(clazzNameBuilder).append(methodNameBuilder);


         final CtMethod agentMethod = CtNewMethod.copy(method, method.getName()+ "_AgentMethod", ctClass, null);

         makePrivateAccess(agentMethod);

         ctClass.addMethod(agentMethod);

         String methodBody = WRAP_METHOD.toString().replace("{METHOD_NAME}", agentMethod.getName());;
        methodBody = methodBody.replace("{INSTRUCT_STMNT}", statmntBuilder.toString());


         if (null != method.getParameterTypes() && method.getParameterTypes().length > 0)
         methodBody = methodBody.replace("{PARAMS}", "$$");
        else
         methodBody = methodBody.replace("{PARAMS}", "");


         if(method.getReturnType().toString().endsWith("[void]"))
         methodBody = methodBody.replace("{RETURN}", "");
        else
         methodBody = methodBody.replace("{RETURN}", "return");


         method.setBody(methodBody, "this", method.getName());



         methodNameBuilder.setLength(0);
        statmntBuilder.setLength(0);



        }




     final byte[] instrmntdByte = ctClass.toBytecode();



     return instrmntdByte;


    }

   } catch ( final Exception    e) {

    e.printStackTrace();
  }

   return classfileBuffer;

  }


  private boolean isGetterOrSetterMethod(ClassFile classFile,String methodName ) throws BadBytecode {

   final MethodInfo minfo = classFile.getMethod(methodName);
  final CodeAttribute codeAttr = minfo.getCodeAttribute();

   boolean isGetterOpCode = false;
  boolean isSetterOpCode = false;
  int opCodeCount = 0;

   if(!methodName.startsWith("get") &&  !methodName.startsWith("set"))
   return false;

   if( codeAttr == null)
   return true;


   final CodeIterator ci = codeAttr.iterator();


   while (ci.hasNext()) {
   opCodeCount++;
   final int index = ci.next();
   final int opByte = ci.byteAt(index);

    if(Mnemonic.OPCODE[opByte] != null )
    if(Mnemonic.OPCODE[opByte].equals("getfield"))
     isGetterOpCode = true;
    else if(Mnemonic.OPCODE[opByte].equals("putfield"))
     isSetterOpCode = true;

   }



   if(opCodeCount == 4 && isSetterOpCode || opCodeCount == 3 && isGetterOpCode)
   return true;

   return false;
 }

  private void makePrivateAccess(CtMethod copyMethod) {

   copyMethod.setModifiers( copyMethod.getModifiers() & ~(Modifier.PROTECTED | Modifier.PUBLIC)  | Modifier.PRIVATE );


  }

}

Step 3: Add entry into MANIFEST.MF file of jar file

Manifest-Version: 1.0 Premain-Class: com.elam.agent.profile.ElamJavaAgent Archiver-Version: Plexus Archiver

Step 4: Add this jar as JVM argument into your running application.

-javaagent:C:\Users\sudhagar\ElamJavaAgent-1.0.jar

Thats it about simple java profiler tool. This is just beginning, I will continue to enhance this tool to get proper details statistics reports out of it.

Please look at below github repository for complete source code. Thanks

https://github.com/getsudhagar/ElamJavaProfiler

comments powered by Disqus