Mercurial > hg > release > thermostat-1.2
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
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>