changeset 575:0375b44c73d4

Implement remote heapdumper. Reviewed-by: vanaltj, jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-August/002967.html
author Roman Kennke <rkennke@redhat.com>
date Fri, 31 Aug 2012 11:22:52 +0200
parents 5451224fbd0e
children 4fbe27356f96
files agent/heapdumper/pom.xml agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/Activator.java agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpException.java agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiver.java agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/JMXHeapDumper.java agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/JMapHeapDumper.java agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/ActivatorTest.java agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiverTest.java agent/pom.xml client/heapdumper/pom.xml client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapView.java client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommandTest.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/swing/HeapSwingViewTest.java common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAO.java common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/heap/HeapDump.java common/core/src/main/java/com/redhat/thermostat/common/heap/HistogramLoader.java common/core/src/main/java/com/redhat/thermostat/common/model/HeapInfo.java common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/model/HeapInfoTest.java distribution/config/bundles.properties distribution/pom.xml
diffstat 30 files changed, 1279 insertions(+), 232 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/pom.xml	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,113 @@
+<?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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-agent</artifactId>
+    <version>0.4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-agent-heapdumper</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Thermostat Heapdumper Agent Module</name>
+
+  <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.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-agent-command</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+  </dependencies>
+
+  <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.agent.heapdumper.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>com.redhat.thermostat.agent.heapdumper</Bundle-SymbolicName>
+            <Export-Package>
+              com.redhat.thermostat.agent.heapdumper
+            </Export-Package>
+            <Private-Package>
+              com.redhat.thermostat.agent.heapdumper.internal
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/Activator.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,60 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+import com.redhat.thermostat.agent.command.ReceiverRegistry;
+
+public class Activator implements BundleActivator {
+
+    private ReceiverRegistry receivers;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        receivers = new ReceiverRegistry(context);
+        receivers.registerReceiver(new HeapDumpReceiver());
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        receivers.unregisterReceivers();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpException.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,59 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+@SuppressWarnings("serial")
+public class HeapDumpException extends Exception {
+
+    public HeapDumpException() {
+        super();
+    }
+
+    public HeapDumpException(String message) {
+        super(message);
+    }
+
+    public HeapDumpException(Throwable cause) {
+        super(cause);
+    }
+
+    public HeapDumpException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiver.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,125 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+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.agent.command.RequestReceiver;
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+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.dao.HeapDAO;
+import com.redhat.thermostat.common.heap.HistogramLoader;
+import com.redhat.thermostat.common.heap.ObjectHistogram;
+import com.redhat.thermostat.common.model.HeapInfo;
+
+public class HeapDumpReceiver implements RequestReceiver {
+
+    private static final Logger log = Logger.getLogger(HeapDumpReceiver.class.getName());
+
+    private JMXHeapDumper jmxHeapDumper;
+    private JMapHeapDumper jmapHeapDumper;
+
+    private HistogramLoader histogramLoader;
+    public HeapDumpReceiver() {
+        this(new JMXHeapDumper(), new JMapHeapDumper(), new HistogramLoader());
+    }
+
+    HeapDumpReceiver(JMXHeapDumper jmxHeapDumper, JMapHeapDumper jmapHeapDumper, HistogramLoader histogramLoader) {
+        this.jmxHeapDumper = jmxHeapDumper;
+        this.jmapHeapDumper = jmapHeapDumper;
+        this.histogramLoader = histogramLoader;
+    }
+
+    @Override
+    public Response receive(Request request) {
+        String vmId = request.getParameter("vmId");
+        try {
+            File heapDumpFile = dumpHeap(vmId);
+            ObjectHistogram histogram = loadHistogram(heapDumpFile.getAbsolutePath());
+            saveHeapDumpInfo(vmId, heapDumpFile, histogram);
+            
+        } catch (IOException e) {
+            log.log(Level.SEVERE, "Unexpected IO problem while writing heap dump", e);
+            return new Response(ResponseType.EXCEPTION);
+        } catch (HeapDumpException e) {
+            log.log(Level.SEVERE, "Unexpected problem while writing heap dump", e);
+            return new Response(ResponseType.EXCEPTION);
+        }
+        return new Response(ResponseType.OK);
+    }
+
+    private File dumpHeap(String vmId) throws IOException, HeapDumpException {
+        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.
+        dumpHeapUsingJMX(vmId, tempFileName);
+        return tempFile;
+    }
+
+    private void dumpHeapUsingJMX(String vmId, String filename) throws HeapDumpException {
+
+        try {
+            jmxHeapDumper.dumpHeap(vmId, filename);
+        } catch (HeapDumpException e) {
+            log.log(Level.WARNING, "Heap dump using JMX failed, trying jmap", e);
+            dumpHeapUsingJMap(vmId, filename);
+        }
+    }
+
+    private void dumpHeapUsingJMap(String vmId, String filename) throws HeapDumpException {
+        jmapHeapDumper.dumpHeap(vmId, filename);
+    }
+
+    private ObjectHistogram loadHistogram(String heapDumpFilename) throws IOException {
+        return histogramLoader.load(heapDumpFilename);
+    }
+
+    private void saveHeapDumpInfo(String vmId, File tempFile, ObjectHistogram histogram) throws IOException {
+    
+        HeapDAO heapDAO = ApplicationContext.getInstance().getDAOFactory().getHeapDAO();
+        HeapInfo heapInfo = new HeapInfo(Integer.parseInt(vmId), System.currentTimeMillis());
+        heapDAO.putHeapInfo(heapInfo, tempFile, histogram);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/JMXHeapDumper.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,92 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+import java.io.File;
+import java.util.Properties;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import com.sun.tools.attach.VirtualMachine;
+
+class JMXHeapDumper {
+
+    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
+
+    void dumpHeap(String vmId, String filename) throws HeapDumpException {
+        try {
+            doHeapDump(vmId, filename);
+        } catch (Exception ex) {
+            throw new HeapDumpException(ex);
+        }
+    }
+
+    private void doHeapDump(String vmId, String filename) throws Exception {
+
+        VirtualMachine vm = VirtualMachine.attach(vmId);
+        String connectorAddress = getConnectorAddress(vm);
+        JMXServiceURL url = new JMXServiceURL(connectorAddress);
+
+        try (JMXConnector conn = JMXConnectorFactory.connect(url)) {
+            MBeanServerConnection mbsc = conn.getMBeanServerConnection();
+            mbsc.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"),
+                        "dumpHeap",
+                        new Object[] { filename, Boolean.TRUE },
+                        new String[] { String.class.getName(), boolean.class.getName() });
+        }
+    }
+
+    private String getConnectorAddress(VirtualMachine vm) throws Exception {
+
+        Properties props = vm.getAgentProperties();
+        String connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
+        if (connectorAddress == null) {
+            props = vm.getSystemProperties();
+            String home = props.getProperty("java.home");
+            String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
+            vm.loadAgent(agent);
+            props = vm.getAgentProperties();
+            connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
+        }
+        return connectorAddress;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/JMapHeapDumper.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,59 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+class JMapHeapDumper {
+
+    private static final Logger log = Logger.getLogger(JMapHeapDumper.class.getName());
+
+    void dumpHeap(String vmId, String filename) throws HeapDumpException {
+        try {
+            Process proc = Runtime.getRuntime().exec(
+                    new String[] { "jmap", "-dump:format=b,file=" + filename, vmId });
+            proc.waitFor();
+            log.info("Heap dump written to: " + filename);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        } catch (IOException ex) {
+            throw new HeapDumpException(ex);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/ActivatorTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,70 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Dictionary;
+
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.agent.command.RequestReceiver;
+
+public class ActivatorTest {
+
+    @Test
+    public void testStartStop() throws Exception {
+        BundleContext ctx = mock(BundleContext.class);
+        ServiceRegistration serviceReg = mock(ServiceRegistration.class);
+        when(ctx.registerService(anyString(), any(), any(Dictionary.class))).thenReturn(serviceReg);
+        Activator activator = new Activator();
+        activator.start(ctx);
+        verify(ctx).registerService(eq(RequestReceiver.class.getName()), isA(HeapDumpReceiver.class), any(Dictionary.class));
+        activator.stop(ctx);
+        verify(serviceReg).unregister();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiverTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,153 @@
+/*
+ * 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.agent.heapdumper.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+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.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.HeapDAO;
+import com.redhat.thermostat.common.heap.HistogramLoader;
+import com.redhat.thermostat.common.heap.ObjectHistogram;
+import com.redhat.thermostat.common.model.HeapInfo;
+
+public class HeapDumpReceiverTest {
+
+    private HeapDAO heapDAO;
+    private Request request;
+    private JMXHeapDumper jmxDumper;
+    private HistogramLoader histogramLoader;
+
+    private HeapDumpReceiver receiver;
+    private JMapHeapDumper jmapDumper;
+
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+
+        heapDAO = mock(HeapDAO.class);
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getHeapDAO()).thenReturn(heapDAO);
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        request = mock(Request.class);
+        when(request.getParameter("vmId")).thenReturn("123");
+        jmxDumper = mock(JMXHeapDumper.class);
+        jmapDumper = mock(JMapHeapDumper.class);
+        histogramLoader = mock(HistogramLoader.class);
+        receiver = new HeapDumpReceiver(jmxDumper, jmapDumper, histogramLoader);
+    }
+
+    @After
+    public void tearDown() {
+        histogramLoader = null;
+        jmapDumper = null;
+        jmxDumper = null;
+        request = null;
+        heapDAO = null;
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @Test
+    public void testJMXHeapDump() throws Exception {
+
+        ObjectHistogram expectedHistogramData = mock(ObjectHistogram.class);
+        when(histogramLoader.load(anyString())).thenReturn(expectedHistogramData);
+        
+        Response response = receiver.receive(request);
+
+        assertEquals(ResponseType.OK, response.getType());
+        ArgumentCaptor<String> filename = ArgumentCaptor.forClass(String.class);
+        verify(jmxDumper).dumpHeap(eq("123"), filename.capture());
+        verify(histogramLoader).load(filename.getValue());
+        ArgumentCaptor<HeapInfo> heapInfo = ArgumentCaptor.forClass(HeapInfo.class);
+        verify(heapDAO).putHeapInfo(heapInfo.capture(), eq(new File(filename.getValue())), same(expectedHistogramData));
+        assertEquals(123, heapInfo.getValue().getVmId());
+    }
+
+    @Test
+    public void verifyFallbackWhenJMXFails() throws HeapDumpException {
+
+        doThrow(new HeapDumpException()).when(jmxDumper).dumpHeap(anyString(), anyString());
+
+        Response response = receiver.receive(request);
+
+        assertEquals(ResponseType.OK, response.getType());
+        verify(jmxDumper).dumpHeap(eq("123"), anyString());
+        
+    }
+
+    @Test
+    public void verifyResponseTypeWhenAllFails() throws HeapDumpException {
+        doThrow(new HeapDumpException()).when(jmxDumper).dumpHeap(anyString(), anyString());
+        doThrow(new HeapDumpException()).when(jmapDumper).dumpHeap(anyString(), anyString());
+        Response response = receiver.receive(request);
+
+        assertEquals(ResponseType.EXCEPTION, response.getType());
+        
+    }
+
+    @Test
+    public void verifyResponseTypeWhenIOFails() throws HeapDumpException, IOException {
+        doThrow(new IOException()).when(histogramLoader).load(anyString());
+
+        Response response = receiver.receive(request);
+
+        assertEquals(ResponseType.EXCEPTION, response.getType());
+        
+    }
+}
--- a/agent/pom.xml	Wed Aug 22 14:52:00 2012 +0200
+++ b/agent/pom.xml	Fri Aug 31 11:22:52 2012 +0200
@@ -62,6 +62,7 @@
     <module>cli</module>
     <module>core</module>
     <module>command</module>
+    <module>heapdumper</module>
   </modules>
 
 </project>
--- a/client/heapdumper/pom.xml	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/pom.xml	Fri Aug 31 11:22:52 2012 +0200
@@ -126,6 +126,19 @@
     </dependency>
     
     <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-command</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-agent-heapdumper</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
       <groupId>org.jfree</groupId>
       <artifactId>jfreechart</artifactId>
     </dependency>
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java	Fri Aug 31 11:22:52 2012 +0200
@@ -38,16 +38,17 @@
 
 import java.text.DecimalFormat;
 import java.text.NumberFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import com.redhat.thermostat.client.heap.HeapView.HeadDumperAction;
+import com.redhat.thermostat.client.heap.HeapView.HeapDumperAction;
 import com.redhat.thermostat.client.heap.chart.OverviewChart;
 import com.redhat.thermostat.client.heap.cli.HeapDumperCommand;
 import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.client.osgi.service.BasicView.Action;
 import com.redhat.thermostat.client.osgi.service.VmInformationServiceController;
-import com.redhat.thermostat.client.osgi.service.BasicView.Action;
 import com.redhat.thermostat.client.ui.UIComponent;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
@@ -79,6 +80,10 @@
     private ApplicationService appService;
 
     public HeapDumpController(final VmRef ref, final ApplicationService appService) {
+        this(ref, appService, new HeapDumperCommand());
+    }
+
+    HeapDumpController(final VmRef ref, final ApplicationService appService, final HeapDumperCommand command) {
         
         this.appService = appService;
         this.ref = ref;
@@ -134,22 +139,18 @@
                 }
             }
         });
-        
-        final HeapDumperCommand command = new HeapDumperCommand();
-        view.addDumperListener(new ActionListener<HeapView.HeadDumperAction>() {
+
+        view.addDumperListener(new ActionListener<HeapView.HeapDumperAction>() {
             @Override
-            public void actionPerformed(ActionEvent<HeadDumperAction> actionEvent) {
+            public void actionPerformed(ActionEvent<HeapDumperAction> actionEvent) {
                 HeapDump dump = null;
                 switch (actionEvent.getActionId()) {
                 case DUMP_REQUESTED:
-                    dump = command.execute(ref);
-                    view.addHeapDump(dump);
-                    
-                    // also, only if it's the fist dump, we jump there
-                    // it doesn't disrupt the workflow if it's the first dump
-                    if (appService.getApplicationCache().getAttribute(ref) == null) {
-                        analyseDump(dump);
-                    }
+                    command.execute(ref, new Runnable() {
+                        public void run() {
+                            view.notifyHeapDumpComplete();
+                        }
+                    });
                     
                     break;
                 
@@ -192,9 +193,18 @@
     }
 
     class HeapOverviewDataCollector implements Runnable {
+        private void checkForHeapDumps() {
+            Collection<HeapInfo> heapInfos = heapDAO.getAllHeapInfo(ref);
+            List<HeapDump> heapDumps = new ArrayList<HeapDump>(heapInfos.size());
+            for (HeapInfo heapInfo : heapInfos) {
+                heapDumps.add(new HeapDump(heapInfo, heapDAO));
+            }
+            view.updateHeapDumpList(heapDumps);
+        }
+
         @Override
         public void run() {
-            
+            checkForHeapDumps();
             List<VmMemoryStat> vmInfo = vmDao.getLatestVmMemoryStats(ref, System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1));
             for (VmMemoryStat memoryStats: vmInfo) {
                 long used = 0l;
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapView.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapView.java	Fri Aug 31 11:22:52 2012 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.client.heap;
 
+import java.util.List;
+
 import com.redhat.thermostat.client.heap.chart.OverviewChart;
 import com.redhat.thermostat.client.osgi.service.BasicView;
 import com.redhat.thermostat.common.ActionListener;
@@ -44,22 +46,22 @@
 
 public abstract class HeapView extends BasicView {
     
-    public enum HeadDumperAction {
+    public enum HeapDumperAction {
         DUMP_REQUESTED,
         ANALYSE,
         REQUEST_ABORTED
     }
    
-    protected final ActionNotifier<HeadDumperAction> heapDumperNotifier;
+    protected final ActionNotifier<HeapDumperAction> heapDumperNotifier;
     protected HeapView() {
-        heapDumperNotifier = new ActionNotifier<HeadDumperAction>(this);
+        heapDumperNotifier = new ActionNotifier<HeapDumperAction>(this);
     }
     
-    public void addDumperListener(ActionListener<HeadDumperAction> listener) {
+    public void addDumperListener(ActionListener<HeapDumperAction> listener) {
         heapDumperNotifier.addActionListener(listener);
     }
     
-    public void removeDumperListener(ActionListener<HeadDumperAction> listener) {
+    public void removeDumperListener(ActionListener<HeapDumperAction> listener) {
         heapDumperNotifier.removeActionListener(listener);
     }
 
@@ -69,4 +71,7 @@
     
     abstract public void openDumpView();
     abstract public void setChildView(BasicView childView);
+    public abstract void notifyHeapDumpComplete();
+
+    public abstract void updateHeapDumpList(List<HeapDump> heapDumps);
 }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java	Fri Aug 31 11:22:52 2012 +0200
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.client.heap.cli;
 
 import java.util.Collection;
+import java.util.concurrent.Semaphore;
 
 import com.redhat.thermostat.client.heap.LocaleResources;
 import com.redhat.thermostat.client.heap.Translate;
@@ -87,9 +88,22 @@
     public void run(CommandContext ctx) throws CommandException {
         HostVMArguments args = new HostVMArguments(ctx.getArguments());
 
-        HeapDump hd = implementation.execute(args.getVM());
-        ctx.getConsole().getOutput().print(Translate.localize(LocaleResources.COMMAND_HEAP_DUMP_DONE));
-        ctx.getConsole().getOutput().print("\n");
+        final Semaphore s = new Semaphore(0);
+        Runnable r = new Runnable() {
+            
+            @Override
+            public void run() {
+                s.release();
+            }
+        };
+        implementation.execute(args.getVM(), r);
+        try {
+            s.acquire();
+            ctx.getConsole().getOutput().print(Translate.localize(LocaleResources.COMMAND_HEAP_DUMP_DONE));
+            ctx.getConsole().getOutput().print("\n");
+        } catch (InterruptedException ex) {
+            // Nothing to do here, just return ASAP.
+        }
     }
 
 }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java	Fri Aug 31 11:22:52 2012 +0200
@@ -36,123 +36,55 @@
 
 package com.redhat.thermostat.client.heap.cli;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.Properties;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.net.InetSocketAddress;
 
-import javax.management.InstanceNotFoundException;
-import javax.management.MBeanException;
-import javax.management.MBeanServerConnection;
-import javax.management.MalformedObjectNameException;
-import javax.management.ObjectName;
-import javax.management.ReflectionException;
-import javax.management.remote.JMXConnector;
-import javax.management.remote.JMXConnectorFactory;
-import javax.management.remote.JMXServiceURL;
-
+import com.redhat.thermostat.client.command.RequestQueue;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
-import com.redhat.thermostat.common.dao.HeapDAO;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.heap.HeapDump;
-import com.redhat.thermostat.common.heap.HistogramLoader;
-import com.redhat.thermostat.common.heap.ObjectHistogram;
-import com.redhat.thermostat.common.model.HeapInfo;
-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.common.utils.OSGIUtils;
 
 public class HeapDumperCommand {
     
-    private static final Logger log = Logger.getLogger(HeapDumperCommand.class.getName());
-    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
+    private static final String RECEIVER_CLASS_NAME = "com.redhat.thermostat.agent.heapdumper.internal.HeapDumpReceiver";
+    private static final String VM_ID_PARAM = "vmId";
 
-    public HeapDump execute(VmRef reference) {
+    private class HeapDumpListener implements RequestResponseListener {
+
+        private Runnable heapDumpCompleteAction;
 
-        try {
-            File heapDumpFile = dumpHeap(reference);
-            ObjectHistogram histogram = loadHistogram(heapDumpFile.getAbsolutePath());
-            HeapInfo info = saveHeapDumpInfo(reference, heapDumpFile, histogram);
-            
-            HeapDump dump = new HeapDump(info, ApplicationContext.getInstance().getDAOFactory().getHeapDAO());
-            
-            return dump;
+        private HeapDumpListener(Runnable heapDumpCompleteAction) {
+            this.heapDumpCompleteAction = heapDumpCompleteAction;
+        }
+
+        @Override
+        public void fireComplete(Request request, Response response) {
+            heapDumpCompleteAction.run();
+        }
         
-        } catch (IOException e) {
-            
-            log.log(Level.SEVERE, "Unexpected IO problem while writing heap dump", e);
-            return null;
-        }
     }
 
-    private File dumpHeap(VmRef reference) throws IOException {
-        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.
-        dumpHeapUsingJMX(reference, tempFileName);
-        return tempFile;
-    }
-    
-    private void dumpHeapUsingJMX(VmRef reference, String filename) {
+    public void execute(VmRef reference, Runnable heapDumpCompleteAction) {
 
-        try {
-            VirtualMachine vm = VirtualMachine.attach(reference.getStringID());
-            Properties props = vm.getAgentProperties();
-            String connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
-            if (connectorAddress == null) {
-               props = vm.getSystemProperties();
-               String home = props.getProperty("java.home");
-               String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
-               vm.loadAgent(agent);
-               props = vm.getAgentProperties();
-               connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
-            }
-            JMXServiceURL url = new JMXServiceURL(connectorAddress);
-            
-            try (JMXConnector conn = JMXConnectorFactory.connect(url)) {
-                MBeanServerConnection mbsc = conn.getMBeanServerConnection();                   
-                mbsc.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"), "dumpHeap",
-                        new Object[] {filename, Boolean.TRUE},
-                        new String[] {String.class.getName(), boolean.class.getName()});                
-            }
-        } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException |
-                 InstanceNotFoundException | MalformedObjectNameException | MBeanException | ReflectionException e) {
-            log.log(Level.WARNING, "Heap dump using JMX failed, trying jmap", e);
-            dumpHeapUsingJMap(reference, filename);
-        }
+        DAOFactory df = ApplicationContext.getInstance().getDAOFactory();
+        HostRef targetHostRef = reference.getAgent();
+        String address = df.getStorage().getConfigListenAddress(targetHostRef);
+        
+        String [] host = address.split(":");
+        InetSocketAddress target = new InetSocketAddress(host[0], Integer.parseInt(host[1]));
+        Request req = new Request(RequestType.RESPONSE_EXPECTED, target);
+        req.setReceiver(RECEIVER_CLASS_NAME);
+        req.setParameter(VM_ID_PARAM, reference.getIdString());
+        req.addListener(new HeapDumpListener(heapDumpCompleteAction));
+
+        RequestQueue queue = OSGIUtils.getInstance().getService(RequestQueue.class);
+        queue.putRequest(req);
+
     }
 
-    private void dumpHeapUsingJMap(VmRef reference, String filename) {
-        try {
-            Process proc = Runtime.getRuntime().exec(
-                    new String[] { "jmap", "-dump:format=b,file=" + filename, reference.getIdString() });
-            proc.waitFor();
-            log.info("Heap dump written to: " + filename);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    private ObjectHistogram loadHistogram(String heapDumpFilename) throws IOException {
-        HistogramLoader histoLoader = new HistogramLoader(heapDumpFilename);
-        return histoLoader.load();
-    }
-
-    private HeapInfo saveHeapDumpInfo(VmRef reference, File tempFile, ObjectHistogram histogram) throws FileNotFoundException {
-    
-        HeapDAO heapDAO = ApplicationContext.getInstance().getDAOFactory().getHeapDAO();
-        HeapInfo heapInfo = new HeapInfo(reference, System.currentTimeMillis());
-        heapInfo.setHeapDumpId(reference.getStringID() + "-" + reference.getAgent().getAgentId() + "-" + heapInfo.getTimestamp());
-        
-        heapDAO.putHeapInfo(heapInfo, new FileInputStream(tempFile), histogram);
-        
-        return heapInfo;
-    }
 }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java	Fri Aug 31 11:22:52 2012 +0200
@@ -37,8 +37,10 @@
 package com.redhat.thermostat.client.heap.swing;
 
 import java.awt.Component;
+import java.awt.EventQueue;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.List;
 
 import javax.swing.BoxLayout;
 import javax.swing.JPanel;
@@ -75,19 +77,7 @@
                 
                 stats.disableHeapDumperControl();
                 
-                SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
-                    @Override
-                    protected Void doInBackground() throws Exception {
-                        heapDumperNotifier.fireAction(HeadDumperAction.DUMP_REQUESTED);
-                        return null;
-                    }
-                    
-                    @Override
-                    protected void done() {
-                        stats.enableHeapDumperControl();
-                    }
-                };
-                worker.execute();
+                heapDumperNotifier.fireAction(HeapDumperAction.DUMP_REQUESTED);
             }
         });
         
@@ -95,7 +85,7 @@
             @Override
             public void valueChanged(ListSelectionEvent arg0) {
                 HeapDump dump = stats.getSelectedHeapDump();
-                heapDumperNotifier.fireAction(HeadDumperAction.ANALYSE, dump);
+                heapDumperNotifier.fireAction(HeapDumperAction.ANALYSE, dump);
             }
         });
         
@@ -194,4 +184,24 @@
     public Component getUiComponent() {
         return visiblePane;
     }
+
+    @Override
+    public void notifyHeapDumpComplete() {
+        EventQueue.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                stats.enableHeapDumperControl();
+            }
+        });
+    }
+
+    @Override
+    public void updateHeapDumpList(final List<HeapDump> heapDumps) {
+        EventQueue.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                stats.updateHeapDumpList(heapDumps);
+            }
+        });
+    }
 }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java	Fri Aug 31 11:22:52 2012 +0200
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.client.heap.swing;
 
 import java.awt.event.ActionListener;
+import java.util.List;
 
 import javax.swing.BoxLayout;
 import javax.swing.DefaultListModel;
@@ -99,9 +100,11 @@
         max.setHorizontalAlignment(SwingConstants.RIGHT);
         
         heapDumpButton = new JButton("Heap Dump");
-        
+        heapDumpButton.setName("heapDumpButton");
+
         JScrollPane dumpListScrollPane = new JScrollPane();
         dumpList = new JList<>();
+        dumpList.setName("heapDumpList");
         listModel = new DefaultListModel<>();
         dumpList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
         dumpList.setModel(listModel);
@@ -145,9 +148,6 @@
         setLayout(groupLayout);
 
         dumpListScrollPane.setViewportView(dumpList);
-
-        // initially invisible
-        dumpList.setVisible(false);
     }
     
     void setChartPanel(JPanel panel) {
@@ -186,19 +186,25 @@
     public void addDump(HeapDump dump) {
         
         listModel.addElement(dump);
-        if (!dumpList.isVisible()) {
-            dumpList.setVisible(true);
-        }
     }
 
     public void clearDumpList() {
         listModel.clear();
-        if (dumpList.isVisible()) {
-            dumpList.setVisible(false);
-        }
     }
     
     public HeapDump getSelectedHeapDump() {
         return dumpList.getSelectedValue();
     }
+
+    public void updateHeapDumpList(List<HeapDump> heapDumps) {
+        int numItemsBefore = listModel.getSize();
+        for (HeapDump heapDump : heapDumps) {
+            if (! listModel.contains(heapDump)) {
+                listModel.addElement(heapDump);
+            }
+        }
+        if (numItemsBefore == 0 && listModel.getSize() > 0) {
+            dumpList.repaint();
+        }
+    }
 }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.heap;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -46,14 +47,15 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
-
-import javax.swing.JComponent;
+import java.util.List;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import com.redhat.thermostat.client.heap.HeapView.HeapDumperAction;
+import com.redhat.thermostat.client.heap.cli.HeapDumperCommand;
 import com.redhat.thermostat.client.osgi.service.ApplicationCache;
 import com.redhat.thermostat.client.osgi.service.ApplicationService;
 import com.redhat.thermostat.common.ActionEvent;
@@ -67,6 +69,7 @@
 import com.redhat.thermostat.common.dao.HeapDAO;
 import com.redhat.thermostat.common.dao.MongoDAOFactory;
 import com.redhat.thermostat.common.dao.VmClassStatDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.heap.HeapDump;
 import com.redhat.thermostat.common.model.HeapInfo;
@@ -74,17 +77,20 @@
 public class HeapDumpControllerTest {
 
     private ActionListener<HeapView.Action> actionListener;
-    private ActionListener<HeapView.HeadDumperAction> heapDumperListener;
+    private ActionListener<HeapView.HeapDumperAction> heapDumperListener;
     
     private Timer timer;
     
-    private HeapDAO heapDao;
+    private HeapDAO heapDAO;
+    private VmMemoryStatDAO vmDAO;
     private HeapView view;
     private HeapDumpDetailsView detailsView;
     
     private HeapDumpController controller;
+    private HeapDumperCommand heapDumperCommand;
     private ApplicationService appService;
-    
+    private ArgumentCaptor<Runnable> timerActionCaptor;
+
     @Before
     public void setUp() {
         ApplicationContextUtil.resetApplicationContext();
@@ -94,9 +100,11 @@
         DAOFactory daoFactory = mock(MongoDAOFactory.class);
         when(daoFactory.getVmClassStatsDAO()).thenReturn(vmClassStatDAO);
         
-        heapDao = mock(HeapDAO.class);
-        when(daoFactory.getHeapDAO()).thenReturn(heapDao);
-        
+        heapDAO = mock(HeapDAO.class);
+        when(daoFactory.getHeapDAO()).thenReturn(heapDAO);
+        vmDAO = mock(VmMemoryStatDAO.class);
+        when(daoFactory.getVmMemoryStatDAO()).thenReturn(vmDAO);
+
         ApplicationContext.getInstance().setDAOFactory(daoFactory);
 
         setUpTimers();
@@ -105,7 +113,7 @@
     
     private void setUpTimers() {
         timer = mock(Timer.class);
-        ArgumentCaptor<Runnable> timerActionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        timerActionCaptor = ArgumentCaptor.forClass(Runnable.class);
         doNothing().when(timer).setAction(timerActionCaptor.capture());
 
         TimerFactory timerFactory = mock(TimerFactory.class);
@@ -148,10 +156,19 @@
         ApplicationCache cache = mock(ApplicationCache.class);
         appService = mock(ApplicationService.class);
         when(appService.getApplicationCache()).thenReturn(cache);
-        VmRef ref = mock(VmRef.class); 
-        controller = new HeapDumpController(ref, appService);
+        VmRef ref = mock(VmRef.class);
+        heapDumperCommand = mock(HeapDumperCommand.class);
+        controller = new HeapDumpController(ref, appService, heapDumperCommand);
     }
     
+    @After
+    public void tearDown() {
+    	controller = null;
+    	vmDAO = null;
+    	heapDAO = null;
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
     @Test
     public void testTimerStartOnViewVisible() {
         
@@ -173,7 +190,7 @@
     @Test
     public void testNotAddHeapDumpsAtStartupWhenNoDumps() {
                 
-        when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(new ArrayList<HeapInfo>());
+        when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(new ArrayList<HeapInfo>());
         
         createController();
         
@@ -188,8 +205,8 @@
         infos.add(info1);
         infos.add(info2);
         
-        when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
-        
+        when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
+
         createController();
         
         verify(view, times(2)).addHeapDump(any(HeapDump.class));
@@ -208,7 +225,7 @@
         infos.add(info1);
         infos.add(info2);
         
-        when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
+        when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
         
         ApplicationCache cache = mock(ApplicationCache.class);
         when(cache.getAttribute(any(VmRef.class))).thenReturn(dump);
@@ -224,14 +241,14 @@
     
     @Test
     public void testNotOpenDumpCalledWhenNoPreviousDump() {
-                
+
         HeapInfo info1 = mock(HeapInfo.class);        
         HeapInfo info2 = mock(HeapInfo.class);
         Collection<HeapInfo> infos = new ArrayList<HeapInfo>();
         infos.add(info1);
         infos.add(info2);
         
-        when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
+        when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
         
         ApplicationCache cache = mock(ApplicationCache.class);
         when(cache.getAttribute(any(VmRef.class))).thenReturn(null);
@@ -243,9 +260,41 @@
         
         verify(view, times(0)).openDumpView();
     }
+
+    @Test
+    public void testRequestHeapDump() {
+
+        setUpListeners();
+
+        heapDumperListener.actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED));
+
+        ArgumentCaptor<Runnable> heapDumpCompleteAction = ArgumentCaptor.forClass(Runnable.class);
+        verify(heapDumperCommand).execute(any(VmRef.class), heapDumpCompleteAction.capture());
+        heapDumpCompleteAction.getValue().run();
+        verify(view).notifyHeapDumpComplete();
+
+    }
+ 
+    @SuppressWarnings("unchecked")
+	@Test
+    public void testTimerChecksForNewHeapDumps() {
+
+        HeapInfo info1 = mock(HeapInfo.class);
+        HeapInfo info2 = mock(HeapInfo.class);
+        Collection<HeapInfo> infos = new ArrayList<HeapInfo>();
+        infos.add(info1);
+        infos.add(info2);
         
-    @After
-    public void tearDown() {
-        ApplicationContextUtil.resetApplicationContext();
+        when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos);
+
+        createController();
+
+        timerActionCaptor.getValue().run();
+
+        @SuppressWarnings("rawtypes")
+		ArgumentCaptor<List> heapDumps = ArgumentCaptor.forClass(List.class);
+        verify(view).updateHeapDumpList(heapDumps.capture());
+        assertTrue(heapDumps.getValue().contains(new HeapDump(info1, heapDAO)));
+        assertTrue(heapDumps.getValue().contains(new HeapDump(info2, heapDAO)));
     }
 }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -39,14 +39,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.isA;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
 
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
-import com.redhat.thermostat.client.heap.cli.DumpHeapCommand;
-import com.redhat.thermostat.client.heap.cli.HeapDumperCommand;
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.SimpleArguments;
@@ -66,6 +68,16 @@
     @Test
     public void verifyAcuallyCallsWorker() throws CommandException {
         HeapDumperCommand impl = mock(HeapDumperCommand.class);
+        final ArgumentCaptor<Runnable> arg = ArgumentCaptor.forClass(Runnable.class);
+        doAnswer(new Answer<Void>() {
+
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                arg.getValue().run();
+                return null;
+            }
+        }).when(impl).execute(any(VmRef.class), arg.capture());
+
         DumpHeapCommand command = new DumpHeapCommand(impl);
 
         TestCommandContextFactory factory = new TestCommandContextFactory();
@@ -76,7 +88,7 @@
 
         command.run(factory.createContext(args));
 
-        verify(impl).execute(isA(VmRef.class));
+        verify(impl).execute(isA(VmRef.class), any(Runnable.class));
         assertEquals("Done\n", factory.getOutput());
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommandTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,137 @@
+/*
+ * 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.cli;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.net.InetSocketAddress;
+import java.util.Collection;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+
+public class HeapDumperCommandTest {
+
+    private HeapDumperCommand cmd;
+    private VmRef vmRef;
+    private RequestQueue reqQueue;
+    private Runnable heapDumpCompleteAction;
+
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+
+        reqQueue = mock(RequestQueue.class);
+        OSGIUtils osgiUtils = mock(OSGIUtils.class);
+        when(osgiUtils.getService(RequestQueue.class)).thenReturn(reqQueue);
+        OSGIUtils.setInstance(osgiUtils);
+
+        HostRef host = mock(HostRef.class);
+        Storage storage = mock(Storage.class);
+        when(storage.getConfigListenAddress(host)).thenReturn("test:123");
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getStorage()).thenReturn(storage);
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        cmd = new HeapDumperCommand();
+        vmRef = mock(VmRef.class);
+        when(vmRef.getIdString()).thenReturn("123");
+        when(vmRef.getAgent()).thenReturn(host);
+        heapDumpCompleteAction = mock(Runnable.class);
+
+    }
+
+    @After
+    public void tearDown() {
+        heapDumpCompleteAction = null;
+        vmRef = null;
+        cmd = null;
+        reqQueue = null;
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @Test
+	public void testExecute() {
+
+        cmd.execute(vmRef, heapDumpCompleteAction);
+
+		ArgumentCaptor<Request> reqArg = ArgumentCaptor.forClass(Request.class);
+		verify(reqQueue).putRequest(reqArg.capture());
+		Request req = reqArg.getValue();
+		assertEquals("com.redhat.thermostat.agent.heapdumper.internal.HeapDumpReceiver", req.getReceiver());
+		verifyClassExists(req.getReceiver());
+		assertEquals(RequestType.RESPONSE_EXPECTED, req.getType());
+		assertEquals("123", req.getParameter("vmId"));
+		assertEquals(new InetSocketAddress("test", 123), req.getTarget());
+
+		Collection<RequestResponseListener> ls = req.getListeners();
+		for (RequestResponseListener l : ls) {
+		    l.fireComplete(req, new Response(ResponseType.OK));
+		}
+		verify(heapDumpCompleteAction).run();
+    }
+
+    private void verifyClassExists(String receiver) {
+        try {
+            Class.forName(receiver);
+        } catch (ClassNotFoundException e) {
+            throw new AssertionFailedError();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/swing/HeapSwingViewTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -0,0 +1,115 @@
+package com.redhat.thermostat.client.heap.swing;
+
+import static org.junit.Assert.fail;
+
+import java.awt.Container;
+import java.awt.EventQueue;
+import java.util.Arrays;
+import java.util.List;
+
+import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+
+import org.fest.swing.annotation.GUITest;
+import org.fest.swing.annotation.RunsInEDT;
+import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.fixture.Containers;
+import org.fest.swing.fixture.DialogFixture;
+import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JButtonFixture;
+import org.fest.swing.fixture.JListFixture;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.redhat.thermostat.client.heap.HeapView.HeapDumperAction;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.heap.HeapDump;
+
+import static org.mockito.Mockito.*
+;
+
+@RunWith(CacioFESTRunner.class)
+public class HeapSwingViewTest {
+
+    private HeapSwingView view;
+
+    private FrameFixture frame;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        GuiActionRunner.execute(new GuiTask() {
+            
+            @Override
+            protected void executeInEDT() throws Throwable {
+                view = new HeapSwingView();
+            }
+        });
+        frame = Containers.showInFrame((Container) view.getUiComponent());
+    }
+
+    @After
+    public void tearDown() {
+        frame.cleanUp();
+        frame = null;
+        view = null;
+    }
+
+    @Test
+    @GUITest
+    public void testActivateHeapDump() {
+        @SuppressWarnings("unchecked")
+        ActionListener<HeapDumperAction> l = mock(ActionListener.class);
+        view.addDumperListener(l);
+        JButtonFixture heapDumpButton = frame.button("heapDumpButton");
+        heapDumpButton.click();
+        verify(l).actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED));
+    }
+
+    @Test
+    @GUITest
+    public void testNotifyHeapDumpComplete() {
+        final JButtonFixture heapDumpButton = frame.button("heapDumpButton");
+        GuiActionRunner.execute(new GuiTask() {
+            
+            @Override
+            protected void executeInEDT() throws Throwable {
+                heapDumpButton.component().setEnabled(false);
+            }
+        });
+
+        view.notifyHeapDumpComplete();
+        frame.robot.waitForIdle();
+
+        heapDumpButton.requireEnabled();
+    }
+
+    @Test
+    @GUITest
+    public void testUpdateHeapDumpList() {
+        JListFixture heapDumpList = frame.list("heapDumpList");
+        heapDumpList.requireItemCount(0);
+
+        HeapDump heapDump = mock(HeapDump.class);
+        List<HeapDump> heapDumps = Arrays.asList(heapDump);
+
+        view.updateHeapDumpList(heapDumps);
+        frame.robot.waitForIdle();
+        heapDumpList.requireItemCount(1);
+
+        view.updateHeapDumpList(heapDumps);
+        frame.robot.waitForIdle();
+        heapDumpList.requireItemCount(1);
+
+    }
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAO.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAO.java	Fri Aug 31 11:22:52 2012 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.common.dao;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
 
@@ -52,7 +54,7 @@
 
     public static final Category heapInfoCategory = new Category("vm-heap-info", Key.ID, Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, heapDumpIdKey, histogramIdKey);
 
-    void putHeapInfo(HeapInfo heapInfo, InputStream heapDump, ObjectHistogram histogramData);
+    void putHeapInfo(HeapInfo heapInfo, File heapDumpFile, ObjectHistogram histogramData) throws IOException;
 
     Collection<HeapInfo> getAllHeapInfo(VmRef vm);
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java	Fri Aug 31 11:22:52 2012 +0200
@@ -38,6 +38,8 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectInputStream;
@@ -47,8 +49,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.bson.types.ObjectId;
-
 import com.redhat.thermostat.common.heap.HeapDump;
 import com.redhat.thermostat.common.heap.ObjectHistogram;
 import com.redhat.thermostat.common.model.HeapInfo;
@@ -69,14 +69,13 @@
     }
 
     @Override
-    public void putHeapInfo(HeapInfo heapInfo, InputStream heapDumpData, ObjectHistogram histogramData) {
-        VmRef vm = heapInfo.getVm();
+    public void putHeapInfo(HeapInfo heapInfo, File heapDumpData, ObjectHistogram histogramData) throws IOException {
+        int vmId = heapInfo.getVmId();
         Chunk chunk = new Chunk(heapInfoCategory, false);
 
-        chunk.put(Key.AGENT_ID, vm.getAgent().getStringID());
-        chunk.put(Key.VM_ID, vm.getId());
+        chunk.put(Key.VM_ID, vmId);
         chunk.put(Key.TIMESTAMP, heapInfo.getTimestamp());
-        String id = vm.getAgent().getStringID() + "-" + vm.getId() + "-" + heapInfo.getTimestamp();
+        String id = heapInfo.getHeapId();
         String heapDumpId = "heapdump-" + id;
         String histogramId = "histogram-" + id;
         if (heapDumpData != null) {
@@ -89,7 +88,7 @@
         }
         storage.putChunk(chunk);
         if (heapDumpData != null) {
-            storage.saveFile(heapDumpId, heapDumpData);
+            storage.saveFile(heapDumpId, new FileInputStream(heapDumpData));
         }
         if (histogramData != null) {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -123,7 +122,8 @@
     }
 
     private HeapInfo convertChunkToHeapInfo(VmRef vm, Chunk chunk) {
-        HeapInfo info = new HeapInfo(vm, chunk.get(Key.TIMESTAMP));
+        int vmId = chunk.get(Key.VM_ID);
+        HeapInfo info = new HeapInfo(vmId, chunk.get(Key.TIMESTAMP));
         info.setHeapId(chunk.get(Key.ID));
         info.setHeapDumpId(chunk.get(HeapDAO.heapDumpIdKey));
         info.setHistogramId(chunk.get(HeapDAO.histogramIdKey));
--- a/common/core/src/main/java/com/redhat/thermostat/common/heap/HeapDump.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/heap/HeapDump.java	Fri Aug 31 11:22:52 2012 +0200
@@ -96,10 +96,6 @@
         this.heapDAO = heapDAO;
     }
 
-    public String getName() {
-        return heapInfo.getVm().getName();
-    }
-
     public long getTimestamp() {
         return heapInfo.getTimestamp();
     }
@@ -223,4 +219,11 @@
         return snapshot.findThing(id);
     }
 
+    public boolean equals(Object o) {
+        return o instanceof HeapDump && ((HeapDump) o).heapInfo.equals(heapInfo);
+    }
+
+    public int hashCode() {
+        return heapInfo.hashCode();
+    }
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/heap/HistogramLoader.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/heap/HistogramLoader.java	Fri Aug 31 11:22:52 2012 +0200
@@ -46,18 +46,12 @@
 
 public class HistogramLoader {
 
-    private String filename;
-
-    public HistogramLoader(String filename) {
-        this.filename = filename;
-    }
-
-    public ObjectHistogram load() throws IOException {
-        Snapshot snapshot = loadHeapdump();
+    public ObjectHistogram load(String filename) throws IOException {
+        Snapshot snapshot = loadHeapdump(filename);
         return computeHistogram(snapshot);
     }
 
-    private Snapshot loadHeapdump() throws IOException {
+    private Snapshot loadHeapdump(String filename) throws IOException {
         File heapdump = new File(filename);
         Snapshot snapshot = Reader.readFile(heapdump.getAbsolutePath(), true, 0);
         snapshot.resolve(true);
--- a/common/core/src/main/java/com/redhat/thermostat/common/model/HeapInfo.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/model/HeapInfo.java	Fri Aug 31 11:22:52 2012 +0200
@@ -38,23 +38,21 @@
 
 import java.util.Objects;
 
-import com.redhat.thermostat.common.dao.VmRef;
-
 public class HeapInfo {
 
-    private VmRef vm;
+    private int vmId;
     private long timestamp;
     private String heapId;
     private String heapDumpId;
     private String histogramId;
 
-    public HeapInfo(VmRef vm, long timestamp) {
-        this.vm = vm;
+    public HeapInfo(int vmId, long timestamp) {
+        this.vmId = vmId;
         this.timestamp = timestamp;
     }
 
-    public VmRef getVm() {
-        return vm;
+    public int getVmId() {
+        return vmId;
     }
 
     public long getTimestamp() {
@@ -83,13 +81,13 @@
             return false;
         }
         HeapInfo other = (HeapInfo) o;
-        return Objects.equals(vm, other.vm) && Objects.equals(heapDumpId, other.heapDumpId)
+        return vmId == other.vmId && Objects.equals(heapDumpId, other.heapDumpId)
                && Objects.equals(histogramId, other.histogramId) && timestamp == other.timestamp;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(vm, heapDumpId, histogramId, timestamp);
+        return Objects.hash(vmId, heapDumpId, histogramId, timestamp);
     }
 
     public String getHistogramId() {
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java	Fri Aug 31 11:22:52 2012 +0200
@@ -44,11 +44,17 @@
 
 public class OSGIUtils {
     
-    private final static OSGIUtils instance = new OSGIUtils();
+	// TODO: Maybe we should stick this singleton into an ApplicationContext?
+    private static OSGIUtils instance = new OSGIUtils();
     public static OSGIUtils getInstance() {
         return instance;
     }
-    
+
+    // This is only here to be used by test code.
+    public static void setInstance(OSGIUtils utils) {
+    	instance = utils;
+    }
+
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public <E extends Object> E getService(Class<E> clazz) {
         BundleContext ctx = FrameworkUtil.getBundle(getClass()).getBundleContext();
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -43,7 +43,6 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -51,6 +50,8 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectOutputStream;
@@ -78,7 +79,7 @@
     private HeapDAO dao;
     private Storage storage;
     private HeapInfo heapInfo;
-    private InputStream heapDumpData;
+    private File heapDumpData;
     private ObjectHistogram histogram;
     private InputStream histogramData;
 
@@ -87,11 +88,13 @@
         storage = mock(Storage.class);
         dao = new HeapDAOImpl(storage);
         
-        HostRef host = new HostRef("987", "test-host");
-        VmRef vm = new VmRef(host, 123, "test-vm");
-        heapInfo = new HeapInfo(vm, 12345);
+        heapInfo = new HeapInfo(123, 12345);
+        heapInfo.setHeapId("987-123-12345");
         byte[] data = new byte[] { 1, 2, 3 };
-        heapDumpData = new ByteArrayInputStream(data);
+        heapDumpData = File.createTempFile("test", "test");
+        FileOutputStream out = new FileOutputStream(heapDumpData);
+        out.write(data);
+        out.close();
         histogramData = createHistogramData();
 
         // Setup for reading data from DB.
@@ -118,12 +121,11 @@
         when(storage.findAll(findAllQuery)).thenReturn(cursor);
 
         // Setup for reading heapdump data.
-        when(storage.loadFile("test-heap")).thenReturn(heapDumpData);
+        when(storage.loadFile("test-heap")).thenReturn(new ByteArrayInputStream(data));
         when(storage.loadFile("test-histo")).thenReturn(histogramData);
 
         // Prepare queries for read-back of _id in putHeapInfo() tests.
         Chunk heap1query = new Chunk(HeapDAO.heapInfoCategory, false);
-        heap1query.put(Key.AGENT_ID, "987");
         heap1query.put(Key.VM_ID, 123);
         heap1query.put(Key.TIMESTAMP, 12345l);
         heap1query.put(HeapDAO.heapDumpIdKey, "heapdump-987-123-12345");
@@ -133,7 +135,6 @@
         when(storage.find(heap1query)).thenReturn(heap1);
 
         Chunk heap2query = new Chunk(HeapDAO.heapInfoCategory, false);
-        heap2query.put(Key.AGENT_ID, "987");
         heap2query.put(Key.VM_ID, 123);
         heap2query.put(Key.TIMESTAMP, 12345l);
         Chunk heap2 = new Chunk(HeapDAO.heapInfoCategory, false);
@@ -173,6 +174,7 @@
     public void tearDown() {
         histogramData = null;
         histogram = null;
+        heapDumpData.delete();
         heapDumpData = null;
         heapInfo = null;
         dao = null;
@@ -199,13 +201,18 @@
         dao.putHeapInfo(heapInfo, heapDumpData, histogram);
 
         Chunk expectedChunk = new Chunk(HeapDAO.heapInfoCategory, false);
-        expectedChunk.put(Key.AGENT_ID, "987");
         expectedChunk.put(Key.VM_ID, 123);
         expectedChunk.put(Key.TIMESTAMP, 12345L);
         expectedChunk.put(HeapDAO.heapDumpIdKey, "heapdump-987-123-12345");
         expectedChunk.put(HeapDAO.histogramIdKey, "histogram-987-123-12345");
         verify(storage).putChunk(expectedChunk);
-        verify(storage).saveFile(eq("heapdump-987-123-12345"), same(heapDumpData));
+        ArgumentCaptor<InputStream> data = ArgumentCaptor.forClass(InputStream.class);
+        verify(storage).saveFile(eq("heapdump-987-123-12345"), data.capture());
+        InputStream in = data.getValue();
+        assertEquals(1, in.read());
+        assertEquals(2, in.read());
+        assertEquals(3, in.read());
+        assertEquals(-1, in.read());
         assertEquals("id1", heapInfo.getHeapId());
         ArgumentCaptor<InputStream> histoStream = ArgumentCaptor.forClass(InputStream.class);
         verify(storage).saveFile(eq("histogram-987-123-12345"), histoStream.capture());
@@ -220,11 +227,10 @@
     }
 
     @Test
-    public void testPutHeapInfoWithoutDump() {
+    public void testPutHeapInfoWithoutDump() throws IOException {
         dao.putHeapInfo(heapInfo, null, null);
 
         Chunk expectedChunk = new Chunk(HeapDAO.heapInfoCategory, false);
-        expectedChunk.put(Key.AGENT_ID, "987");
         expectedChunk.put(Key.VM_ID, 123);
         expectedChunk.put(Key.TIMESTAMP, 12345L);
 
@@ -244,11 +250,11 @@
         VmRef vm = new VmRef(host, 234, "test-vm");
         Collection<HeapInfo> heapInfos = dao.getAllHeapInfo(vm);
         
-        HeapInfo info1 = new HeapInfo(vm, 12345);
+        HeapInfo info1 = new HeapInfo(234, 12345);
         info1.setHeapDumpId("test1");
         info1.setHistogramId("histotest1");
         
-        HeapInfo info2 = new HeapInfo(vm, 23456);
+        HeapInfo info2 = new HeapInfo(234, 23456);
         info2.setHeapDumpId("test2");
         info2.setHistogramId("histotest2");
         
--- a/common/core/src/test/java/com/redhat/thermostat/common/model/HeapInfoTest.java	Wed Aug 22 14:52:00 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/model/HeapInfoTest.java	Fri Aug 31 11:22:52 2012 +0200
@@ -49,18 +49,15 @@
 public class HeapInfoTest {
 
     private HeapInfo heapInfo;
-    private VmRef vm;
 
     @Before
     public void setUp() {
-        HostRef hostRef = new HostRef("321", "test-host");
-        vm = new VmRef(hostRef, 123, "test-vm");
-        heapInfo = new HeapInfo(vm, 12345);
+        heapInfo = new HeapInfo(321, 12345);
     }
 
     @Test
     public void testProperties() {
-        assertSame(vm, heapInfo.getVm());
+        assertEquals(321, heapInfo.getVmId());
         assertEquals(12345, heapInfo.getTimestamp());
     }
 
--- a/distribution/config/bundles.properties	Wed Aug 22 14:52:00 2012 +0200
+++ b/distribution/config/bundles.properties	Fri Aug 31 11:22:52 2012 +0200
@@ -34,7 +34,8 @@
         thermostat-common-core-@project.version@.jar, \
         thermostat-agent-cli-@project.version@.jar, \
         thermostat-common-command-@project.version@.jar, \
-        thermostat-agent-command-@project.version@.jar
+        thermostat-agent-command-@project.version@.jar, \
+        thermostat-agent-heapdumper-@project.version@.jar
 
 storage = thermostat-agent-core-@project.version@.jar, \
           thermostat-osgi-process-handler-@project.version@.jar, \
--- a/distribution/pom.xml	Wed Aug 22 14:52:00 2012 +0200
+++ b/distribution/pom.xml	Fri Aug 31 11:22:52 2012 +0200
@@ -287,6 +287,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-agent-heapdumper</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-common-core</artifactId>
       <version>${project.version}</version>
     </dependency>