changeset 1553:24397a6eba11

Make profile-vm use an instrumenting profiler Reviewed-by: jerboaa, neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011402.html
author Omair Majid <omajid@redhat.com>
date Tue, 18 Nov 2014 14:04:56 -0500
parents 7b820886678a
children 700efd315f0e
files pom.xml vm-profiler/agent/pom.xml vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerException.java vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java vm-profiler/agent/src/main/resources/com/redhat/thermostat/vm/profiler/agent/internal/settings.properties vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiverTest.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java vm-profiler/distribution/pom.xml vm-profiler/jvm-agent/pom.xml vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/AsmBasedInstrumentor.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ClassLoaderFriendlyClassWriter.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/Main.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfileRecorder.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerAgent.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerInstrumentor.java vm-profiler/jvm-agent/src/main/resources/META-INF/MANIFEST.MF vm-profiler/pom.xml
diffstat 19 files changed, 1123 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/pom.xml	Tue Nov 18 14:02:06 2014 -0500
+++ b/pom.xml	Tue Nov 18 14:04:56 2014 -0500
@@ -229,6 +229,7 @@
     <expectj.version>2.0.7</expectj.version>
 
     <jdktools.version>1.7.0</jdktools.version>
+    <asm.version>5.0.3</asm.version>
     <jfreechart.version>1.0.14</jfreechart.version>
     <!-- This should match the version jfreechart pulls in (if any).
          See the main thermostat bash script where this property is
--- a/vm-profiler/agent/pom.xml	Tue Nov 18 14:02:06 2014 -0500
+++ b/vm-profiler/agent/pom.xml	Tue Nov 18 14:04:56 2014 -0500
@@ -47,6 +47,22 @@
   <packaging>bundle</packaging>
   <name>Thermostat VM Profiler Agent plugin</name>
   <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <excludes>
+          <exclude>**/*.png</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <includes>
+          <include>**/*.png</include>
+        </includes>
+      </resource>
+    </resources>
     <plugins>
       <plugin>
         <groupId>org.apache.felix</groupId>
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java	Tue Nov 18 14:02:06 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java	Tue Nov 18 14:04:56 2014 -0500
@@ -36,25 +36,40 @@
 
 package com.redhat.thermostat.vm.profiler.agent.internal;
 
+import java.util.Properties;
+
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.agent.command.ReceiverRegistry;
 
 public class Activator implements BundleActivator {
 
-    private ReceiverRegistry register;
+    private ReceiverRegistry requestHandlerRegisteration;
+    private VmStatusListenerRegistrar vmStatusRegistrar;
+    private ProfileVmRequestReceiver profileRequestHandler;
 
     @Override
     public void start(final BundleContext context) throws Exception {
-        register = new ReceiverRegistry(context);
-        register.registerReceiver(new ProfileVmRequestReceiver());
+        Properties configuration = new Properties();
+        configuration.load(this.getClass().getResourceAsStream("settings.properties"));
+        VmProfiler profiler = new VmProfiler(configuration);
+        profileRequestHandler = new ProfileVmRequestReceiver(profiler);
+
+        requestHandlerRegisteration = new ReceiverRegistry(context);
+        requestHandlerRegisteration.registerReceiver(profileRequestHandler);
+
+        vmStatusRegistrar = new VmStatusListenerRegistrar(context);
+        vmStatusRegistrar.register(profileRequestHandler);
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        register.unregisterReceivers();
-        register = null;
+        requestHandlerRegisteration.unregisterReceivers();
+        requestHandlerRegisteration = null;
+
+        vmStatusRegistrar.unregister(profileRequestHandler);
     }
 }
 
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java	Tue Nov 18 14:02:06 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java	Tue Nov 18 14:04:56 2014 -0500
@@ -36,30 +36,93 @@
 
 package com.redhat.thermostat.vm.profiler.agent.internal;
 
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.agent.VmStatusListener;
 import com.redhat.thermostat.agent.command.RequestReceiver;
 import com.redhat.thermostat.common.command.Request;
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
 
