changeset 369:6bcdf872ca87

Implement very basic heap dumper. Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-June/001776.html PR 1032
author Roman Kennke <rkennke@redhat.com>
date Wed, 13 Jun 2012 16:04:57 +0200
parents e087c8526a58
children 912f1f0e5e09
files agent/pom.xml client/core/pom.xml client/heapdumper/pom.xml client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/Activator.java client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpAction.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/ActivatorTest.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpActionTest.java client/killvm/pom.xml client/memory-stats-panel/pom.xml client/pom.xml client/vmclassstat/pom.xml common/pom.xml distribution/config/osgi-export.properties distribution/pom.xml dolphin/pom.xml pom.xml tools/pom.xml unix-process-handler/pom.xml
diffstat 18 files changed, 625 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/agent/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/agent/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -84,7 +84,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/client/core/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -137,7 +137,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012 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.
+
+-->
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>thermostat-client</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.3-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-client-heapdumper</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat Client Heap Dumper plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-Activator>com.redhat.thermostat.client.heap.Activator</Bundle-Activator>
+            <Private-Package>com.redhat.thermostat.client.heap</Private-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common</artifactId>
+      <version>${project.version}</version>
+      <type>bundle</type>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-core</artifactId>
+      <version>${project.version}</version>
+      <type>bundle</type>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/Activator.java	Wed Jun 13 16:04:57 2012 +0200
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 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.client.heap;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
+import com.redhat.thermostat.client.osgi.service.VMContextAction;
+
+public class Activator implements BundleActivator {
+
+    private ServiceRegistration contextServiceReg;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+
+        // FIXME: there should be a better way than this
+        ServiceListener listener = new ServiceListener() {
+            
+            private ApplicationService appService;
+            private boolean contextActionServiceLoaded;
+            private boolean applicationServiceLoaded;
+            
+            @Override
+            public void serviceChanged(ServiceEvent event) {
+                
+                ServiceReference reference = event.getServiceReference();
+                Object service = context.getService(reference);
+                switch (event.getType()) {
+                case ServiceEvent.REGISTERED:
+                    if (service instanceof ContextAction) {
+                        contextActionServiceLoaded = true;
+                    } else if (service instanceof ApplicationService) {
+                        applicationServiceLoaded = true;
+                        appService = (ApplicationService) service;
+                    }
+                    break;
+
+                default:
+                    break;
+                }
+                
+                if (contextActionServiceLoaded && applicationServiceLoaded) {
+                    contextServiceReg = context.registerService(VMContextAction.class.getName(),
+                                            new HeapDumpAction(appService.getDAOFactory()), null);
+                }
+            }
+        };
+        
+        String filter = "(|(objectClass=" + ContextAction.class.getName() + ")"
+                + "  (objectClass=" + ApplicationService.class.getName() + "))";
+
+        context.addServiceListener(listener, filter);
+        ServiceReference[] services = context.getServiceReferences(null, null);
+        if (services != null) {
+            for (int i = 0; i < services.length; i++) {
+                listener.serviceChanged(new ServiceEvent(
+                        ServiceEvent.REGISTERED, services[i]));
+            }
+        }
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        if (contextServiceReg != null) {
+            contextServiceReg.unregister();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpAction.java	Wed Jun 13 16:04:57 2012 +0200
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012 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.client.heap;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.client.osgi.service.Filter;
+import com.redhat.thermostat.client.osgi.service.VMContextAction;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.Ref;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmInfo;
+
+/**
+ * Implements the {@link VMContextAction} entry point to provide a kill switch
+ * for the currently selected Virtual Machine. 
+ */
+public class HeapDumpAction implements VMContextAction {
+
+    private static final Logger log = Logger.getLogger(HeapDumpAction.class.getName());
+
+    private final DAOFactory dao;
+
+    public HeapDumpAction(DAOFactory dao) {
+        this.dao = dao;
+    }
+
+    @Override
+    public String getName() {
+        return "Heap Dump";
+    }
+
+    @Override
+    public String getDescription() {
+        return "Generate a heap dump";
+    }
+
+    @Override
+    public void execute(VmRef reference) {
+        try {
+            File tempFile = Files.createTempFile("thermostat-", "-heapdump").toFile();
+            String tempFileName = tempFile.getAbsolutePath();
+            tempFile.delete(); // Need to delete before dumping heap, jmap does not override existing file and stop with an error.
+            Process proc = Runtime.getRuntime().exec(new String[] {"jmap", "-dump:format=b,file=" + tempFileName, reference.getIdString()});
+            try {
+                proc.waitFor();
+                log.info("Heap dump written to: " + tempFileName);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        } catch (IOException e) {
+            log.log(Level.SEVERE, "Unexpected IO problem while writing heap dump", e);
+        }
+    }
+
+    @Override
+    public Filter getFilter() {
+        return new LocalAndAliveFilter();
+    }
+
+    private class LocalAndAliveFilter implements Filter {
+
+        @Override
+        public boolean matches(Ref ref) {
+            // TODO implement local checking too
+            if (ref instanceof VmRef) {
+                VmRef vm = (VmRef) ref;
+                VmInfo vmInfo = dao.getVmInfoDAO().getVmInfo(vm);
+                return vmInfo.isAlive();
+            } else {
+                return false;
+            }
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/ActivatorTest.java	Wed Jun 13 16:04:57 2012 +0200
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2012 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.client.heap;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Dictionary;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
+import com.redhat.thermostat.client.osgi.service.VMContextAction;
+
+public class ActivatorTest {
+
+    private Activator activator;
+    private BundleContext bundleContext;
+    private ArgumentCaptor<ServiceListener> serviceListener;
+    private ServiceReference ref1;
+    private ContextAction service1;
+    private ServiceReference ref2;
+    private ApplicationService service2;
+    private ServiceReference ref3;
+
+    @Before
+    public void setUp() throws InvalidSyntaxException {
+        activator = new Activator();
+        bundleContext = mock(BundleContext.class);
+        serviceListener = ArgumentCaptor.forClass(ServiceListener.class);
+        doNothing().when(bundleContext).addServiceListener(serviceListener.capture(), anyString());
+        ref1 = mock(ServiceReference.class);
+        service1 = mock(ContextAction.class);
+        when(bundleContext.getService(ref1)).thenReturn(service1);
+        ref2 = mock(ServiceReference.class);
+        service2 = mock(ApplicationService.class);
+        when(bundleContext.getService(ref2)).thenReturn(service2);
+        ref3 = mock(ServiceReference.class);
+        when(bundleContext.getService(ref3)).thenReturn(new Object());
+    }
+
+    @Test
+    public void testStart() throws Exception {
+        activator.start(bundleContext);
+        verifyRegistration(false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref1, false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref2, true);
+    }
+
+    @Test
+    public void testStartAppServiceFirst() throws Exception {
+        activator.start(bundleContext);
+        verifyRegistration(false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref2, false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref1, true);
+    }
+
+    @Test
+    public void testStartOtherService() throws Exception {
+        activator.start(bundleContext);
+        verifyRegistration(false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref2, false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref3, false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref1, true);
+    }
+
+    @Test
+    public void testStartDifferentEventType() throws Exception {
+        activator.start(bundleContext);
+        verifyRegistration(false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref2, false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.MODIFIED, ref1, false);
+        fireServiceEventAndVerifyRegistration(ServiceEvent.REGISTERED, ref1, true);
+    }
+
+    @Test
+    public void testStartExistingServices() throws Exception {
+
+        when(bundleContext.getServiceReferences(null, null)).thenReturn(new ServiceReference[] { ref1, ref2 });
+
+        activator.start(bundleContext);
+
+        verifyRegistration(true);
+    }
+
+    @Test
+    public void testStop() throws Exception {
+        ServiceRegistration serviceReg = mock(ServiceRegistration.class);
+        when(bundleContext.registerService(eq(VMContextAction.class.getName()), any(), any(Dictionary.class))).thenReturn(serviceReg);
+        testStart(); // Performs all the registration tasks.
+        verify(serviceReg, never()).unregister();
+        activator.stop(bundleContext);
+        verify(serviceReg).unregister();
+    }
+
+    @Test
+    public void testStopIncompleteRegistration() throws Exception {
+        ServiceRegistration serviceReg = mock(ServiceRegistration.class);
+        when(bundleContext.registerService(eq(VMContextAction.class.getName()), any(), any(Dictionary.class))).thenReturn(serviceReg);
+
+        activator.start(bundleContext);
+        fireServiceEvent(ServiceEvent.REGISTERED, ref2);
+        verify(serviceReg, never()).unregister();
+
+        activator.stop(bundleContext);
+        verify(serviceReg, never()).unregister();
+    }
+
+    private void fireServiceEventAndVerifyRegistration(int type, ServiceReference ref, boolean expectRegisterCalled) {
+        fireServiceEvent(type, ref);
+        verifyRegistration(expectRegisterCalled);
+    }
+
+    private void fireServiceEvent(int type, ServiceReference ref) {
+        ServiceEvent event = new ServiceEvent(type, ref);
+        serviceListener.getValue().serviceChanged(event);
+    }
+
+    private void verifyRegistration(boolean expectRegisterCalled) {
+        if (expectRegisterCalled) {
+            verify(bundleContext).registerService(eq(VMContextAction.class.getName()), Matchers.isA(HeapDumpAction.class), any(Dictionary.class));
+        } else {
+            verify(bundleContext, never()).registerService(anyString(), any(), any(Dictionary.class));
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpActionTest.java	Wed Jun 13 16:04:57 2012 +0200
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 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.client.heap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.client.osgi.service.Filter;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.Ref;
+import com.redhat.thermostat.common.dao.VmInfoDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmInfo;
+
+public class HeapDumpActionTest {
+
+    private HeapDumpAction heapDumpAction;
+    private DAOFactory dao;
+    private VmRef aliveVmRef;
+    private VmRef deadVmRef;
+
+    @Before
+    public void setUp() {
+        dao = mock(DAOFactory.class);
+        VmInfoDAO vmInfoDAO = mock(VmInfoDAO.class);
+        when(dao.getVmInfoDAO()).thenReturn(vmInfoDAO);
+
+        VmInfo vmInfo1 = mock(VmInfo.class);
+        when(vmInfo1.isAlive()).thenReturn(true);
+        aliveVmRef = mock(VmRef.class);
+        when(vmInfoDAO.getVmInfo(aliveVmRef)).thenReturn(vmInfo1);
+
+        VmInfo vmInfo2 = mock(VmInfo.class);
+        when(vmInfo2.isAlive()).thenReturn(false);
+        deadVmRef = mock(VmRef.class);
+        when(vmInfoDAO.getVmInfo(deadVmRef)).thenReturn(vmInfo2);
+
+        heapDumpAction = new HeapDumpAction(dao);
+    }
+
+    @After
+    public void tearDown() {
+        heapDumpAction = null;
+        deadVmRef = null;
+        aliveVmRef = null;
+        dao = null;
+    }
+
+    @Test
+    public void testName() {
+        assertEquals("Heap Dump", heapDumpAction.getName());
+    }
+
+    @Test
+    public void testDescription() {
+        assertEquals("Generate a heap dump", heapDumpAction.getDescription());
+    }
+
+    @Test
+    public void testFilter() {
+        Filter filter = heapDumpAction.getFilter();
+        assertTrue(filter.matches(aliveVmRef));
+        assertFalse(filter.matches(deadVmRef));
+        assertFalse(filter.matches(mock(Ref.class)));
+    }
+
+    @Test
+    public void testExec() throws IOException {
+        // TODO: Not tested yet. We cannot mock Runtime.exec(), not even with PowerMock.
+        // We should create a wrapper API (that needs to remain untested, and for this
+        // reason should be *really* dumb), against which we can test.
+    }
+}
--- a/client/killvm/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/killvm/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -15,7 +15,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/client/memory-stats-panel/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/memory-stats-panel/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -15,7 +15,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/client/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -62,6 +62,7 @@
     <module>core</module>
     <module>vmclassstat</module>
     <module>killvm</module>
+    <module>heapdumper</module>
     <module>memory-stats-panel</module>
     <module>living-vm-filter</module>
   </modules>
--- a/client/vmclassstat/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/vmclassstat/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -15,7 +15,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/common/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/common/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -56,7 +56,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/distribution/config/osgi-export.properties	Tue Jun 12 11:30:24 2012 +0200
+++ b/distribution/config/osgi-export.properties	Wed Jun 13 16:04:57 2012 +0200
@@ -47,6 +47,7 @@
 org.jfree.ui
 sun.swing
 sun.misc
+sun.jvm.hotspot.tools
 
 jline=2.5.0
 jline.console=2.5.0
--- a/distribution/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/distribution/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -234,6 +234,12 @@
     	<type>bundle</type>
     </dependency>
     <dependency>
+    	<groupId>com.redhat.thermostat</groupId>
+    	<artifactId>thermostat-client-heapdumper</artifactId>
+    	<version>${project.version}</version>
+    	<type>bundle</type>
+    </dependency>
+    <dependency>
         <groupId>com.redhat.thermostat</groupId>
         <artifactId>thermostat-laf</artifactId>
         <version>${project.version}</version>
--- a/dolphin/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/dolphin/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -29,7 +29,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -136,6 +136,11 @@
           <artifactId>jacoco-maven-plugin</artifactId>
           <version>0.5.6.201201232323</version>
         </plugin>
+        <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <version>1.4.0</version>
+        </plugin>
       </plugins>
     </pluginManagement>
     <plugins>
--- a/tools/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/tools/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -103,7 +103,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
--- a/unix-process-handler/pom.xml	Tue Jun 12 11:30:24 2012 +0200
+++ b/unix-process-handler/pom.xml	Wed Jun 13 16:04:57 2012 +0200
@@ -17,7 +17,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>1.4.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>