changeset 1555:7b8199115995

Support starting/stopping profiling on demand Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011474.html
author Omair Majid <omajid@redhat.com>
date Tue, 18 Nov 2014 14:06:45 -0500
parents 700efd315f0e
children 247db2fa05c5
files 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/VmProfiler.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControlMXBean.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/Main.java vm-profiler/jvm-agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/jvm/MainTest.java
diffstat 8 files changed, 390 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java	Tue Nov 18 14:05:59 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java	Tue Nov 18 14:06:45 2014 -0500
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.vm.profiler.agent.internal;
 
+import java.util.Map;
 import java.util.Properties;
 
 import org.osgi.framework.BundleActivator;
@@ -43,33 +44,54 @@
 
 import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.agent.command.ReceiverRegistry;
+import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
+import com.redhat.thermostat.common.MultipleServiceTracker;
 
 public class Activator implements BundleActivator {
 
-    private ReceiverRegistry requestHandlerRegisteration;
-    private VmStatusListenerRegistrar vmStatusRegistrar;
-    private ProfileVmRequestReceiver profileRequestHandler;
+    private MultipleServiceTracker tracker;
 
     @Override
     public void start(final BundleContext context) throws Exception {
-        Properties configuration = new Properties();
+        final Properties configuration = new Properties();
         configuration.load(this.getClass().getResourceAsStream("settings.properties"));
-        VmProfiler profiler = new VmProfiler(configuration);
-        profileRequestHandler = new ProfileVmRequestReceiver(profiler);
+
+        Class<?>[] deps = new Class<?>[] { MXBeanConnectionPool.class };
+        tracker = new MultipleServiceTracker(context, deps, new MultipleServiceTracker.Action() {
+            private ReceiverRegistry requestHandlerRegisteration;
+            private VmStatusListenerRegistrar vmStatusRegistrar;
+            private ProfileVmRequestReceiver profileRequestHandler;
+
+            @Override
+            public void dependenciesUnavailable() {
+                requestHandlerRegisteration.unregisterReceivers();
+                requestHandlerRegisteration = null;
+
+                vmStatusRegistrar.unregister(profileRequestHandler);
 
-        requestHandlerRegisteration = new ReceiverRegistry(context);
-        requestHandlerRegisteration.registerReceiver(profileRequestHandler);
+                profileRequestHandler = null;
+            }
+
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                MXBeanConnectionPool pool = (MXBeanConnectionPool) services.get(MXBeanConnectionPool.class.getName());
+                VmProfiler profiler = new VmProfiler(configuration, pool);
+                profileRequestHandler = new ProfileVmRequestReceiver(profiler);
 
-        vmStatusRegistrar = new VmStatusListenerRegistrar(context);
-        vmStatusRegistrar.register(profileRequestHandler);
+                requestHandlerRegisteration = new ReceiverRegistry(context);
+                requestHandlerRegisteration.registerReceiver(profileRequestHandler);
+
+                vmStatusRegistrar = new VmStatusListenerRegistrar(context);
+                vmStatusRegistrar.register(profileRequestHandler);
+            }
+        });
+
+        tracker.open();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        requestHandlerRegisteration.unregisterReceivers();
-        requestHandlerRegisteration = null;
-
-        vmStatusRegistrar.unregister(profileRequestHandler);
+        tracker.close();
     }
 }
 
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java	Tue Nov 18 14:05:59 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java	Tue Nov 18 14:06:45 2014 -0500
@@ -41,6 +41,11 @@
 import java.util.Properties;
 import java.util.logging.Logger;
 
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+
+import com.redhat.thermostat.agent.utils.management.MXBeanConnection;
+import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.sun.tools.attach.AgentInitializationException;
 import com.sun.tools.attach.AgentLoadException;
@@ -51,16 +56,18 @@
 
     private static final Logger logger = LoggingUtils.getLogger(VmProfiler.class);
 
+    private final MXBeanConnectionPool connectionPool;
     private final Attacher attacher;
 
     private String agentJarPath;
     private String asmJarPath;
 
-    public VmProfiler(Properties configuration) {
-        this(configuration, new Attacher());
+    public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool) {
+        this(configuration, connectionPool, new Attacher());
     }
 
