Mercurial > hg > release > thermostat-1.2
changeset 1585:574c6023b53f
Clean up classes in vm-profiler agent
Reviewed-by: vanaltj
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-December/011891.html
line wrap: on
line diff
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java Wed Dec 03 16:01:36 2014 +0100 +++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java Wed Dec 03 11:53:17 2014 -0500 @@ -66,16 +66,18 @@ tracker = new MultipleServiceTracker(context, deps, new MultipleServiceTracker.Action() { private ReceiverRegistry requestHandlerRegisteration; private VmStatusListenerRegistrar vmStatusRegistrar; - private ProfileVmRequestReceiver profileRequestHandler; + private ProfilerRequestReceiver profileRequestHandler; + private ProfilerVmStatusListener livenessListener; @Override public void dependenciesUnavailable() { requestHandlerRegisteration.unregisterReceivers(); requestHandlerRegisteration = null; + profileRequestHandler = null; - vmStatusRegistrar.unregister(profileRequestHandler); + vmStatusRegistrar.unregister(livenessListener); + livenessListener = null; - profileRequestHandler = null; } @Override @@ -85,14 +87,15 @@ ProfileDAO dao = get(ProfileDAO.class, services); String writerId = writerIdProvider.getWriterID(); - VmProfiler profiler = new VmProfiler(configuration, pool); - profileRequestHandler = new ProfileVmRequestReceiver(writerId, profiler, dao); + VmProfiler profiler = new VmProfiler(writerId, configuration, dao, pool); + profileRequestHandler = new ProfilerRequestReceiver(profiler); requestHandlerRegisteration = new ReceiverRegistry(context); requestHandlerRegisteration.registerReceiver(profileRequestHandler); + livenessListener = new ProfilerVmStatusListener(profiler); vmStatusRegistrar = new VmStatusListenerRegistrar(context); - vmStatusRegistrar.register(profileRequestHandler); + vmStatusRegistrar.register(livenessListener); } private <T> T get(Class<T> klass, Map<String, Object> services) { return (T) services.get(klass.getName());
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java Wed Dec 03 16:01:36 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ -/* - * 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.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -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.Clock; -import com.redhat.thermostat.common.SystemClock; -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.ProfileDAO; -import com.redhat.thermostat.vm.profiler.common.ProfileRequest; -import com.redhat.thermostat.vm.profiler.common.ProfileStatusChange; - -public class ProfileVmRequestReceiver implements RequestReceiver, VmStatusListener { - - private static final Logger logger = LoggingUtils.getLogger(ProfileVmRequestReceiver.class); - - /** A pid that corresponds to an unknown */ - private static final int UNKNOWN_VMID = -1; - - private static final Response OK = new Response(ResponseType.OK); - private static final Response ERROR = new Response(ResponseType.NOK); - - static class FileTimeStampLatestFirst implements Comparator<File> { - @Override - public int compare(File o1, File o2) { - return Long.compare(o2.lastModified(), o1.lastModified()); - } - } - - static class ProfileUploaderCreator { - ProfileUploader create(ProfileDAO dao, String agentId, String vmId, int pid) { - return new ProfileUploader(dao, agentId, vmId, pid); - } - } - - private final ConcurrentHashMap<String, Integer> vmIdToPid = new ConcurrentHashMap<>(); - - private final String agentId; - - private final Clock clock; - private final VmProfiler profiler; - private final ProfileDAO dao; - private ProfileUploaderCreator uploaderCreator; - - private List<Integer> currentlyProfiledVms = new ArrayList<>(); - - public ProfileVmRequestReceiver(String agentId, VmProfiler profiler, ProfileDAO dao) { - this(agentId, new SystemClock(), profiler, dao, new ProfileUploaderCreator()); - } - - public ProfileVmRequestReceiver(String agentId, Clock clock, VmProfiler profiler, ProfileDAO dao, ProfileUploaderCreator uploaderCreator) { - this.clock = clock; - this.profiler = profiler; - this.dao = dao; - this.uploaderCreator = uploaderCreator; - - this.agentId = agentId; - } - - @Override - public synchronized void vmStatusChanged(Status newStatus, String vmId, int pid) { - if (newStatus == Status.VM_ACTIVE || newStatus == Status.VM_STARTED) { - // TODO assert not already being profiled - vmIdToPid.putIfAbsent(vmId, pid); - } else { - disableProfilerIfActive(vmId, pid); - vmIdToPid.remove(vmId, pid); - } - } - - private void disableProfilerIfActive(String vmId, int pid) { - if (currentlyProfiledVms.contains(pid)) { - stopProfiling(vmId, pid, false); - } - } - - @Override - public synchronized Response receive(Request request) { - 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: - return startProfiling(vmId, pid); - case ProfileRequest.STOP_PROFILING: - return stopProfiling(vmId, pid, true); - default: - logger.warning("Unknown command: '" + value + "'"); - return ERROR; - } - } - - /** 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; - } - } - - private Response startProfiling(String vmId, int pid) { - logger.info("Starting profiling " + pid); - try { - profiler.startProfiling(pid); - currentlyProfiledVms.add(pid); - dao.addStatus(new ProfileStatusChange(agentId, vmId, clock.getRealTimeMillis(), true)); - return OK; - } catch (Exception e) { - logger.log(Level.INFO, "start profiling failed", e); - return ERROR; - } - } - - private Response stopProfiling(String vmId, int pid, boolean alive) { - logger.info("Stopping profiling " + pid); - try { - ProfileUploader uploader = uploaderCreator.create(dao, agentId, vmId, pid); - if (alive) { - // if the VM is alive, communicate with it directly - profiler.stopProfiling(pid, uploader); - } else { - findAndUploadProfilingResultsStoredOnDisk(pid, uploader); - } - dao.addStatus(new ProfileStatusChange(agentId, vmId, clock.getRealTimeMillis(), false)); - currentlyProfiledVms.remove((Integer) pid); - return OK; - } catch (Exception e) { - logger.log(Level.INFO, "stop profiling failed", e); - return ERROR; - } - } - - private void findAndUploadProfilingResultsStoredOnDisk(final int pid, ProfileUploader uploader) throws IOException { - long timeStamp = clock.getRealTimeMillis(); - // look for latest profiling data that it might have emitted on shutdown - File file = findProfilingResultFile(pid); - uploader.upload(timeStamp, file); - } - - private File findProfilingResultFile(final int pid) { - // from InstrumentationControl: - // return Files.createTempFile("thermostat-" + getProcessId(), ".perfdata", attributes); - String tmpDir = System.getProperty("java.io.tmpdir"); - File[] files = new File(tmpDir).listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.startsWith("thermostat-" + pid + "-") && name.endsWith(".perfdata"); - } - }); - - List<File> filesSortedByTimeStamp = Arrays.asList(files); - Collections.sort(filesSortedByTimeStamp, new FileTimeStampLatestFirst()); - return filesSortedByTimeStamp.get(0); - } - -}
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerException.java Wed Dec 03 16:01:36 2014 +0100 +++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerException.java Wed Dec 03 11:53:17 2014 -0500 @@ -38,7 +38,12 @@ public class ProfilerException extends Exception { + public ProfilerException(String message) { + super(message); + } + 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/ProfilerRequestReceiver.java Wed Dec 03 11:53:17 2014 -0500 @@ -0,0 +1,105 @@ +/* + * 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.util.logging.Level; +import java.util.logging.Logger; + +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 ProfilerRequestReceiver implements RequestReceiver { + + private static final Logger logger = LoggingUtils.getLogger(ProfilerRequestReceiver.class); + + private static final Response OK = new Response(ResponseType.OK); + private static final Response ERROR = new Response(ResponseType.NOK); + + private final VmProfiler profiler; + + public ProfilerRequestReceiver(VmProfiler profiler) { + this.profiler = profiler; + } + + @Override + public Response receive(Request request) { + String value = request.getParameter(ProfileRequest.PROFILE_ACTION); + String vmId = request.getParameter(ProfileRequest.VM_ID); + + try { + switch (value) { + case ProfileRequest.START_PROFILING: + return startProfiling(vmId); + case ProfileRequest.STOP_PROFILING: + return stopProfiling(vmId); + default: + logger.warning("Unknown command: '" + value + "'"); + return ERROR; + } + } catch (Exception e) { + logger.log(Level.FINE, "Exception handling command", e); + return ERROR; + } + } + + private Response startProfiling(String vmId) { + logger.info("Starting profiling " + vmId); + try { + profiler.startProfiling(vmId); + return OK; + } catch (Exception e) { + logger.log(Level.INFO, "start profiling failed", e); + return ERROR; + } + } + + private Response stopProfiling(String vmId) { + logger.info("Stopping profiling " + vmId); + try { + profiler.stopProfiling(vmId); + return OK; + } catch (Exception e) { + logger.log(Level.INFO, "stop profiling failed", e); + return ERROR; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerVmStatusListener.java Wed Dec 03 11:53:17 2014 -0500 @@ -0,0 +1,58 @@ +/* + * 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 com.redhat.thermostat.agent.VmStatusListener; + +public class ProfilerVmStatusListener implements VmStatusListener { + + private final VmProfiler profiler; + + public ProfilerVmStatusListener(VmProfiler profiler) { + this.profiler = profiler; + } + + @Override + public void vmStatusChanged(Status newStatus, String vmId, int pid) { + if (newStatus == Status.VM_ACTIVE || newStatus == Status.VM_STARTED) { + profiler.vmStarted(vmId, pid); + } else { + profiler.vmStopped(vmId, 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/RemoteProfilerCommunicator.java Wed Dec 03 11:53:17 2014 -0500 @@ -0,0 +1,133 @@ +/* + * 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 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.sun.tools.attach.AgentInitializationException; +import com.sun.tools.attach.AgentLoadException; +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; + +class RemoteProfilerCommunicator { + + // TODO should this maintain enough state to know when to attach and load agent? + + // published by the jvm-agent + private static final String INSTRUMENTATION_OBJECT = "com.redhat.thermostat:type=InstrumentationControl"; + + static class Attacher { + VirtualMachine attach(String pid) throws AttachNotSupportedException, IOException { + return VirtualMachine.attach(pid); + } + } + + private MXBeanConnectionPool connectionPool; + private final Attacher attacher; + + public RemoteProfilerCommunicator(MXBeanConnectionPool connectionPool) { + this(connectionPool, new Attacher()); + } + + public RemoteProfilerCommunicator(MXBeanConnectionPool connectionPool, Attacher attacher) { + this.connectionPool = connectionPool; + this.attacher = attacher; + } + + public void loadAgentIntoPid(int pid, String jar, String options) throws ProfilerException { + try { + VirtualMachine vm = attacher.attach(String.valueOf(pid)); + try { + vm.loadAgent(jar, options); + } 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 startProfiling(int pid) throws ProfilerException { + invokeMethodOnInstrumentation(pid, "startProfiling"); + } + + public void stopProfiling(int pid) throws ProfilerException { + invokeMethodOnInstrumentation(pid, "stopProfiling"); + } + + public String getProfilingDataFile(int pid) throws ProfilerException { + return (String) getInstrumentationAttribute(pid, "ProfilingDataFile"); + } + + private Object invokeMethodOnInstrumentation(int pid, String name) throws ProfilerException { + try { + MXBeanConnection connection = connectionPool.acquire(pid); + try { + ObjectName instrumentation = new ObjectName(INSTRUMENTATION_OBJECT); + MBeanServerConnection server = connection.get(); + return server.invoke(instrumentation, name, new Object[0], new String[0]); + } finally { + connectionPool.release(pid, connection); + } + } catch (Exception e) { + throw new ProfilerException("Unable to communicate with remote profiler", e); + } + } + + private Object getInstrumentationAttribute(int pid, String name) throws ProfilerException { + try { + MXBeanConnection connection = connectionPool.acquire(pid); + try { + ObjectName instrumentation = new ObjectName(INSTRUMENTATION_OBJECT); + MBeanServerConnection server = connection.get(); + return server.getAttribute(instrumentation, name); + } finally { + connectionPool.release(pid, connection); + } + } catch (Exception e) { + throw new ProfilerException("Unable to communicate with remote profiler", e); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmIdToPidMapper.java Wed Dec 03 11:53:17 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.internal; + +import java.util.concurrent.ConcurrentHashMap; + +class VmIdToPidMapper { + + /** A pid that corresponds to an unknown */ + static final int UNKNOWN_VMID = -1; + + private final ConcurrentHashMap<String, Integer> vmIdToPid = new ConcurrentHashMap<>(); + + void add(String vmId, int pid) { + vmIdToPid.putIfAbsent(vmId, pid); + } + + /** Return the pid or {@link #UNKNOWN_VMID} */ + int getPid(String vmId) { + Integer pid = vmIdToPid.get(vmId); + if (pid == null) { + return UNKNOWN_VMID; + } else { + return pid; + } + } + + void remove(String vmId, int pid) { + vmIdToPid.remove(vmId, pid); + } + +}
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java Wed Dec 03 16:01:36 2014 +0100 +++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java Wed Dec 03 11:53:17 2014 -0500 @@ -37,116 +37,178 @@ package com.redhat.thermostat.vm.profiler.agent.internal; import java.io.File; +import java.io.FilenameFilter; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Objects; 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.Clock; import com.redhat.thermostat.common.SystemClock; 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; +import com.redhat.thermostat.vm.profiler.common.ProfileDAO; +import com.redhat.thermostat.vm.profiler.common.ProfileStatusChange; public class VmProfiler { + static class MostRecentFileFirst implements Comparator<File> { + @Override + public int compare(File o1, File o2) { + return Long.compare(o2.lastModified(), o1.lastModified()); + } + } + + static class ProfileUploaderCreator { + ProfileUploader create(ProfileDAO dao, String agentId, String vmId, int pid) { + return new ProfileUploader(dao, agentId, vmId, pid); + } + } + private static final Logger logger = LoggingUtils.getLogger(VmProfiler.class); - private final MXBeanConnectionPool connectionPool; - private final Attacher attacher; - private final Clock clock; + private final List<Integer> currentlyProfiledVmPids = new ArrayList<>(); + + private final VmIdToPidMapper vmIdToPid = new VmIdToPidMapper(); - private String agentJarPath; - private String asmJarPath; + private final String agentId; + private final Clock clock; + private final ProfileDAO dao; + private final ProfileUploaderCreator uploaderCreator; + private final RemoteProfilerCommunicator remote; - public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool) { - this(configuration, connectionPool, new Attacher(), new SystemClock()); + private final String agentJarPath; + private final String asmJarPath; + + public VmProfiler(String agentId, Properties configuration, ProfileDAO dao, MXBeanConnectionPool pool) { + this(agentId, configuration, dao, new SystemClock(), new ProfileUploaderCreator(), new RemoteProfilerCommunicator(pool)); } - public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool, Attacher attacher, Clock clock) { - this.connectionPool = connectionPool; - this.attacher = attacher; + VmProfiler(String agentId, Properties configuration, + ProfileDAO dao, + Clock clock, ProfileUploaderCreator creator, RemoteProfilerCommunicator remote) { + this.agentId = agentId; this.clock = clock; + this.dao = dao; + this.uploaderCreator = creator; + this.remote = remote; // 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 { - loadProfilerAgentIntoPid(pid); - - invokeMethodOnInstrumentation(pid, "startProfiling"); + public synchronized void vmStarted(String vmId, int pid) { + // assert not already being profiled + if (currentlyProfiledVmPids.contains((Integer) pid)) { + throw new IllegalStateException("VM " + pid + " is already being profiled"); + } + vmIdToPid.add(vmId, pid); } - private void loadProfilerAgentIntoPid(int pid) throws ProfilerException { + public synchronized void vmStopped(String vmId, int pid) { 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); + disableProfilerIfActive(vmId, pid); + } catch (ProfilerException e) { + logger.warning(e.getMessage()); + } + vmIdToPid.remove(vmId, pid); + } + + private void disableProfilerIfActive(String vmId, int pid) throws ProfilerException { + if (currentlyProfiledVmPids.contains(pid)) { + stopProfiling(vmId, false); } } - public void stopProfiling(int pid, ProfileUploader uploader) throws ProfilerException { - invokeMethodOnInstrumentation(pid, "stopProfiling"); + public synchronized void startProfiling(String vmId) throws ProfilerException { + int pid = vmIdToPid.getPid(vmId); + if (pid == VmIdToPidMapper.UNKNOWN_VMID) { + throw new ProfilerException("Unknown VmId " + vmId); + } + + if (currentlyProfiledVmPids.contains((Integer) pid)) { + throw new ProfilerException("Already profiling the VM"); + } + + // TODO make this adjustable at run-time + // eg: asmJarPath + ":" + agentJarPath; + String jarsToLoad = ""; + logger.info("Asking " + pid + " to load agent '" + agentJarPath + "' with arguments '" + jarsToLoad + "'"); + // FIXME Only load the first time + remote.loadAgentIntoPid(pid, agentJarPath, jarsToLoad); + + remote.startProfiling(pid); + + currentlyProfiledVmPids.add(pid); + dao.addStatus(new ProfileStatusChange(agentId, vmId, clock.getRealTimeMillis(), true)); + } + + public synchronized void stopProfiling(String vmId) throws ProfilerException { + stopProfiling(vmId, true); + } + + private void stopProfiling(String vmId, boolean alive) throws ProfilerException { + int pid = vmIdToPid.getPid(vmId); + if (pid == VmIdToPidMapper.UNKNOWN_VMID) { + throw new ProfilerException("VmId not found: " + vmId); + } + + if (!currentlyProfiledVmPids.contains(pid)) { + throw new ProfilerException("Vm is not being profiled: " + vmId); + } - String profilingDataFile = (String) getInstrumentationAttribute(pid, "ProfilingDataFile"); + ProfileUploader uploader = uploaderCreator.create(dao, agentId, vmId, pid); + if (alive) { + stopRemoteProfilerAndUploadResults(pid, uploader); + } else { + findAndUploadProfilingResultsStoredOnDisk(pid, uploader); + } + dao.addStatus(new ProfileStatusChange(agentId, vmId, clock.getRealTimeMillis(), false)); + currentlyProfiledVmPids.remove((Integer) pid); + } + + private void stopRemoteProfilerAndUploadResults(int pid, ProfileUploader uploader) throws ProfilerException { + remote.stopProfiling(pid); + + String profilingDataFile = remote.getProfilingDataFile(pid); + upload(uploader, clock.getRealTimeMillis(), new File(profilingDataFile)); + } + + private void findAndUploadProfilingResultsStoredOnDisk(final int pid, ProfileUploader uploader) throws ProfilerException { + long timeStamp = clock.getRealTimeMillis(); + // look for latest profiling data that it might have emitted on shutdown + File file = findProfilingResultFile(pid); + upload(uploader, timeStamp, file); + } + + private File findProfilingResultFile(final int pid) { + // from InstrumentationControl: + // return Files.createTempFile("thermostat-" + getProcessId() + "-", ".perfdata", attributes); + String tmpDir = System.getProperty("java.io.tmpdir"); + File[] files = new File(tmpDir).listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith("thermostat-" + pid + "-") && name.endsWith(".perfdata"); + } + }); + + List<File> filesSortedByTimeStamp = Arrays.asList(files); + Collections.sort(filesSortedByTimeStamp, new MostRecentFileFirst()); + return filesSortedByTimeStamp.get(0); + } + + private void upload(ProfileUploader uploader, long timeStamp, File file) throws ProfilerException { try { - uploader.upload(clock.getRealTimeMillis(), new File(profilingDataFile)); + uploader.upload(clock.getRealTimeMillis(), file); } catch (IOException e) { throw new ProfilerException("Unable to save profiling data into storage", e); } } - private Object 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(); - return server.invoke(instrumentation, name, new Object[0], new String[0]); - } finally { - connectionPool.release(pid, connection); - } - } catch (Exception e) { - throw new ProfilerException("Unable to communicate with remote profiler", e); - } - } - - private Object getInstrumentationAttribute(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(); - return server.getAttribute(instrumentation, name); - } finally { - connectionPool.release(pid, connection); - } - } catch (Exception e) { - throw new ProfilerException("Unable to communicate with remote profiler", e); - } - } - - static class Attacher { - VirtualMachine attach(String pid) throws AttachNotSupportedException, IOException { - return VirtualMachine.attach(pid); - } - } }
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java Wed Dec 03 16:01:36 2014 +0100 +++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java Wed Dec 03 11:53:17 2014 -0500 @@ -42,6 +42,7 @@ import org.junit.Test; +import com.redhat.thermostat.agent.VmStatusListener; import com.redhat.thermostat.agent.command.RequestReceiver; import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool; import com.redhat.thermostat.storage.core.WriterID; @@ -65,10 +66,12 @@ activator.start(context); - assertTrue(context.isServiceRegistered(RequestReceiver.class.getName(), ProfileVmRequestReceiver.class)); + assertTrue(context.isServiceRegistered(RequestReceiver.class.getName(), ProfilerRequestReceiver.class)); + assertTrue(context.isServiceRegistered(VmStatusListener.class.getName(), ProfilerVmStatusListener.class)); activator.stop(context); - assertFalse(context.isServiceRegistered(RequestReceiver.class.getName(), ProfileVmRequestReceiver.class)); + assertFalse(context.isServiceRegistered(RequestReceiver.class.getName(), ProfilerRequestReceiver.class)); + assertFalse(context.isServiceRegistered(VmStatusListener.class.getName(), ProfilerVmStatusListener.class)); } }
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiverTest.java Wed Dec 03 16:01:36 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -/* - * 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.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.BufferedWriter; -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; - -import org.junit.Before; -import org.junit.Test; - -import com.redhat.thermostat.agent.VmStatusListener.Status; -import com.redhat.thermostat.common.Clock; -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.agent.internal.ProfileVmRequestReceiver.ProfileUploaderCreator; -import com.redhat.thermostat.vm.profiler.common.ProfileDAO; -import com.redhat.thermostat.vm.profiler.common.ProfileRequest; -import com.redhat.thermostat.vm.profiler.common.ProfileStatusChange; - -public class ProfileVmRequestReceiverTest { - - private static final String AGENT_ID = "agent-id"; - - private static final String VM_ID = "foo"; - private static final int VM_PID = 1; - private static final long TIMESTAMP = 99; - - private ProfileVmRequestReceiver requestReceiver; - - private Clock clock; - private VmProfiler profiler; - private ProfileDAO dao; - private ProfileUploader uploader; - - @Before - public void setUp() { - clock = mock(Clock.class); - when(clock.getRealTimeMillis()).thenReturn(TIMESTAMP); - - profiler = mock(VmProfiler.class); - dao = mock(ProfileDAO.class); - uploader = mock(ProfileUploader.class); - ProfileUploaderCreator uploaderCreator = mock(ProfileUploaderCreator.class); - when(uploaderCreator.create(dao, AGENT_ID, VM_ID, VM_PID)).thenReturn(uploader); - - requestReceiver = new ProfileVmRequestReceiver(AGENT_ID, clock, profiler, dao, uploaderCreator); - } - - @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); - - verify(dao).addStatus(new ProfileStatusChange(AGENT_ID, VM_ID, TIMESTAMP, true)); - } - - @Test - public void startAndStopProfiling() throws ProfilerException { - Request request; - - requestReceiver.vmStatusChanged(Status.VM_STARTED, VM_ID, VM_PID); - request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING); - 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(eq(VM_PID), isA(ProfileUploader.class)); - verify(dao, times(2)).addStatus(isA(ProfileStatusChange.class)); - } - - @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()); - verify(dao, never()).addStatus(isA(ProfileStatusChange.class)); - } - - @Test - public void readsProfilingResultsOnVmExit() throws Exception { - Request request; - - requestReceiver.vmStatusChanged(Status.VM_STARTED, VM_ID, VM_PID); - request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING); - requestReceiver.receive(request); - - // simulate target vm exiting - File profilingResults = new File(System.getProperty("java.io.tmpdir"), "thermostat-" + VM_PID + "-foobar.perfdata"); - try (BufferedWriter writer = Files.newBufferedWriter(profilingResults.toPath(), StandardCharsets.UTF_8)) { - writer.append("test file, please ignore"); - } - - requestReceiver.vmStatusChanged(Status.VM_STOPPED, VM_ID, VM_PID); - - verify(profiler, never()).stopProfiling(anyInt(), isA(ProfileUploader.class)); - verify(uploader).upload(TIMESTAMP, profilingResults); - verify(dao, times(2)).addStatus(isA(ProfileStatusChange.class)); - - profilingResults.delete(); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerRequestReceiverTest.java Wed Dec 03 11:53:17 2014 -0500 @@ -0,0 +1,121 @@ +/* + * 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.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.Before; +import org.junit.Test; + +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 ProfilerRequestReceiverTest { + + private static final String VM_ID = "foo"; + + private ProfilerRequestReceiver requestReceiver; + + private VmProfiler profiler; + + @Before + public void setUp() { + profiler = mock(VmProfiler.class); + + requestReceiver = new ProfilerRequestReceiver(profiler); + } + + @Test + public void forwardsStartRequestToProfiler() throws Exception { + Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING); + Response result = requestReceiver.receive(request); + + verify(profiler).startProfiling(VM_ID); + + assertEquals(ResponseType.OK, result.getType()); + } + + @Test + public void exceptionThrownFromProfilerResultsInStarError() throws Exception { + doThrow(ProfilerException.class).when(profiler).startProfiling(VM_ID); + Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING); + Response result = requestReceiver.receive(request); + + assertEquals(ResponseType.NOK, result.getType()); + } + + @Test + public void forwardsStopRequestToProfiler() throws Exception { + Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.STOP_PROFILING); + Response result = requestReceiver.receive(request); + + verify(profiler).stopProfiling(VM_ID); + + assertEquals(ResponseType.OK, result.getType()); + } + + @Test + public void exceptionThrownFromProfilerResultsInStopError() throws Exception { + doThrow(ProfilerException.class).when(profiler).stopProfiling(VM_ID); + Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.STOP_PROFILING); + Response result = requestReceiver.receive(request); + + assertEquals(ResponseType.NOK, result.getType()); + } + + @Test + public void unknownRequestCausesError() { + Request request = ProfileRequest.create(null, VM_ID, "bad-request"); + Response result = requestReceiver.receive(request); + + verifyNoMoreInteractions(profiler); + + assertEquals(ResponseType.NOK, result.getType()); + } + + @Test + public void receiverIsValidTargetForRequest() throws Exception { + Request request = ProfileRequest.create(null, VM_ID, ProfileRequest.START_PROFILING); + assertEquals(ProfilerRequestReceiver.class.getName(), request.getReceiver()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfilerVmStatusListenerTest.java Wed Dec 03 11:53:17 2014 -0500 @@ -0,0 +1,84 @@ +/* + * 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.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.agent.VmStatusListener.Status; + + +public class ProfilerVmStatusListenerTest { + + private static final String VM_ID = "foo"; + private static final int VM_PID = 1; + + private VmProfiler profiler; + + private ProfilerVmStatusListener listener; + + @Before + public void setUp() { + profiler = mock(VmProfiler.class); + listener = new ProfilerVmStatusListener(profiler); + } + + @Test + public void forwardsStartEventToProfiler() throws Exception { + listener.vmStatusChanged(Status.VM_STARTED, VM_ID, VM_PID); + verify(profiler).vmStarted(VM_ID, VM_PID); + } + + @Test + public void forwardsActiveEventToProfiler() throws Exception { + listener.vmStatusChanged(Status.VM_ACTIVE, VM_ID, VM_PID); + verify(profiler).vmStarted(VM_ID, VM_PID); + } + + @Test + public void forwardsStopEventToProfiler() throws Exception { + listener.vmStatusChanged(Status.VM_STOPPED, VM_ID, VM_PID); + verify(profiler).vmStopped(VM_ID, VM_PID); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/RemoteProfilerCommunicatorTest.java Wed Dec 03 11:53:17 2014 -0500 @@ -0,0 +1,135 @@ +/* + * 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.*; +import static org.mockito.Matchers.eq; +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 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.RemoteProfilerCommunicator.Attacher; +import com.sun.tools.attach.VirtualMachine; + +public class RemoteProfilerCommunicatorTest { + + private static final String OBJECT_NAME = "com.redhat.thermostat:type=InstrumentationControl"; + private static final int PID = 0; + + private MXBeanConnection connection; + private MXBeanConnectionPool pool; + + private VirtualMachine vm; + private Attacher attacher; + + private RemoteProfilerCommunicator communicator; + + private MBeanServerConnection server; + + @Before + public void setUp() throws Exception { + server = mock(MBeanServerConnection.class); + connection = mock(MXBeanConnection.class); + when(connection.get()).thenReturn(server); + + pool = mock(MXBeanConnectionPool.class); + when(pool.acquire(PID)).thenReturn(connection); + + vm = mock(VirtualMachine.class); + attacher = mock(Attacher.class); + when(attacher.attach(eq(String.valueOf(PID)))).thenReturn(vm); + + communicator = new RemoteProfilerCommunicator(pool, attacher); + } + + @Test + public void loadAgentIntoPidAttachesAndLoadsTheAgent() throws Exception { + String AGENT_JAR = "agent-jar"; + String AGENT_OPTIONS = "foo-bar"; + + communicator.loadAgentIntoPid(PID, AGENT_JAR, AGENT_OPTIONS); + + verify(attacher).attach(String.valueOf(PID)); + verify(vm).loadAgent(AGENT_JAR, AGENT_OPTIONS); + verify(vm).detach(); + verifyNoMoreInteractions(vm); + } + + @Test + public void startProfilingMakesAnRmiCall() throws Exception { + communicator.startProfiling(PID); + + verify(server).invoke( + new ObjectName(OBJECT_NAME), + "startProfiling", + new Object[0], + new String[0]); + verifyNoMoreInteractions(server); + verify(pool).release(PID, connection); + } + + @Test + public void stopProfilingMakesAnRmiCall() throws Exception { + communicator.stopProfiling(PID); + + verify(server).invoke( + new ObjectName(OBJECT_NAME), + "stopProfiling", + new Object[0], + new String[0]); + verify(pool).release(PID, connection); + } + + @Test + public void getDataFileWorks() throws Exception { + communicator.getProfilingDataFile(PID); + + verify(server).getAttribute( + new ObjectName(OBJECT_NAME), + "ProfilingDataFile"); + verify(pool).release(PID, connection); + } +}
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java Wed Dec 03 16:01:36 2014 +0100 +++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java Wed Dec 03 11:53:17 2014 -0500 @@ -36,101 +36,150 @@ 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.times; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import java.io.BufferedWriter; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; 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.common.Clock; -import com.redhat.thermostat.vm.profiler.agent.internal.VmProfiler.Attacher; -import com.sun.tools.attach.VirtualMachine; +import com.redhat.thermostat.vm.profiler.agent.internal.VmProfiler.ProfileUploaderCreator; +import com.redhat.thermostat.vm.profiler.common.ProfileDAO; +import com.redhat.thermostat.vm.profiler.common.ProfileStatusChange; public class VmProfilerTest { + private static final String AGENT_ID = "some-agent"; + private static final String VM_ID = "some-vm"; + private static final int PID = 0; + + private static final String AGENT_JAR = "foo"; + private static final String ASM_JAR = "bar"; + private static final long TIMESTAMP = 1_000_000_000; + private VmProfiler profiler; - private VirtualMachine vm; - private MBeanServerConnection server; - private MXBeanConnection connection; - private MXBeanConnectionPool connectionPool; - private Attacher attacher; + private RemoteProfilerCommunicator remote; private Clock clock; + private ProfileDAO dao; - private final int PID = 0; - - private final String AGENT_JAR = "foo"; - private final String ASM_JAR = "bar"; - private final long TIME = 1_000_000_000; - - private ObjectName instrumentationName; + private ProfileUploader uploader; + private ProfileUploaderCreator profileUploaderCreator; @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); + dao = mock(ProfileDAO.class); + clock = mock(Clock.class); - when(clock.getRealTimeMillis()).thenReturn(TIME); + when(clock.getRealTimeMillis()).thenReturn(TIMESTAMP); - attacher = mock(Attacher.class); - vm = mock(VirtualMachine.class); - when(attacher.attach(isA(String.class))).thenReturn(vm); + uploader = mock(ProfileUploader.class); + profileUploaderCreator = mock(ProfileUploaderCreator.class); + when(profileUploaderCreator.create(dao, AGENT_ID, VM_ID, PID)).thenReturn(uploader); + + remote = mock(RemoteProfilerCommunicator.class); + + profiler = new VmProfiler(AGENT_ID, props, dao, clock, profileUploaderCreator, remote); + } - server = mock(MBeanServerConnection.class); - - connection = mock(MXBeanConnection.class); - when(connection.get()).thenReturn(server); + @Test (expected=ProfilerException.class) + public void doesNotProfileNotStartedVms() throws Exception { + profiler.startProfiling(VM_ID); + } - connectionPool = mock(MXBeanConnectionPool.class); - when(connectionPool.acquire(PID)).thenReturn(connection); + @Test (expected=ProfilerException.class) + public void doesNotProfileDeadVms() throws Exception { + profiler.vmStarted(VM_ID, PID); + profiler.vmStopped(VM_ID, PID); + profiler.startProfiling(VM_ID); + } - profiler = new VmProfiler(props, connectionPool, attacher, clock); + @Test (expected=ProfilerException.class) + public void doesNotStartingProfilingTwice() throws Exception { + profiler.vmStarted(VM_ID, PID); + profiler.startProfiling(VM_ID); + profiler.startProfiling(VM_ID); } @Test public void startingProfilingLoadsJvmAgentAndMakesAnRmiCall() throws Exception { - profiler.startProfiling(PID); + profiler.vmStarted(VM_ID, PID); + profiler.startProfiling(VM_ID); + + verify(remote).loadAgentIntoPid(PID, AGENT_JAR, ""); + verify(remote).startProfiling(PID); + verify(dao).addStatus(new ProfileStatusChange(AGENT_ID, VM_ID, TIMESTAMP, true)); + verifyNoMoreInteractions(remote); + } + + @Test (expected=ProfilerException.class) + public void doesNotStopProfilingAnUnknownVm() throws Exception { + profiler.stopProfiling(VM_ID); + } - verify(attacher).attach(String.valueOf(PID)); - verify(vm).loadAgent(AGENT_JAR, ""); - verify(vm).detach(); - verifyNoMoreInteractions(vm); + @Test (expected=ProfilerException.class) + public void doesNotStopProfilingNonProfiledVm() throws Exception { + profiler.vmStarted(VM_ID, PID); + profiler.stopProfiling(VM_ID); + } - verify(server).invoke(instrumentationName, "startProfiling", new Object[0], new String[0]); - verify(connectionPool).release(PID, connection); + @Test (expected=ProfilerException.class) + public void errorOnStoppingTwice() throws Exception { + final String FILE = "foobar"; + when(remote.getProfilingDataFile(PID)).thenReturn(FILE); + + profiler.vmStarted(VM_ID, PID); + profiler.startProfiling(VM_ID); + profiler.stopProfiling(VM_ID); + profiler.stopProfiling(VM_ID); } @Test - public void stoppingProfilingLoadsJvmAgentAndMakesAnRmiCall() throws Exception { + public void stoppingProfilingMakesInvokesStopUsingRmiAndUploadsData() throws Exception { + final String FILE = "foobar"; - when(server.getAttribute(instrumentationName, "ProfilingDataFile")).thenReturn(FILE); + when(remote.getProfilingDataFile(PID)).thenReturn(FILE); - ProfileUploader uploader = mock(ProfileUploader.class); + profiler.vmStarted(VM_ID, PID); + profiler.startProfiling(VM_ID); + profiler.stopProfiling(VM_ID); - profiler.stopProfiling(PID, uploader); + verify(dao).addStatus(new ProfileStatusChange(AGENT_ID, VM_ID, TIMESTAMP, false)); + + verify(remote).stopProfiling(PID); + verify(uploader).upload(TIMESTAMP, new File(FILE)); + verifyNoMoreInteractions(uploader); + } - verifyNoMoreInteractions(vm); + @Test + public void gathersAndUploadsProfileDataOnVmExit() throws Exception { + // this is written on the target vm exit + File profilingResults = new File(System.getProperty("java.io.tmpdir"), "thermostat-" + PID + "-foobar.perfdata"); + try (BufferedWriter writer = Files.newBufferedWriter(profilingResults.toPath(), StandardCharsets.UTF_8)) { + writer.append("test file, please ignore"); + } - verify(server).invoke(instrumentationName, "stopProfiling", new Object[0], new String[0]); - verify(uploader).upload(TIME, new File(FILE)); - verify(connectionPool, times(2)).release(PID, connection); + profiler.vmStarted(VM_ID, PID); + profiler.startProfiling(VM_ID); + profiler.vmStopped(VM_ID, PID); + verify(remote, never()).stopProfiling(PID); + verify(uploader).upload(TIMESTAMP, profilingResults); + verify(dao).addStatus(new ProfileStatusChange(AGENT_ID, VM_ID, TIMESTAMP, false)); + profilingResults.delete(); } }
--- a/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileRequest.java Wed Dec 03 16:01:36 2014 +0100 +++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileRequest.java Wed Dec 03 11:53:17 2014 -0500 @@ -45,7 +45,7 @@ public static Request create(InetSocketAddress address, String vmId, String action) { Request request = new Request(RequestType.RESPONSE_EXPECTED, address); - request.setReceiver("com.redhat.thermostat.vm.profiler.agent.internal.ProfileVmRequestReceiver"); + request.setReceiver("com.redhat.thermostat.vm.profiler.agent.internal.ProfilerRequestReceiver"); request.setParameter(Request.ACTION, ProfileRequest.NAME); request.setParameter(ProfileRequest.PROFILE_ACTION, action); request.setParameter(ProfileRequest.VM_ID, vmId);