Mercurial > hg > release > thermostat-1.4
changeset 1555:7b8199115995
Support starting/stopping profiling on demand
Reviewed-by: neugens
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011474.html
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"))); + } +}