changeset 5:28d99847408a

Add support for using asm to instrument bytecode
author Omair Majid <omajid@redhat.com>
date Thu, 28 Aug 2014 15:56:15 -0400
parents 0ac082b5cb28
children 560ea074aa69
files agent/ProfileUsingJavassist.java agent/com/redhat/omajid/Agent.java agent/com/redhat/omajid/ProfileUsingAsm.java agent/com/redhat/omajid/ProfileUsingJavassist.java agent/com/redhat/omajid/Profiler.java agent/com/redhat/omajid/ProfilerInstrumentor.java agent/manifest.mf attacher/Attacher.java lib/asm-all.jar make.sh
diffstat 10 files changed, 323 insertions(+), 177 deletions(-) [+]
line wrap: on
line diff
--- a/agent/ProfileUsingJavassist.java	Mon Aug 25 17:28:12 2014 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-import java.io.IOException;
-import java.lang.instrument.ClassFileTransformer;
-import java.lang.instrument.IllegalClassFormatException;
-import java.lang.instrument.Instrumentation;
-import java.lang.instrument.UnmodifiableClassException;
-import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
-import javassist.ByteArrayClassPath;
-import javassist.CannotCompileException;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtMethod;
-import javassist.Modifier;
-import javassist.NotFoundException;
-
-public class ProfileUsingJavassist {
-
-    private static List<String> ignorePackageRegexps = new ArrayList<>();
-
-    static {
-        // jdk packages
-        ignorePackageRegexps.add("java\\..*");
-        ignorePackageRegexps.add("javax\\..*");
-        ignorePackageRegexps.add("com\\.sun\\..*");
-        ignorePackageRegexps.add("sun\\..*");
-        ignorePackageRegexps.add("jdk\\..*");
-
-        // our library
-        ignorePackageRegexps.add("javassist\\..*");
-    }
-
-    public static void premain(String agentArgs, Instrumentation instrumentation) {
-        System.out.println("premain() called");
-        installProfiler(instrumentation);
-        instrumentAlreadyLoadedClasses(instrumentation);
-
-        addShutdownHookToPrintStatsOnEnd();
-    }
-
-    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
-        System.out.println("agentmain() called");
-        installProfiler(instrumentation);
-        instrumentAlreadyLoadedClasses(instrumentation);
-
-        addShutdownHookToPrintStatsOnEnd();
-    }
-
-    private static void installProfiler(Instrumentation instrumentation) {
-        ClassInstrumenter profiler = new ClassInstrumenter();
-        instrumentation.addTransformer(profiler, true);
-    }
-
-    private static void instrumentAlreadyLoadedClasses(Instrumentation instrumentation) {
-        for (Class<?> klass : instrumentation.getAllLoadedClasses()) {
-
-            if (klass.getName().startsWith(ProfileUsingJavassist.class.getName())) {
-                continue;
-            }
-
-            if (!instrumentation.isModifiableClass(klass)) {
-                continue;
-            }
-
-            Class<?>[] classes = new Class<?>[] { klass };
-            try {
-                instrumentation.retransformClasses(classes);
-            } catch (UnmodifiableClassException e) {
-                e.printStackTrace();
-            }
-
-        }
-    }
-
-
-    private static void addShutdownHookToPrintStatsOnEnd() {
-        Runtime.getRuntime().addShutdownHook(new Thread() {
-            @Override
-            public void run() {
-                System.out.println("=====");
-                System.out.println("Collected stats");
-                System.out.format("%100s\t%15s%n", "Method", "Total time (ns)");
-                Map<String, AtomicLong> data = Profiler.getInstance().getData();
-                for (Map.Entry<String, AtomicLong> entry : data.entrySet()) {
-                    System.out.format("%100s\t%15d%n", entry.getKey(), entry.getValue().get());
-                }
-                System.out.println("=====");
-            }
-        });
-    }
-
-
-    static class Profiler {
-
-        private static final Profiler profiler = new Profiler();
-
-        private ConcurrentHashMap<String,AtomicLong> profileData = new ConcurrentHashMap<>();
-
-        public static Profiler getInstance() {
-            return profiler;
-        }
-
-        public void addData(String dataName, long time) {
-            AtomicLong value = profileData.get(dataName);
-            if (value == null) {
-                value = profileData.putIfAbsent(dataName, new AtomicLong(time));
-            }
-            if (value != null) {
-                value.addAndGet(time);
-            }
-        }
-
-        public Map<String, AtomicLong> getData() {
-            return profileData;
-        }
-    }
-
-    static class ClassInstrumenter implements ClassFileTransformer {
-
-        private static final String PREFIX = "thermostatMonitoringAgentGeneratedSynthetic";
-        @Override
-        public byte[] transform(ClassLoader loader, String className,
-                Class<?> classBeingRedefined,
-                ProtectionDomain protectionDomain, byte[] classfileBuffer)
-                throws IllegalClassFormatException {
-
-            className = className.replace('/', '.');
-            for (String regex : ignorePackageRegexps) {
-                if (className.matches(regex)) {
-                    return null;
-                }
-            }
-
-            if (className.startsWith(ProfileUsingJavassist.class.getName())) {
-                return null;
-            }
-
-            System.out.println("transforming '" + className + "'");
-            ClassPool classPool = ClassPool.getDefault();
-
-            ByteArrayClassPath byteArrayClass = new ByteArrayClassPath(className, classfileBuffer);
-            classPool.insertClassPath(byteArrayClass);
-            try {
-                CtClass klass = classPool.get(className);
-                for (CtMethod method : klass.getDeclaredMethods()) {
-                    String name = method.getLongName();
-                    int modifiers = method.getModifiers();
-                    if (!Modifier.isAbstract(modifiers) &&
-                            !Modifier.isNative(modifiers)) {
-                        method.addLocalVariable(PREFIX + "StartTime", CtClass.longType);
-                        method.insertBefore(PREFIX + "StartTime = System.nanoTime();");
-                        method.insertAfter("ProfileUsingJavassist.Profiler.getInstance().addData(\"" + name + "\", (System.nanoTime() - " + PREFIX + "StartTime));");
-                    }
-                }
-                return klass.toBytecode();
-            } catch (NotFoundException | CannotCompileException | IOException e) {
-                System.err.println("Unable to patch " + className);
-                e.printStackTrace();
-                return null;
-            }
-
-        }
-
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/com/redhat/omajid/Agent.java	Thu Aug 28 15:56:15 2014 -0400
@@ -0,0 +1,76 @@
+package com.redhat.omajid;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Agent {
+
+    public static void premain(String agentArgs, Instrumentation instrumentation) {
+        System.out.println("premain() called");
+        installProfiler(agentArgs, instrumentation);
+        instrumentAlreadyLoadedClasses(instrumentation);
+
+        addShutdownHookToPrintStatsOnEnd();
+    }
+
+    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
+        System.out.println("agentmain() called");
+        installProfiler(agentArgs, instrumentation);
+        instrumentAlreadyLoadedClasses(instrumentation);
+
+        addShutdownHookToPrintStatsOnEnd();
+    }
+
+    private static void installProfiler(String mechanism, Instrumentation instrumentation) {
+        ProfilerInstrumentor profiler = null;
+        if (mechanism == null || mechanism.equals("") || mechanism.equals("javassist")) {
+            System.out.println("Using javassist");
+            profiler = new ProfileUsingJavassist();
+        } else {
+            System.out.println("Using asm");
+            profiler = new ProfileUsingAsm();
+        }
+        instrumentation.addTransformer(profiler, true);
+    }
+
+    private static void instrumentAlreadyLoadedClasses(Instrumentation instrumentation) {
+        for (Class<?> klass : instrumentation.getAllLoadedClasses()) {
+
+            if (klass.getName().startsWith(ProfileUsingAsm.class.getName())) {
+                continue;
+            }
+
+            if (!instrumentation.isModifiableClass(klass)) {
+                continue;
+            }
+
+            Class<?>[] classes = new Class<?>[] { klass };
+            try {
+                instrumentation.retransformClasses(classes);
+            } catch (UnmodifiableClassException e) {
+                e.printStackTrace();
+            }
+
+        }
+    }
+
+    private static void addShutdownHookToPrintStatsOnEnd() {
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                System.out.println("=====");
+                System.out.println("Collected stats");
+                System.out.format("%100s\t%15s%n", "Method", "Total time (ns)");
+                Map<String, AtomicLong> data = Profiler.getInstance().getData();
+                for (Map.Entry<String, AtomicLong> entry : data.entrySet()) {
+                    System.out.format("%100s\t%15d%n", entry.getKey(), entry.getValue().get());
+                }
+                System.out.println("=====");
+            }
+        });
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/com/redhat/omajid/ProfileUsingAsm.java	Thu Aug 28 15:56:15 2014 -0400
@@ -0,0 +1,89 @@
+package com.redhat.omajid;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.AdviceAdapter;
+
+public class ProfileUsingAsm extends ProfilerInstrumentor {
+
+    @Override
+    public byte[] transform(String className, byte[] classBytes) {
+        ClassReader reader = new ClassReader(classBytes);
+        ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
+        InstrumentingClassAdapter instrumentor = new InstrumentingClassAdapter(writer);
+        reader.accept(instrumentor, ClassReader.SKIP_FRAMES);
+
+        return writer.toByteArray();
+
+    }
+
+    static class InstrumentingClassAdapter extends ClassAdapter {
+
+        private String className;
+
+        public InstrumentingClassAdapter(ClassVisitor visitor) {
+            super(visitor);
+        }
+
+        @Override
+        public void visit(int version, int access, String name,
+                String signature, String superName, String[] interfaces) {
+            super.visit(version, access, name, signature, superName, interfaces);
+
+            className = name;
+        }
+        @Override
+        public MethodVisitor visitMethod(int access, String name, String desc,
+                String signature, String[] exceptions) {
+            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
+            
+            if (mv != null) {
+                mv = new InstrumentingMethodAdapter(mv, className, access, name, desc);
+            }
+            return mv;
+        }
+    }
+
+    static class InstrumentingMethodAdapter extends AdviceAdapter {
+
+        private int time;
+
+        private String className;
+        private String methodName;
+
+        protected InstrumentingMethodAdapter(MethodVisitor mv, String className, int access, String methodName, String desc) {
+            super(mv, access, methodName, desc);
+
+            this.className = className;
+            this.methodName = methodName;
+        }
+
+        @Override
+        protected void onMethodEnter() {
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J");
+            time = newLocal(Type.LONG_TYPE);
+            mv.visitVarInsn(Opcodes.LSTORE, time);
+        }
+        
+        @Override
+        protected void onMethodExit(int opCode) {
+            String profilerClassName = Profiler.class.getCanonicalName().replace('.', '/');
+            
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J");
+            mv.visitVarInsn(Opcodes.LLOAD, time);
+            mv.visitInsn(Opcodes.LSUB);
+            int diff = newLocal(Type.LONG_TYPE);
+            mv.visitVarInsn(Opcodes.LSTORE, diff);
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, profilerClassName, "getInstance", "()L" + profilerClassName + ";");
+            mv.visitLdcInsn(className + "." + methodName + methodDesc);
+            mv.visitVarInsn(Opcodes.LLOAD, diff);
+            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, profilerClassName, "addData", "(Ljava/lang/String;J)V");
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/com/redhat/omajid/ProfileUsingJavassist.java	Thu Aug 28 15:56:15 2014 -0400
@@ -0,0 +1,41 @@
+package com.redhat.omajid;
+import java.io.IOException;
+
+import javassist.ByteArrayClassPath;
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.Modifier;
+import javassist.NotFoundException;
+
+public class ProfileUsingJavassist extends ProfilerInstrumentor {
+
+    private static final String PREFIX = "thermostatMonitoringAgentGeneratedSynthetic";
+
+    @Override
+    public byte[] transform(String className, byte[] classBytes) {
+        ClassPool classPool = ClassPool.getDefault();
+
+        ByteArrayClassPath byteArrayClass = new ByteArrayClassPath(className, classBytes);
+        classPool.insertClassPath(byteArrayClass);
+        try {
+            CtClass klass = classPool.get(className);
+            for (CtMethod method : klass.getDeclaredMethods()) {
+                String name = method.getLongName();
+                int modifiers = method.getModifiers();
+                if (!Modifier.isAbstract(modifiers) &&
+                        !Modifier.isNative(modifiers)) {
+                    method.addLocalVariable(PREFIX + "StartTime", CtClass.longType);
+                    method.insertBefore(PREFIX + "StartTime = System.nanoTime();");
+                    method.insertAfter(Profiler.class.getName() + ".getInstance().addData(\"" + name + "\", (System.nanoTime() - " + PREFIX + "StartTime));");
+                }
+            }
+            return klass.toBytecode();
+        } catch (NotFoundException | CannotCompileException | IOException e) {
+            System.err.println("Unable to patch " + className);
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/com/redhat/omajid/Profiler.java	Thu Aug 28 15:56:15 2014 -0400
@@ -0,0 +1,29 @@
+package com.redhat.omajid;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Profiler {
+
+    private static final Profiler profiler = new Profiler();
+
+    private ConcurrentHashMap<String,AtomicLong> profileData = new ConcurrentHashMap<>();
+
+    public static Profiler getInstance() {
+        return profiler;
+    }
+
+    public void addData(String dataName, long time) {
+        AtomicLong value = profileData.get(dataName);
+        if (value == null) {
+            value = profileData.putIfAbsent(dataName, new AtomicLong(time));
+        }
+        if (value != null) {
+            value.addAndGet(time);
+        }
+    }
+
+    public Map<String, AtomicLong> getData() {
+        return profileData;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/com/redhat/omajid/ProfilerInstrumentor.java	Thu Aug 28 15:56:15 2014 -0400
@@ -0,0 +1,62 @@
+package com.redhat.omajid;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class ProfilerInstrumentor implements ClassFileTransformer {
+
+    private static List<String> ignorePackageRegexps = new ArrayList<>();
+
+    static {
+        // jdk packages
+        ignorePackageRegexps.add("java\\..*");
+        ignorePackageRegexps.add("javax\\..*");
+        ignorePackageRegexps.add("com\\.sun\\..*");
+        ignorePackageRegexps.add("sun\\..*");
+        ignorePackageRegexps.add("jdk\\..*");
+
+        // this class
+        ignorePackageRegexps.add(ProfilerInstrumentor.class.getPackage().getName() + "\\..*");
+
+        // our libraries
+        ignorePackageRegexps.add("javassist\\..*");
+        ignorePackageRegexps.add("org.objectweb.asm\\..*");
+    }
+
+    public static void main(String[] args) {
+        for (String exp : ignorePackageRegexps) {
+            System.out.println(exp);
+        }
+    }
+
+    @Override
+    public byte[] transform(ClassLoader loader, String className,
+            Class<?> classBeingRedefined,
+            ProtectionDomain protectionDomain, byte[] classfileBuffer)
+            throws IllegalClassFormatException {
+
+        className = className.replace('/', '.');
+        for (String regex : ignorePackageRegexps) {
+            if (className.matches(regex)) {
+                return null;
+            }
+        }
+
+        if (className.startsWith(ProfilerInstrumentor.class.getName())) {
+            return null;
+        }
+
+        System.out.println("transforming '" + className + "'");
+        
+        return transform(className, classfileBuffer);
+    }
+
+    public byte[] transform(String className, byte[] classBytes) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+}
+
--- a/agent/manifest.mf	Mon Aug 25 17:28:12 2014 -0400
+++ b/agent/manifest.mf	Thu Aug 28 15:56:15 2014 -0400
@@ -1,4 +1,4 @@
-Premain-Class: ProfileUsingJavassist
-Agent-Class: ProfileUsingJavassist
-Boot-Class-Path: ../lib/javassist.jar
+Premain-Class: com.redhat.omajid.Agent
+Agent-Class: com.redhat.omajid.Agent
+Boot-Class-Path: javassist.jar asm-all.jar
 Can-Retransform-Classes: true
--- a/attacher/Attacher.java	Mon Aug 25 17:28:12 2014 -0400
+++ b/attacher/Attacher.java	Thu Aug 28 15:56:15 2014 -0400
@@ -9,9 +9,10 @@
     public static void main(String[] args) {
         String pid = args[0];
         String agentJar = args[1];
+        String tool = args[2];
         try {
             VirtualMachine vm = VirtualMachine.attach(pid);
-            vm.loadAgent(agentJar);
+            vm.loadAgent(agentJar, tool);
         } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) {
             System.err.println("Unable to attach to pid " + pid);
             e.printStackTrace(System.err);
Binary file lib/asm-all.jar has changed
--- a/make.sh	Mon Aug 25 17:28:12 2014 -0400
+++ b/make.sh	Thu Aug 28 15:56:15 2014 -0400
@@ -1,4 +1,4 @@
-#!/bin/bash -e
+#!/bin/bash -ex
 
 set -o monitor # job control
 
@@ -22,6 +22,7 @@
 TEST_BUILD=build/test
 
 JAVASSIST_JAR=lib/javassist.jar
+ASM_JAR=lib/asm-all.jar
 
 # Remove everything
 rm -rf ${BUILD}
@@ -29,7 +30,7 @@
 # Build agent
 mkdir -p ${AGENT_BUILD}
 find ${AGENT_SRC} -iname '*.java' > ${BUILD}/agent-java-files
-${JAVAC} @${BUILD}/agent-java-files -cp ${JAVASSIST_JAR} -d ${AGENT_BUILD}
+${JAVAC} @${BUILD}/agent-java-files -cp ${JAVASSIST_JAR}:${ASM_JAR} -d ${AGENT_BUILD}
 ${JAR} cmf ${AGENT_SRC}/manifest.mf ${BUILD}/${AGENT_JAR_NAME} -C ${AGENT_BUILD} .
 
 # Build attacher
@@ -38,15 +39,31 @@
 ${JAVAC} @${BUILD}/attacher-java-files -cp ${TOOLS_JAR} -d ${ATTACHER_BUILD}
 ${JAR} cmf ${ATTACHER_SRC}/manifest.mf ${BUILD}/${ATTACHER_JAR_NAME} -C ${ATTACHER_BUILD} .
 
+# copy libs over
+cp ${JAVASSIST_JAR} ${BUILD}
+cp ${ASM_JAR} ${BUILD}
+
 # build test programs
 mkdir -p ${TEST_BUILD}
 find ${TEST_SRC} -iname '*.java' > ${BUILD}/test-java-files
 ${JAVAC} @${BUILD}/test-java-files -d ${TEST_BUILD}
 
+TOOL=javassist
+
 # Run load-agent-on-startup
-${JAVA} -javaagent:${BUILD}/${AGENT_JAR_NAME} -cp ${TEST_BUILD} PrintDateAndTime
+${JAVA} -javaagent:${BUILD}/${AGENT_JAR_NAME}=${TOOL} -cp ${TEST_BUILD} PrintDateAndTime
 
 # Run load-agent-after-startup
 ${JAVA} -cp ${TEST_BUILD} ContinuousObjectAllocator & PID="$!"
-${JAVA} -cp ${TOOLS_JAR}:${BUILD}/${ATTACHER_JAR_NAME} Attacher ${PID} ${BUILD}/${AGENT_JAR_NAME}
+${JAVA} -cp ${TOOLS_JAR}:${BUILD}/${ATTACHER_JAR_NAME} Attacher ${PID} ${BUILD}/${AGENT_JAR_NAME} ${TOOL}
 wait %1
+
+TOOL=asm
+
+# Run load-agent-on-startup
+${JAVA} -javaagent:${BUILD}/${AGENT_JAR_NAME}=${TOOL} -cp ${TEST_BUILD} PrintDateAndTime
+
+# Run load-agent-after-startup
+${JAVA} -cp ${TEST_BUILD} ContinuousObjectAllocator & PID="$!"
+${JAVA} -cp ${TOOLS_JAR}:${BUILD}/${ATTACHER_JAR_NAME} Attacher ${PID} ${BUILD}/${AGENT_JAR_NAME} ${TOOL}
+wait %1