-public class ProfileVmRequestReceiver implements RequestReceiver {
+public class ProfileVmRequestReceiver implements RequestReceiver, VmStatusListener {
+
+    private static final Logger logger = LoggingUtils.getLogger(ProfileVmRequestReceiver.class);
+
+    private ConcurrentHashMap<String, Integer> vmIdToPid = new ConcurrentHashMap<>();
+
+    /** A pid that corresponds to an unknown */
+    private static final int UNKNOWN_VMID = -1;
+
+    private final VmProfiler profiler;
+
+    public ProfileVmRequestReceiver(VmProfiler profiler) {
+        this.profiler = profiler;
+    }
+
+    @Override
+    public void vmStatusChanged(Status newStatus, String vmId, int pid) {
+        if (newStatus == Status.VM_ACTIVE || newStatus == Status.VM_STARTED) {
+            // assert not already being profiled
+            vmIdToPid.putIfAbsent(vmId, pid);
+        } else {
+            // FIXME disable profiler if active?
+            vmIdToPid.remove(vmId, pid);
+        }
+    }
 
     @Override
     public Response receive(Request request) {
+        final Response OK = new Response(ResponseType.OK);
+        final Response ERROR = new Response(ResponseType.NOK);
+
         String value = request.getParameter(ProfileRequest.PROFILE_ACTION);
         String vmId = request.getParameter(ProfileRequest.VM_ID);
+
+        int pid = getPid(vmId);
+        if (pid == UNKNOWN_VMID) {
+            logger.warning("Unknown vmId: " + vmId + ". Known vmIds are : " + vmIdToPid.keySet().toString());
+            return ERROR;
+        }
+
         switch (value) {
         case ProfileRequest.START_PROFILING:
-            System.out.println("Starting profiling");
-            break;
+            logger.info("Starting profiling " + pid);
+            try {
+                profiler.startProfiling(pid);
+                return OK;
+            } catch (Exception e) {
+                logger.log(Level.INFO, "start profiling failed", e);
+                return ERROR;
+            }
+            /* should not reach here */
         case ProfileRequest.STOP_PROFILING:
-            System.out.println("Stopping profiling");
-            break;
+            logger.info("Stopping profiling " + pid);
+            try {
+                profiler.stopProfiling(pid);
+                return OK;
+            } catch (Exception e) {
+                logger.log(Level.INFO, "stop profiling failed", e);
+                return ERROR;
+            }
+            /* should not reach here */
         default:
-            return new Response(ResponseType.ERROR);
+            logger.warning("Unknown command: '" + value + "'");
+            return ERROR;
         }
+    }
 
-        return new Response(ResponseType.OK);
+    /** Return the pid or {@link #UNKNOWN_VMID} */
+    private int getPid(String vmId) {
+        Integer pid = vmIdToPid.get(vmId);
+        if (pid == null) {
+            return UNKNOWN_VMID;
+        } else {
+            return pid;
+        }
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerException.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.internal;
+
+public class ProfilerException extends Exception {
+
+    public ProfilerException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.internal;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.sun.tools.attach.AgentInitializationException;
+import com.sun.tools.attach.AgentLoadException;
+import com.sun.tools.attach.AttachNotSupportedException;
+import com.sun.tools.attach.VirtualMachine;
+
+public class VmProfiler {
+
+    private static final Logger logger = LoggingUtils.getLogger(VmProfiler.class);
+
+    private final Attacher attacher;
+
+    private String agentJarPath;
+    private String asmJarPath;
+
+    public VmProfiler(Properties configuration) {
+        this(configuration, new Attacher());
+    }
+
+    public VmProfiler(Properties configuration, Attacher attacher) {
+        this.attacher = attacher;
+
+        // requireNonNull protects against bad config with missing values
+        agentJarPath = Objects.requireNonNull(configuration.getProperty("AGENT_JAR"));
+        asmJarPath = Objects.requireNonNull(configuration.getProperty("ASM_JAR"));
+    }
+
+    public void startProfiling(int pid) throws ProfilerException {
+        try {
+            VirtualMachine vm = attacher.attach(String.valueOf(pid));
+            try {
+                String jarsToLoad = ""; // asmJarPath + ":" + agentJarPath;
+                logger.info("Asking " + pid + " to load agent '" + agentJarPath + "' with arguments '" + jarsToLoad + "'");
+                vm.loadAgent(agentJarPath, jarsToLoad);
+            } catch (AgentLoadException | AgentInitializationException e) {
+                throw new ProfilerException("Error starting profiler", e);
+            } finally {
+                vm.detach();
+            }
+        } catch (IOException | AttachNotSupportedException e) {
+            throw new ProfilerException("Error starting profiler", e);
+        }
+    }
+
+    public void stopProfiling(int pid) throws ProfilerException {
+        // TODO Auto-generated method stub
+    }
+
+    static class Attacher {
+        VirtualMachine attach(String pid) throws AttachNotSupportedException, IOException {
+            return VirtualMachine.attach(pid);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/main/resources/com/redhat/thermostat/vm/profiler/agent/internal/settings.properties	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,2 @@
+AGENT_JAR = ${thermostat.vm.profiler.dir}/thermostat-vm-profiler-jvm-agent-${project.version}.jar
+ASM_JAR = ${thermostat.vm.profiler.dir}/asm-all-${asm.version}.jar
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiverTest.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
+
+public class ProfileVmRequestReceiverTest {
+
+    private static final String VM_ID = "foo";
+    private static final int VM_PID = 1;
+
+    private ProfileVmRequestReceiver requestReceiver;
+
+    private VmProfiler profiler;
+
+    @Before
+    public void setUp() {
+        profiler = mock(VmProfiler.class);
+
+        requestReceiver = new ProfileVmRequestReceiver(profiler);
+    }
+
+    @Test
+    public void doesNotProfileUnknownVm() {
+        Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING);
+        Response result = requestReceiver.receive(request);
+        assertEquals(ResponseType.NOK, result.getType());
+    }
+
+    @Test
+    public void profilesKnownVms() throws ProfilerException {
+        Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING);
+
+        requestReceiver.vmStatusChanged(Status.VM_STARTED, VM_ID, VM_PID);
+        Response result = requestReceiver.receive(request);
+
+        assertEquals(ResponseType.OK, result.getType());
+        verify(profiler).startProfiling(VM_PID);
+    }
+
+    @Test
+    public void startAndStopProfiling() throws ProfilerException {
+        Request request;
+        request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING);
+
+        requestReceiver.vmStatusChanged(Status.VM_STARTED, VM_ID, VM_PID);
+        Response result = requestReceiver.receive(request);
+
+        assertEquals(ResponseType.OK, result.getType());
+        verify(profiler).startProfiling(VM_PID);
+
+        request = ProfileRequest.create(null, VM_ID, ProfileRequest.STOP_PROFILING);
+        result = requestReceiver.receive(request);
+
+        assertEquals(ResponseType.OK, result.getType());
+        verify(profiler).stopProfiling(VM_PID);
+    }
+
+    @Test
+    public void doesNotProfileDeadVms() {
+        requestReceiver.vmStatusChanged(Status.VM_STARTED, VM_ID, VM_PID);
+        requestReceiver.vmStatusChanged(Status.VM_STOPPED, VM_ID, VM_PID);
+
+        Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING);
+        Response result = requestReceiver.receive(request);
+        assertEquals(ResponseType.NOK, result.getType());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.internal;
+
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.vm.profiler.agent.internal.VmProfiler.Attacher;
+import com.sun.tools.attach.AgentInitializationException;
+import com.sun.tools.attach.AgentLoadException;
+import com.sun.tools.attach.AttachNotSupportedException;
+import com.sun.tools.attach.VirtualMachine;
+
+public class VmProfilerTest {
+
+    private VmProfiler profiler;
+    private VirtualMachine vm;
+    private Attacher attacher;
+
+    private final String AGENT_JAR = "foo";
+    private final String ASM_JAR = "bar";
+
+    @Before
+    public void setUp() throws Exception {
+        Properties props = new Properties();
+        props.setProperty("AGENT_JAR", AGENT_JAR);
+        props.setProperty("ASM_JAR", ASM_JAR);
+
+        attacher = mock(Attacher.class);
+        vm = mock(VirtualMachine.class);
+        when(attacher.attach(isA(String.class))).thenReturn(vm);
+
+        profiler = new VmProfiler(props, attacher);
+    }
+
+    @Test
+    public void loadsJvmAgent() throws ProfilerException, AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException {
+        final int PID = 0;
+
+        profiler.startProfiling(PID);
+
+        verify(attacher).attach(String.valueOf(PID));
+        verify(vm).loadAgent(AGENT_JAR, "");
+        verify(vm).detach();
+    }
+}
--- a/vm-profiler/distribution/pom.xml	Tue Nov 18 14:02:06 2014 -0500
+++ b/vm-profiler/distribution/pom.xml	Tue Nov 18 14:04:56 2014 -0500
@@ -112,6 +112,16 @@
       <artifactId>thermostat-vm-profiler-agent</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-profiler-jvm-agent</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm-all</artifactId>
+      <version>${asm.version}</version>
+    </dependency>
   </dependencies>
 
 </project>
--- a/vm-profiler/jvm-agent/pom.xml	Tue Nov 18 14:02:06 2014 -0500
+++ b/vm-profiler/jvm-agent/pom.xml	Tue Nov 18 14:04:56 2014 -0500
@@ -52,6 +52,22 @@
     thermostat and does not know about thermostat.  It is not OSGi, and
     has no dependencies on OSGi.
   -->
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
+            <manifestEntries>
+              <Boot-Class-Path>${thermostat.vm.profiler.dir}asm-all-${asm.version}.jar ${thermostat.vm.profiler.dir}thermostat-vm-profiler-jvm-agent-${project.version}.jar</Boot-Class-Path>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
 
   <dependencies>
     <dependency>
@@ -64,5 +80,10 @@
       <artifactId>mockito-core</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm-all</artifactId>
+      <version>${asm.version}</version>
+    </dependency>
   </dependencies>
 </project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/AsmBasedInstrumentor.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.jvm;
+
+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 AsmBasedInstrumentor extends ProfilerInstrumentor {
+
+    private static final String RECORDER_CLASS_NAME =
+            ProfileRecorder.class.getCanonicalName().replace('.', '/');
+
+    @Override
+    public byte[] transform(ClassLoader cl, String className, byte[] classBytes) {
+        try {
+            Thread.currentThread().setContextClassLoader(cl);
+            // pipe data: reader -> instrumentor -> writer
+            ClassReader reader = new ClassReader(classBytes);
+            ClassWriter writer = new ClassLoaderFriendlyClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS, cl);
+            InstrumentingClassAdapter instrumentor = new InstrumentingClassAdapter(writer);
+            reader.accept(instrumentor, ClassReader.SKIP_FRAMES);
+            return writer.toByteArray();
+
+        } catch (Exception e) {
+            System.err.println("Error transforming: " + className);
+            e.printStackTrace();
+            throw new AssertionError(e);
+        }
+    }
+
+    static class InstrumentingClassAdapter extends ClassVisitor {
+
+        private String className;
+
+        public InstrumentingClassAdapter(ClassVisitor visitor) {
+            super(Opcodes.ASM5, 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) {
+                throw new AssertionError("mv is null");
+            }
+            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(Opcodes.ASM5, mv, access, methodName, desc);
+
+            this.className = className;
+            this.methodName = methodName;
+        }
+
+        @Override
+        protected void onMethodEnter() {
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
+            time = newLocal(Type.LONG_TYPE);
+            mv.visitVarInsn(Opcodes.LSTORE, time);
+        }
+
+        @Override
+        protected void onMethodExit(int opCode) {
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
+            mv.visitVarInsn(Opcodes.LLOAD, time);
+            mv.visitInsn(Opcodes.LSUB);
+            int diff = newLocal(Type.LONG_TYPE);
+            mv.visitVarInsn(Opcodes.LSTORE, diff);
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, RECORDER_CLASS_NAME, "getInstance", "()L" + RECORDER_CLASS_NAME + ";", false);
+            mv.visitLdcInsn(className + "." + methodName + methodDesc);
+            mv.visitVarInsn(Opcodes.LLOAD, diff);
+            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, RECORDER_CLASS_NAME, "addData", "(Ljava/lang/String;J)V", false);
+        }
+
+        // for debugging: insert opcodes to invoke System.exit()
+        // private void insertSystemExit() {
+        //     mv.visitInsn(ICONST_1);
+        //     mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "exit", "(I)V", false);
+        // }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ClassLoaderFriendlyClassWriter.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.jvm;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+/** A {@link ClassWriter} that works with custom classloaders */
+public class ClassLoaderFriendlyClassWriter extends ClassWriter {
+
+    private ClassLoader loader;
+
+    public ClassLoaderFriendlyClassWriter(ClassReader classReader, int flags, ClassLoader loader) {
+        super(classReader, flags);
+        this.loader = loader;
+    }
+
+    // this plays nicer with custom classloaders
+    @Override
+    protected String getCommonSuperClass(String type1, String type2) {
+        // this is code from ClassWriter.getCommonSuperClass except with the
+        // hardcoded loader replaced with user-supplied classloader
+        Class<?> c, d;
+        try {
+            c = Class.forName(type1.replace('/', '.'), false, loader);
+            d = Class.forName(type2.replace('/', '.'), false, loader);
+        } catch (Exception e) {
+            throw new RuntimeException(e.toString());
+        }
+        if (c.isAssignableFrom(d)) {
+            return type1;
+        }
+        if (d.isAssignableFrom(c)) {
+            return type2;
+        }
+        if (c.isInterface() || d.isInterface()) {
+            return "java/lang/Object";
+        } else {
+            do {
+                c = c.getSuperclass();
+            } while (!c.isAssignableFrom(d));
+            return c.getName().replace('.', '/');
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/Main.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.jvm;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Main {
+
+    private Instrumentation instrumentation;
+
+    public Main(Instrumentation instrumentation) {
+        this.instrumentation = instrumentation;
+    }
+
+    public void run() {
+        // System.out.println("AGENT: My classloader is " + this.getClass().getClassLoader());
+        handleInitializationTasks();
+    }
+
+    public void handleInitializationTasks() {
+        long start = System.nanoTime();
+
+        // TODO defer any action till later to make sure the agent gets
+        // installed. Later actions can fail without hanging things.
+
+        addShutdownHookToPrintStatsOnEnd();
+        ProfilerInstrumentor profiler = installProfiler(instrumentation);
+
+        instrumentAlreadyLoadedClasses(instrumentation, profiler);
+        long end = System.nanoTime();
+        System.out.println("AGENT: done in : " + (end - start) + "ns");
+    }
+
+    private static ProfilerInstrumentor installProfiler(Instrumentation instrumentation) {
+        ProfilerInstrumentor inst = new AsmBasedInstrumentor();
+        instrumentation.addTransformer(inst, true);
+        return inst;
+    }
+
+    private static void instrumentAlreadyLoadedClasses(Instrumentation instrumentation, ProfilerInstrumentor profiler) {
+        long start = System.nanoTime();
+
+        List<Class<?>> toTransform = new ArrayList<>();
+
+        for (Class<?> klass : instrumentation.getAllLoadedClasses()) {
+            boolean skipThisClass = false;
+            if (!instrumentation.isModifiableClass(klass)) {
+                skipThisClass = true;
+            }
+            if (!profiler.shouldInstrument(klass)) {
+                skipThisClass = true;
+            }
+
+            if (skipThisClass) {
+                continue;
+            }
+
+            toTransform.add(klass);
+        }
+
+        if (toTransform.size() > 0) {
+            System.out.println("AGENT: Retransforming " + toTransform.size() + " classes");
+            try {
+                instrumentation.retransformClasses(toTransform.toArray(new Class<?>[toTransform.size()]));
+            } catch (UnmodifiableClassException e) {
+                throw new AssertionError("Tried to modify an unmodifiable class", e);
+            } catch (InternalError e) {
+                e.printStackTrace();
+                System.err.println("Error retransforming already loaded classes.");
+            }
+        }
+        long end = System.nanoTime();
+        System.out.println("AGENT: Retansforming took: " + (end - start) + "ns");
+    }
+
+    private static void addShutdownHookToPrintStatsOnEnd() {
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                System.out.println("=====");
+                System.out.println("Collected stats");
+                System.out.format("%15s\t%s%n", "Total time (ns)", "Method");
+                Map<String, AtomicLong> data = ProfileRecorder.getInstance().getData();
+                for (Map.Entry<String, AtomicLong> entry : data.entrySet()) {
+                    System.out.format("%15d\t%s%n", entry.getValue().get(), entry.getKey());
+                }
+                System.out.println("=====");
+            }
+        });
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfileRecorder.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.jvm;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ProfileRecorder {
+
+    private static final ProfileRecorder profileRecorder = new ProfileRecorder();
+
+    private ConcurrentHashMap<String,AtomicLong> profileData = new ConcurrentHashMap<>();
+
+    public static ProfileRecorder getInstance() {
+        return profileRecorder;
+    }
+
+    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/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerAgent.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.jvm;
+
+import java.io.IOException;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.jar.JarFile;
+
+/**
+ * This is an {@link Instrumentation} agent that sets up the classpath with jars
+ * supplied as agent parameters and then invokes {@link Main}.
+ */
+public class ProfilerAgent {
+
+    public static void premain(String agentArgs, Instrumentation instrumentation) {
+        initializeAgent(agentArgs, instrumentation);
+    }
+
+    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
+        initializeAgent(agentArgs, instrumentation);
+    }
+
+    private static void initializeAgent(String args, Instrumentation instrumentation) {
+        // This is the saner approach, but for now we are brute-forcing a
+        // hardcoded path using a manifest entry
+        // String jars = args;
+        // addJarsToClassPath(jars, instrumentation);
+
+        invokeMain(instrumentation);
+    }
+
+    private static void addJarsToClassPath(String jars, Instrumentation instrumentation) throws AssertionError {
+        // System.out.println("Classpath: " + System.getProperty("java.class.path"));
+        boolean addToBoot = true;
+        String[] jarPaths = jars.split(":");
+        for (String jarPath : jarPaths) {
+            JarFile jarFile = null;
+            try {
+                jarFile = new JarFile(jarPath);
+                // FIXME needs to be bootclassloader if it is to be visible everywhere
+                if (addToBoot) {
+                    instrumentation.appendToBootstrapClassLoaderSearch(jarFile);
+                } else {
+                    instrumentation.appendToSystemClassLoaderSearch(jarFile);
+                }
+                System.out.println("AGENT: Added" + jarPath + " to " + (addToBoot ? "bootstrap" : "system") + " classpath");
+            } catch (IOException e) {
+                throw new AssertionError(jarFile + " not found!");
+            }
+        }
+    }
+
+    private static void invokeMain(Instrumentation instrumentation) {
+        try {
+            // do this via reflection so the version in system boot classpath is used!
+            Class<?> klass = ClassLoader.getSystemClassLoader().loadClass("com.redhat.thermostat.vm.profiler.agent.jvm.Main");
+            Constructor<?> constructor = klass.getConstructor(Instrumentation.class);
+            Object main = constructor.newInstance(instrumentation);
+            Method runMethod = klass.getMethod("run");
+            runMethod.invoke(main);
+        } catch (ReflectiveOperationException | SecurityException e) {
+            e.printStackTrace();
+            System.err.println("Unable to initialize agent");
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerInstrumentor.java	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.agent.jvm;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class ProfilerInstrumentor implements ClassFileTransformer {
+
+    private static List<Pattern> ignorePackageRegexps = new ArrayList<>();
+
+    static {
+        // jdk packages
+        ignorePackageRegexps.add(Pattern.compile("java\\..*"));
+        ignorePackageRegexps.add(Pattern.compile("javax\\..*"));
+        ignorePackageRegexps.add(Pattern.compile("com\\.sun\\..*"));
+        ignorePackageRegexps.add(Pattern.compile("sun\\..*"));
+        ignorePackageRegexps.add(Pattern.compile("jdk\\..*"));
+
+        // this class
+        ignorePackageRegexps.add(Pattern.compile("com\\.redhat\\.thermostat\\.vm\\.profiler\\.agent\\.jvm\\..*"));
+
+        // our dependencies
+        ignorePackageRegexps.add(Pattern.compile("org.objectweb.asm\\..*"));
+    }
+
+    @Override
+    public byte[] transform(ClassLoader loader, String className,
+            Class<?> classBeingRedefined,
+            ProtectionDomain protectionDomain, byte[] classfileBuffer)
+            throws IllegalClassFormatException {
+
+        className = className.replace('/', '.');
+        if (!shouldInstrument(className)) {
+            return null;
+        }
+
+        // System.out.println("transforming '" + className + "'");
+
+        return transform(loader, className, classfileBuffer);
+    }
+
+    public boolean shouldInstrument(Class<?> clazz) {
+        if (clazz.isArray()) {
+            return false;
+        }
+
+        // TODO what to do with anonymous classes?
+
+        return shouldInstrument(clazz.getName());
+    }
+
+    public boolean shouldInstrument(String className) {
+        if (className == null) {
+            return true;
+        }
+        for (Pattern packagePattern : ignorePackageRegexps) {
+            if (packagePattern.matcher(className).matches()) {
+                return false;
+            }
+        }
+
+        if (className.startsWith(ProfilerInstrumentor.class.getName())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public abstract byte[] transform(ClassLoader cl, String className, byte[] classBytes);
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/jvm-agent/src/main/resources/META-INF/MANIFEST.MF	Tue Nov 18 14:04:56 2014 -0500
@@ -0,0 +1,4 @@
+Premain-Class: com.redhat.thermostat.vm.profiler.agent.jvm.ProfilerAgent
+Agent-Class: com.redhat.thermostat.vm.profiler.agent.jvm.ProfilerAgent
+Can-Retransform-Classes: true
+Comment: Boot-Class-Path added by build system
\ No newline at end of file
--- a/vm-profiler/pom.xml	Tue Nov 18 14:02:06 2014 -0500
+++ b/vm-profiler/pom.xml	Tue Nov 18 14:04:56 2014 -0500
@@ -50,6 +50,11 @@
 
   <name>Thermostat VM CPU plugin</name>
 
+  <properties>
+    <thermostat.vm.profiler.dir>${thermostat.home}plugins/vm-profiler/</thermostat.vm.profiler.dir>
+    <thermostat.vm.profiler.agent.asm.path>${thermostat.home}plugins/vm-profiler/asm-all-${asm.version}.jar</thermostat.vm.profiler.agent.asm.path>
+  </properties>
+
   <modules>
     <module>agent</module>
     <module>jvm-agent</module>