-    public VmProfiler(Properties configuration, Attacher attacher) {
+    public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool, Attacher attacher) {
+        this.connectionPool = connectionPool;
         this.attacher = attacher;
 
         // requireNonNull protects against bad config with missing values
@@ -69,6 +76,12 @@
     }
 
     public void startProfiling(int pid) throws ProfilerException {
+        loadProfilerAgentIntoPid(pid);
+
+        invokeMethodOnInstrumentation(pid, "startProfiling");
+    }
+
+    private void loadProfilerAgentIntoPid(int pid) throws ProfilerException {
         try {
             VirtualMachine vm = attacher.attach(String.valueOf(pid));
             try {
@@ -86,7 +99,22 @@
     }
 
     public void stopProfiling(int pid) throws ProfilerException {
-        // TODO Auto-generated method stub
+        invokeMethodOnInstrumentation(pid, "stopProfiling");
+    }
+
+    private void invokeMethodOnInstrumentation(int pid, String name) throws ProfilerException {
+        try {
+            MXBeanConnection connection = connectionPool.acquire(pid);
+            try {
+                ObjectName instrumentation = new ObjectName("com.redhat.thermostat:type=InstrumentationControl");
+                MBeanServerConnection server = connection.get();
+                server.invoke(instrumentation, name, new Object[0], new String[0]);
+            } finally {
+                connectionPool.release(pid, connection);
+            }
+        } catch (Exception e) {
+            throw new ProfilerException("Unable to start remote profiling", e);
+        }
     }
 
     static class Attacher {
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java	Tue Nov 18 14:05:59 2014 -0500
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java	Tue Nov 18 14:06:45 2014 -0500
@@ -38,17 +38,22 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 
 import org.junit.Test;
 
 import com.redhat.thermostat.agent.command.RequestReceiver;
+import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
 import com.redhat.thermostat.testutils.StubBundleContext;
 
 public class ActivatorTest {
 
     @Test
     public void requestHandlerIsRegistered() throws Exception {
+        MXBeanConnectionPool pool = mock(MXBeanConnectionPool.class);
         StubBundleContext context = new StubBundleContext();
+        context.registerService(MXBeanConnectionPool.class, pool, null);
+
         Activator activator = new Activator();
 
         activator.start(context);
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java	Tue Nov 18 14:05:59 2014 -0500
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java	Tue Nov 18 14:06:45 2014 -0500
@@ -39,31 +39,43 @@
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import java.io.IOException;
 import java.util.Properties;
 
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.agent.utils.management.MXBeanConnection;
+import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
 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 MBeanServerConnection server;
+    private MXBeanConnection connection;
+    private MXBeanConnectionPool connectionPool;
     private Attacher attacher;
 
+    private final int PID = 0;
+
     private final String AGENT_JAR = "foo";
     private final String ASM_JAR = "bar";
 
+    private ObjectName instrumentationName;
+
     @Before
     public void setUp() throws Exception {
+        instrumentationName = new ObjectName("com.redhat.thermostat:type=InstrumentationControl");
+
         Properties props = new Properties();
         props.setProperty("AGENT_JAR", AGENT_JAR);
         props.setProperty("ASM_JAR", ASM_JAR);
@@ -72,17 +84,37 @@
         vm = mock(VirtualMachine.class);
         when(attacher.attach(isA(String.class))).thenReturn(vm);
 
-        profiler = new VmProfiler(props, attacher);
+        server = mock(MBeanServerConnection.class);
+
+        connection = mock(MXBeanConnection.class);
+        when(connection.get()).thenReturn(server);
+
+        connectionPool = mock(MXBeanConnectionPool.class);
+        when(connectionPool.acquire(PID)).thenReturn(connection);
+
+        profiler = new VmProfiler(props, connectionPool, attacher);
     }
 
     @Test
-    public void loadsJvmAgent() throws ProfilerException, AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException {
-        final int PID = 0;
-
+    public void startingProfilingLoadsJvmAgentAndMakesAnRmiCall() throws Exception {
         profiler.startProfiling(PID);
 
         verify(attacher).attach(String.valueOf(PID));
         verify(vm).loadAgent(AGENT_JAR, "");
         verify(vm).detach();
+        verifyNoMoreInteractions(vm);
+
+        verify(server).invoke(instrumentationName, "startProfiling", new Object[0], new String[0]);
+        verify(connectionPool).release(PID, connection);
+    }
+
+    @Test
+    public void stoppingProfilingLoadsJvmAgentAndMakesAnRmiCall() throws Exception {
+        profiler.stopProfiling(PID);
+
+        verifyNoMoreInteractions(vm);
+
+        verify(server).invoke(instrumentationName, "stopProfiling", new Object[0], new String[0]);
+        verify(connectionPool).release(PID, connection);
     }
 }
--- /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/InstrumentationControl.java	Tue Nov 18 14:06:45 2014 -0500
@@ -0,0 +1,143 @@
+/*
+ * 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 InstrumentationControl implements InstrumentationControlMXBean {
+
+    private final Instrumentation instrumentation;
+    private final ProfilerInstrumentor classInstrumentor;
+
+    private boolean profiling = false;
+
+    public InstrumentationControl(Instrumentation instrumentation) {
+        this.instrumentation = instrumentation;
+        this.classInstrumentor = new AsmBasedInstrumentor();
+    }
+
+    void initialize() {
+        addShutdownHookToPrintStatsOnEnd();
+    }
+
+    private 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("=====");
+            }
+        });
+    }
+
+    @Override
+    public void startProfiling() {
+        System.out.println("AGENT: startProfiling()");
+        if (profiling) {
+            throw new IllegalStateException("Already started");
+        }
+        profiling = true;
+
+        instrumentation.addTransformer(classInstrumentor, true);
+        retransformAlreadyLoadedClasses(instrumentation, classInstrumentor);
+    }
+
+    @Override
+    public void stopProfiling() {
+        System.out.println("AGENT: stopProfiling()");
+        if (!profiling) {
+            throw new IllegalStateException("Not profiling");
+        }
+        profiling = false;
+
+        instrumentation.removeTransformer(classInstrumentor);
+        retransformAlreadyLoadedClasses(instrumentation, classInstrumentor);
+    }
+
+    @Override
+    public boolean isProfiling() {
+        return profiling;
+    }
+
+    private void retransformAlreadyLoadedClasses(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");
+    }
+
+}
--- /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/InstrumentationControlMXBean.java	Tue Nov 18 14:06:45 2014 -0500
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/** Adds and removes instrumentation to generate profiling data */
+public interface InstrumentationControlMXBean {
+
+    /** Enable profiling. */
+    void startProfiling();
+
+    /** Disable profiling */
+    void stopProfiling();
+
+    /** @returns whether profiling is currently active or not */
+    boolean isProfiling();
+
+}
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/Main.java	Tue Nov 18 14:05:59 2014 -0500
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/Main.java	Tue Nov 18 14:06:45 2014 -0500
@@ -37,95 +37,40 @@
 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;
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
 
 public class Main {
 
     private Instrumentation instrumentation;
+    private MBeanServer server;
 
     public Main(Instrumentation instrumentation) {
+        this(instrumentation, ManagementFactory.getPlatformMBeanServer());
+    }
+
+    public Main(Instrumentation instrumentation, MBeanServer server) {
         this.instrumentation = instrumentation;
+        this.server = server;
     }
 
     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;
+        InstrumentationControl control = new InstrumentationControl(instrumentation);
+        control.initialize();
+        try {
+            ObjectName name = new ObjectName("com.redhat.thermostat:type=InstrumentationControl");
+            server.registerMBean(control, name);
+        } catch (Exception e) {
+            System.err.println("Unable to attach agent");
+            e.printStackTrace();
+        }
     }
 
-    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");
+    public static void main(String[] args) {
+        new Main(null).run();
     }
-
-    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/test/java/com/redhat/thermostat/vm/profiler/agent/jvm/MainTest.java	Tue Nov 18 14:06:45 2014 -0500
@@ -0,0 +1,62 @@
+/*
+ * 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 static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.lang.instrument.Instrumentation;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+
+public class MainTest {
+
+    @Test
+    public void registersAnMXBean() throws Exception {
+        Instrumentation instrumentation = mock(Instrumentation.class);
+        MBeanServer mbeanServer = mock(MBeanServer.class);
+        Main main = new Main(instrumentation, mbeanServer);
+        main.run();
+
+        verify(mbeanServer).registerMBean(isA(InstrumentationControl.class), eq(new ObjectName("com.redhat.thermostat:type=InstrumentationControl")));
+    }
+}