changeset 925:259c60624174

Update all Backends to use new API for Vm start/stop Reviewed-by: ebaron Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-January/005252.html PR 1258
author Omair Majid <omajid@redhat.com>
date Mon, 21 Jan 2013 13:48:50 -0500
parents c739844c6d68
children 4f3b798807f3
files agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java common/core/src/main/java/com/redhat/thermostat/common/Pair.java common/core/src/test/java/com/redhat/thermostat/common/PairTest.java vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/Activator.java vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackend.java vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListener.java vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/ActivatorTest.java vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackendTest.java vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListenerTest.java vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/Activator.java vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackend.java vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListener.java vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/ActivatorTest.java vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackendTest.java vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListenerTest.java vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/Activator.java vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackend.java vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListener.java vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListener.java vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/ActivatorTest.java vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackendTest.java vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryDataExtractorTest.java vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListenerTest.java vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListenerTest.java
diffstat 26 files changed, 868 insertions(+), 979 deletions(-) [+]
line wrap: on
line diff
--- a/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Mon Jan 21 13:48:50 2013 -0500
@@ -181,7 +181,8 @@
      * This method is called by the framework when the {@link Backend} is
      * registered.
      *
-     * @return true on success, false if there was an error
+     * @return {@code true} if the backend was activated successfully or
+     * already active. {@code false} if there was an error
      */
     public abstract boolean activate();
 
@@ -196,7 +197,8 @@
      * This method is called by the framework when the {@link Backend} is
      * deregistered.
      *
-     * @return true on success
+     * @return {@code true} if the backend was successfully deactivated or
+     * already inactive. {@code false} if the backend is still active.
      */
     public abstract boolean deactivate();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Pair.java	Mon Jan 21 13:48:50 2013 -0500
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 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.common;
+
+import java.util.Objects;
+
+/**
+ * A container that hold two values.
+ * <p>
+ * The values may be related or unrelated.
+ * <p>
+ * For most predictable results, the params should be immutable. If the value of
+ * {@link #hashCode()} is relevant, the two values must provide sane
+ * implementations of hashCode too.
+ *
+ * @param <F> the type of the first value
+ * @param <S> the type of the second value
+ */
+public class Pair<F, S> {
+
+    private final F first;
+    private final S second;
+
+    public Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    public F getFirst() {
+        return first;
+    }
+
+    public S getSecond() {
+        return second;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Pair)) {
+            return false;
+        }
+        Pair<?,?> other = (Pair<?,?>) obj;
+        return Objects.equals(first, other.first) && Objects.equals(second, other.second);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(first, second);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/PairTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 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.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class PairTest {
+
+    @Test
+    public void testCreateAndExtractParts() {
+        Pair<String, String> pair = new Pair<>("1", "2");
+
+        assertEquals("1", pair.getFirst());
+        assertEquals("2", pair.getSecond());
+    }
+
+    @Test
+    public void testEquals() {
+        Pair<String, String> pair = new Pair<>("1", "2");
+        Pair<String, String> samePair = new Pair<>("1", "2");
+
+        Pair<String, String> differentPair = new Pair<>("2", "1");
+
+        assertFalse(pair.equals(null));
+        assertTrue(pair.equals(samePair));
+        assertTrue(samePair.equals(pair));
+        assertFalse(pair.equals(differentPair));
+        assertFalse(pair.equals(new Object()));
+    }
+
+    @Test
+    public void testHashCode() {
+        Pair<String, String> pair = new Pair<>("1", "2");
+        Pair<String, String> samePair = new Pair<>("1", "2");
+        Pair<String, String> differentPair = new Pair<>("lol", "code");
+
+        assertFalse(pair.hashCode() == differentPair.hashCode());
+        assertTrue(pair.hashCode() == samePair.hashCode());
+    }
+}
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/Activator.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/Activator.java	Mon Jan 21 13:48:50 2013 -0500
@@ -42,6 +42,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.common.MultipleServiceTracker;
@@ -57,6 +58,9 @@
     
     @Override
     public void start(final BundleContext context) throws Exception {
+
+        final VmStatusListenerRegistrar registrar = new VmStatusListenerRegistrar(context);
+
         Class<?>[] deps = new Class<?>[] {
                 BackendService.class,
                 VmClassStatDAO.class
@@ -67,7 +71,7 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmClassStatDAO vmClassStatDao = (VmClassStatDAO) services.get(VmClassStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmClassStatBackend(vmClassStatDao, version);
+                backend = new VmClassStatBackend(vmClassStatDao, version, registrar);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackend.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackend.java	Mon Jan 21 13:48:50 2013 -0500
@@ -37,42 +37,53 @@
 package com.redhat.thermostat.vm.classstat.agent.internal;
 
 import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import sun.jvmstat.monitor.HostIdentifier;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.VmIdentifier;
+import sun.jvmstat.monitor.event.VmListener;
 
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
+import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 
-public class VmClassStatBackend extends Backend {
+public class VmClassStatBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmClassStatBackend.class);
 
-    private VmClassStatDAO vmClassStats;
-    private HostIdentifier hostId;
+    private final VmClassStatDAO vmClassStats;
+    private final VmStatusListenerRegistrar registrar;
+
+    private final Map<Integer, Pair<MonitoredVm, ? extends VmListener>> pidToVmAndListener = new HashMap<>();
+
     private MonitoredHost host;
-    private VmClassStatHostListener hostListener;
+
     private boolean started;
 
-    public VmClassStatBackend(VmClassStatDAO vmClassStatDAO, Version version) {
+    public VmClassStatBackend(VmClassStatDAO vmClassStatDAO, Version version, VmStatusListenerRegistrar registrar) {
         super(new BackendID("VM Classes Backend", VmClassStatBackend.class.getName()));
         this.vmClassStats = vmClassStatDAO;
+        this.registrar = registrar;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers class loading statistics about a JVM");
         setConfigurationValue(BackendsProperties.VERSION.name(), version.getVersionNumber());
         
         try {
-            hostId = new HostIdentifier((String) null);
+            HostIdentifier hostId = new HostIdentifier((String) null);
             host = MonitoredHost.getMonitoredHost(hostId);
-            hostListener = new VmClassStatHostListener(vmClassStats, attachToNewProcessByDefault());
         } catch (MonitorException me) {
             LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
         } catch (URISyntaxException use) {
@@ -80,29 +91,24 @@
         }
     }
 
+    /*
+     * Methods from Backend
+     */
+
     @Override
     public boolean activate() {
         if (!started && host != null) {
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "Failed to add host listener", me);
-            }
-
+            registrar.register(this);
+            started = true;
         }
         return started;
     }
 
     @Override
     public boolean deactivate() {
-        if (started && host != null) {
-            try {
-                host.removeHostListener(hostListener);
-                started = false;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.INFO, "Failed to remove host listener");
-            }
+        if (started) {
+            registrar.unregister(this);
+            started = false;
         }
         return !started;
     }
@@ -128,10 +134,69 @@
     }
 
     /*
+     *  Methods from VmStatusListener
+     */
+
+    @Override
+    public void vmStatusChanged(Status newStatus, int pid) {
+        switch (newStatus) {
+        case VM_STARTED:
+            /* fall-through */
+        case VM_ACTIVE:
+            vmStarted(pid);
+            break;
+        case VM_STOPPED:
+            vmStopped(pid);
+            break;
+        }
+    }
+
+    private void vmStarted(int pid) {
+        if (attachToNewProcessByDefault()) {
+            try {
+                MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(new VmIdentifier(String.valueOf(pid))));
+                VmClassStatVmListener listener = new VmClassStatVmListener(vmClassStats, pid);
+                vm.addVmListener(listener);
+
+                pidToVmAndListener.put(pid, new Pair<>(vm, listener));
+                LOGGER.finer("Attached VmListener for VM: " + pid);
+            } catch (MonitorException | URISyntaxException e) {
+                LOGGER.log(Level.WARNING, "Could not attach to new vm " + pid, e);
+            }
+        } else {
+            LOGGER.log(Level.FINE, "skipping new vm " + pid);
+        }
+    }
+
+    private void vmStopped(Integer pid) {
+        Pair<MonitoredVm, ? extends VmListener> data = pidToVmAndListener.remove(pid);
+        // if there is no data, we must never have attached to the vm. Nothing to do.
+        if (data == null) {
+            return;
+        }
+
+        MonitoredVm vm = data.getFirst();
+        VmListener listener = data.getSecond();
+        try {
+            vm.removeVmListener(listener);
+        } catch (MonitorException e) {
+            LOGGER.log(Level.WARNING, "can't remove vm listener", e);
+        }
+        vm.detach();
+    }
+
+    /*
      * For testing purposes only.
      */
     void setHost(MonitoredHost host) {
         this.host = host;
     }
-    
+
+    /*
+     * For testing purposes only.
+     */
+    Map<Integer, Pair<MonitoredVm, ? extends VmListener>> getPidToDataMap() {
+        return pidToVmAndListener;
+    }
+
 }
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListener.java	Mon Jan 21 12:17:46 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.classstat.agent.internal;
-
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.MonitoredVm;
-import sun.jvmstat.monitor.VmIdentifier;
-import sun.jvmstat.monitor.event.HostEvent;
-import sun.jvmstat.monitor.event.HostListener;
-import sun.jvmstat.monitor.event.VmListener;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
-
-public class VmClassStatHostListener implements HostListener {
-
-    private static final Logger logger = LoggingUtils.getLogger(VmClassStatHostListener.class);
-
-    private boolean attachNew;
-
-    private final VmClassStatDAO vmClassStatDAO;
-
-    private Map<Integer, MonitoredVm> monitoredVms  = new HashMap<>();
-    private Map<MonitoredVm, VmClassStatVmListener> registeredListeners  = new ConcurrentHashMap<>();
-
-    VmClassStatHostListener(VmClassStatDAO vmClassStatDAO, boolean attachNew) {
-        this.vmClassStatDAO = vmClassStatDAO;
-        this.attachNew = attachNew;        
-    }
-
-    void removeAllListeners() {
-        for (MonitoredVm vm : monitoredVms.values()) {
-            VmListener listener = registeredListeners.get(vm);
-            try {
-                if (listener != null) {
-                    vm.removeVmListener(listener);
-                }
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "can't remove vm listener", e);
-            }
-        }
-    }
-    
-    @Override
-    public void disconnected(HostEvent event) {
-        logger.warning("Disconnected from host");
-    }
-
-    @SuppressWarnings("unchecked") // Unchecked casts to (Set<Integer>).
-    @Override
-    public void vmStatusChanged(VmStatusChangeEvent event) {
-        MonitoredHost host = event.getMonitoredHost();
-
-        for (Integer newVm : (Set<Integer>) event.getStarted()) {
-            try {
-                logger.fine("New vm: " + newVm);
-                sendNewVM(newVm, host);
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "error getting info for new vm" + newVm, e);
-            } catch (URISyntaxException e) {
-                logger.log(Level.WARNING, "error getting info for new vm" + newVm, e);
-            }
-        }
-
-        for (Integer stoppedVm : (Set<Integer>) event.getTerminated()) {
-            try {
-                logger.fine("stopped vm: " + stoppedVm);
-                sendStoppedVM(stoppedVm, host);
-            } catch (URISyntaxException e) {
-                logger.log(Level.WARNING, "error getting info for stopped vm" + stoppedVm, e);
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "error getting info for stopped vm" + stoppedVm, e);
-            }
-        }
-    }
-
-    private void sendNewVM(Integer vmId, MonitoredHost host)
-            throws MonitorException, URISyntaxException {
-        MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(
-                new VmIdentifier(vmId.toString())));
-        if (vm != null) {
-            if (attachNew) {
-                VmClassStatVmListener listener = new VmClassStatVmListener(vmClassStatDAO, vmId);
-                vm.addVmListener(listener);
-                
-                registeredListeners.put(vm, listener);
-                logger.finer("Attached VmListener for VM: " + vmId);
-            } else {
-                logger.log(Level.FINE, "skipping new vm " + vmId);
-            }
-
-            monitoredVms.put(vmId, vm);
-        }
-    }
-
-    private void sendStoppedVM(Integer vmId, MonitoredHost host) throws URISyntaxException, MonitorException {
-        
-        VmIdentifier resolvedVmID = host.getHostIdentifier().resolve(new VmIdentifier(vmId.toString()));
-        if (resolvedVmID != null) {
-            MonitoredVm vm = monitoredVms.remove(vmId);
-            VmListener listener = registeredListeners.remove(vm);
-            try {
-                if (listener != null) {
-                    vm.removeVmListener(listener);
-                }
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "can't remove vm listener", e);
-            }
-            vm.detach();
-        }
-    }
-    
-    /*
-     * For testing purposes only.
-     */
-    Map<Integer, MonitoredVm> getMonitoredVms() {
-        return monitoredVms;
-    }
-    
-    /*
-     * For testing purposes only.
-     */
-    Map<MonitoredVm, VmClassStatVmListener> getRegisteredListeners() {
-        return registeredListeners;
-    }
-}
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/ActivatorTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/ActivatorTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -92,6 +92,9 @@
         VmClassStatBackend backend = activator.getBackend();
         assertNotNull(backend);
 
+        // something in core thermostat activates the backend; do it manually here
+        backend.activate();
+
         activator.stop(context);
         
         assertFalse(backend.isActive());
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackendTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackendTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -38,20 +38,30 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.net.URISyntaxException;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
+import sun.jvmstat.monitor.HostIdentifier;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.event.HostListener;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.VmIdentifier;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 
@@ -59,6 +69,9 @@
     
     private VmClassStatBackend backend;
     private MonitoredHost host;
+    private VmStatusListenerRegistrar registrar;
+    private HostIdentifier hostIdentifier;
+    private MonitoredVm monitoredVm1;
 
     @Before
     public void setup() throws MonitorException, URISyntaxException {
@@ -67,25 +80,139 @@
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
         
-        backend = new VmClassStatBackend(vmClassStatDao, version);
+        registrar = mock(VmStatusListenerRegistrar.class);
+
+        hostIdentifier = mock(HostIdentifier.class);
+        when(hostIdentifier.resolve(isA(VmIdentifier.class))).then(new Answer<VmIdentifier>() {
+            @Override
+            public VmIdentifier answer(InvocationOnMock invocation) throws Throwable {
+                return (VmIdentifier) invocation.getArguments()[0];
+            }
+        });
+        host = mock(MonitoredHost.class);
+        when(host.getHostIdentifier()).thenReturn(hostIdentifier);
+
+        monitoredVm1 = mock(MonitoredVm.class);
+
+        backend = new VmClassStatBackend(vmClassStatDao, version, registrar);
         
-        host = mock(MonitoredHost.class);
         backend.setHost(host);
     }
 
     @Test
-    public void testStart() throws MonitorException {
+    public void testActivate() {
         backend.activate();
-        verify(host).addHostListener(any(HostListener.class));
+        assertTrue(backend.isActive());
+        verify(registrar).register(backend);
+    }
+
+    @Test
+    public void testActivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.activate());
         assertTrue(backend.isActive());
     }
 
     @Test
-    public void testStop() throws MonitorException {
-        backend.activate();
-        backend.deactivate();
-        verify(host).removeHostListener(any(HostListener.class));
+    public void testCanNotActivateWithoutMonitoredHost() {
+        backend.setHost(null);
+
+        assertFalse(backend.activate());
         assertFalse(backend.isActive());
     }
     
+    @Test
+    public void testDeactivate() {
+        backend.activate();
+        backend.deactivate();
+        verify(registrar).unregister(backend);
+        assertFalse(backend.isActive());
+    }
+
+    @Test
+    public void testDeactivateTwice() {
+        backend.activate();
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+        assertTrue(backend.deactivate());
+    }
+
+    @Test
+    public void testNewVM() throws MonitorException, URISyntaxException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+
+        verify(monitoredVm1).addVmListener(isA(VmClassStatVmListener.class));
+    }
+
+    @Test
+    public void testAlreadyRunningVM() throws MonitorException, URISyntaxException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_ACTIVE, 1);
+
+        verify(monitoredVm1).addVmListener(isA(VmClassStatVmListener.class));
+    }
+
+    @Test
+    public void testStatVMGetMonitoredVmFails() throws MonitorException {
+        MonitorException monitorException = new MonitorException();
+        when(host.getMonitoredVm(isA(VmIdentifier.class))).thenThrow(monitorException);
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+
+        assertFalse(backend.getPidToDataMap().containsKey(1));
+    }
+
+    @Test
+    public void testStoppedVM() throws MonitorException, URISyntaxException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verify(monitoredVm1).removeVmListener(isA(VmClassStatVmListener.class));
+    }
+
+    @Test
+    public void testUnknownVMStopped() throws URISyntaxException, MonitorException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verifyNoMoreInteractions(monitoredVm1);
+    }
+
+    @Test
+    public void testErrorRemovingVmListener() throws URISyntaxException, MonitorException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+        MonitorException monitorException = new MonitorException();
+        doThrow(monitorException).when(monitoredVm1).removeVmListener(isA(VmClassStatVmListener.class));
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verify(monitoredVm1).detach();
+    }
+
+    @Test
+    public void testOrderValue() {
+        int orderValue = backend.getOrderValue();
+        assertTrue(orderValue > Ordered.ORDER_MEMORY_GROUP);
+        assertTrue(orderValue < Ordered.ORDER_NETWORK_GROUP);
+    }
 }
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListenerTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.classstat.agent.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import sun.jvmstat.monitor.HostIdentifier;
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.MonitoredVm;
-import sun.jvmstat.monitor.VmIdentifier;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
-
-public class VmClassStatHostListenerTest {
-    
-    private VmClassStatHostListener hostListener;
-    private MonitoredHost host;
-    private MonitoredVm monitoredVm1;
-    private MonitoredVm monitoredVm2;
-
-    @Before
-    public void setup() throws MonitorException, URISyntaxException {
-        VmClassStatDAO vmGcStatDAO = mock(VmClassStatDAO.class);
-        hostListener = new VmClassStatHostListener(vmGcStatDAO, true);
-        
-        host = mock(MonitoredHost.class);
-        HostIdentifier hostId = mock(HostIdentifier.class);
-        monitoredVm1 = mock(MonitoredVm.class);
-        monitoredVm2 = mock(MonitoredVm.class);
-        VmIdentifier vmId1 = new VmIdentifier("1");
-        VmIdentifier vmId2 = new VmIdentifier("2");
-        when(host.getHostIdentifier()).thenReturn(hostId);
-        when(host.getMonitoredVm(eq(vmId1))).thenReturn(monitoredVm1);
-        when(host.getMonitoredVm(eq(vmId2))).thenReturn(monitoredVm2);
-        when(hostId.resolve(eq(vmId1))).thenReturn(vmId1);
-        when(hostId.resolve(eq(vmId2))).thenReturn(vmId2);
-    }
-    
-    @Test
-    public void testNewVM() throws InterruptedException, MonitorException {
-        startVMs();
-        
-        assertTrue(hostListener.getMonitoredVms().containsKey(1));
-        assertTrue(hostListener.getMonitoredVms().containsKey(2));
-        assertEquals(monitoredVm1, hostListener.getMonitoredVms().get(1));
-        assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
-        
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm1));
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm2));
-    }
-    
-    @Test
-    public void testStoppedVM() throws InterruptedException, MonitorException {
-        final Set<Integer> stopped = new HashSet<>();
-        stopped.add(1);
-        
-        startVMs();
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getMonitoredHost()).thenReturn(host);
-        when(event.getStarted()).thenReturn(Collections.emptySet());
-        when(event.getTerminated()).thenReturn(stopped);
-        hostListener.vmStatusChanged(event);
-        
-        // Ensure only 1 removed
-        assertFalse(hostListener.getMonitoredVms().containsKey(1));
-        assertTrue(hostListener.getMonitoredVms().containsKey(2));
-        assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
-        
-        assertFalse(hostListener.getRegisteredListeners().containsKey(monitoredVm1));
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm2));
-    }
-
-    private void startVMs() throws InterruptedException, MonitorException {
-        final Set<Integer> started = new HashSet<>();
-        started.add(1);
-        started.add(2);
-
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getMonitoredHost()).thenReturn(host);
-        when(event.getStarted()).thenReturn(started);
-        when(event.getTerminated()).thenReturn(Collections.emptySet());
-        hostListener.vmStatusChanged(event);
-    }
-}
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -37,15 +37,20 @@
 package com.redhat.thermostat.vm.classstat.agent.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
 import sun.jvmstat.monitor.Monitor;
+import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.event.MonitorStatusChangeEvent;
 import sun.jvmstat.monitor.event.VmEvent;
 
 import com.redhat.thermostat.storage.model.VmClassStat;
@@ -57,12 +62,35 @@
     private static final Integer VM_ID = 123;
     private static final Long LOADED_CLASSES = 1234L;
 
+    private VmClassStatDAO dao;
+    private VmClassStatVmListener listener;
+
+    @Before
+    public void setUp() {
+        dao = mock(VmClassStatDAO.class);
+        listener = new VmClassStatVmListener(dao, VM_ID);
+    }
+
+    @Test
+    public void testDisconnected() {
+        VmEvent vmEvent = mock(VmEvent.class);
+
+        listener.disconnected(vmEvent);
+
+        verifyNoMoreInteractions(vmEvent, dao);
+    }
+
+    @Test
+    public void testMonitorStatusChanged() {
+        MonitorStatusChangeEvent statusChangeEvent = mock(MonitorStatusChangeEvent.class);
+
+        listener.monitorStatusChanged(statusChangeEvent);
+
+        verifyNoMoreInteractions(statusChangeEvent, dao);
+    }
+
     @Test
     public void testMonitorUpdatedClassStat() throws Exception {
-
-        VmClassStatDAO dao = mock(VmClassStatDAO.class);
-
-        VmClassStatVmListener l = new VmClassStatVmListener(dao, VM_ID);
         VmEvent vmEvent = mock(VmEvent.class);
         MonitoredVm monitoredVm = mock(MonitoredVm.class);
         Monitor m = mock(Monitor.class);
@@ -70,7 +98,7 @@
         when(monitoredVm.findByName("java.cls.loadedClasses")).thenReturn(m);
         when(vmEvent.getMonitoredVm()).thenReturn(monitoredVm);
 
-        l.monitorsUpdated(vmEvent);
+        listener.monitorsUpdated(vmEvent);
 
         ArgumentCaptor<VmClassStat> arg = ArgumentCaptor.forClass(VmClassStat.class);
         verify(dao).putVmClassStat(arg.capture());
@@ -81,10 +109,6 @@
 
     @Test
     public void testMonitorUpdatedClassStatTwice() throws Exception {
-
-        VmClassStatDAO dao = mock(VmClassStatDAO.class);
-
-        VmClassStatVmListener l = new VmClassStatVmListener(dao, VM_ID);
         VmEvent vmEvent = mock(VmEvent.class);
         MonitoredVm monitoredVm = mock(MonitoredVm.class);
         Monitor m = mock(Monitor.class);
@@ -92,10 +116,24 @@
         when(monitoredVm.findByName("java.cls.loadedClasses")).thenReturn(m);
         when(vmEvent.getMonitoredVm()).thenReturn(monitoredVm);
 
-        l.monitorsUpdated(vmEvent);
-        l.monitorsUpdated(vmEvent);
+        listener.monitorsUpdated(vmEvent);
+        listener.monitorsUpdated(vmEvent);
 
         // This checks a bug where the Category threw an IllegalStateException because the DAO
         // created a new one on each call, thus violating the unique guarantee of Category.
     }
+
+    @Test
+    public void testMonitorUpdateFails() throws MonitorException {
+        VmEvent vmEvent = mock(VmEvent.class);
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+        MonitorException monitorException = new MonitorException();
+
+        when(monitoredVm.findByName(anyString())).thenThrow(monitorException);
+        when(vmEvent.getMonitoredVm()).thenReturn(monitoredVm);
+
+        listener.monitorsUpdated(vmEvent);
+
+        verifyNoMoreInteractions(dao);
+    }
 }
--- a/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/Activator.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/Activator.java	Mon Jan 21 13:48:50 2013 -0500
@@ -44,6 +44,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.common.MultipleServiceTracker;
@@ -60,6 +61,8 @@
     
     @Override
     public void start(final BundleContext context) throws Exception {
+        final VmStatusListenerRegistrar registrar = new VmStatusListenerRegistrar(context);
+
         executor = Executors.newSingleThreadScheduledExecutor();
 
         Class<?>[] deps = new Class<?>[] {
@@ -72,7 +75,7 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmCpuStatDAO vmCpuStatDao = (VmCpuStatDAO) services.get(VmCpuStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmCpuBackend(executor, vmCpuStatDao, version);
+                backend = new VmCpuBackend(executor, vmCpuStatDao, version, registrar);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackend.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackend.java	Mon Jan 21 13:48:50 2013 -0500
@@ -38,16 +38,15 @@
 
 import java.io.BufferedReader;
 import java.io.IOException;
-import java.net.URISyntaxException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import sun.jvmstat.monitor.HostIdentifier;
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
@@ -60,7 +59,7 @@
 import com.redhat.thermostat.utils.SysConf;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
 
-public class VmCpuBackend extends Backend {
+public class VmCpuBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmCpuBackend.class);
     static final long PROC_CHECK_INTERVAL = 1000; // TODO make this configurable.
@@ -68,15 +67,17 @@
     private VmCpuStatBuilder vmCpuStatBuilder;
     private VmCpuStatDAO vmCpuStats;
     private ScheduledExecutorService executor;
-    private HostIdentifier hostId;
-    private MonitoredHost host;
-    private VmCpuHostListener hostListener;
+    private VmStatusListenerRegistrar registrar;
     private boolean started;
 
-    public VmCpuBackend(ScheduledExecutorService executor, VmCpuStatDAO vmCpuStatDao, Version version) {
+    private final List<Integer> pidsToMonitor = new CopyOnWriteArrayList<>();
+
+    public VmCpuBackend(ScheduledExecutorService executor, VmCpuStatDAO vmCpuStatDao, Version version,
+            VmStatusListenerRegistrar registrar) {
         super(new BackendID("VM CPU Backend", VmCpuBackend.class.getName()));
         this.executor = executor;
         this.vmCpuStats = vmCpuStatDao;
+        this.registrar = registrar;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers CPU statistics about a JVM");
@@ -88,25 +89,17 @@
         ProcessStatusInfoBuilder builder = new ProcessStatusInfoBuilder(new ProcDataSource());
         int numCpus = getCpuCount(source);
         vmCpuStatBuilder = new VmCpuStatBuilder(clock, numCpus, ticksPerSecond, builder);
-        
-        try {
-            hostId = new HostIdentifier((String) null);
-            host = MonitoredHost.getMonitoredHost(hostId);
-            hostListener = new VmCpuHostListener(vmCpuStatBuilder);
-        } catch (MonitorException me) {
-            LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
-        } catch (URISyntaxException use) {
-            LOGGER.log(Level.WARNING, "Failed to create host identifier", use);
-        }
     }
 
     @Override
     public boolean activate() {
-        if (!started && host != null) {
+        if (!started) {
+            registrar.register(this);
+
             executor.scheduleAtFixedRate(new Runnable() {
                 @Override
                 public void run() {
-                    for (Integer pid : hostListener.getPidsToMonitor()) {
+                    for (Integer pid : pidsToMonitor) {
                         if (vmCpuStatBuilder.knowsAbout(pid)) {
                             VmCpuStat dataBuilt = vmCpuStatBuilder.build(pid);
                             if (dataBuilt != null) {
@@ -119,28 +112,18 @@
                 }
             }, 0, PROC_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
 
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "Failed to add host listener", me);
-            }
-
+            started = true;
         }
         return started;
     }
 
     @Override
     public boolean deactivate() {
-        if (started && host != null) {
+        if (started) {
             executor.shutdown();
+            registrar.unregister(this);
 
-            try {
-                host.removeHostListener(hostListener);
-                started = false;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.INFO, "Failed to remove host listener");
-            }
+            started = false;
         }
         return !started;
     }
@@ -183,10 +166,23 @@
     }
 
     /*
-     * For testing purposes only.
+     * Methods implementing VmStatusListener
      */
-    void setHost(MonitoredHost host) {
-        this.host = host;
+    @Override
+    public void vmStatusChanged(Status newStatus, int pid) {
+        switch (newStatus) {
+        case VM_STARTED:
+            /* fall-through */
+        case VM_ACTIVE:
+            pidsToMonitor.add(pid);
+            break;
+        case VM_STOPPED:
+            // the cast is important because it changes the call from remove(index) to remove(Object)
+            pidsToMonitor.remove((Integer) pid);
+            vmCpuStatBuilder.forgetAbout(pid);
+            break;
+        }
+
     }
     
     /*
@@ -195,12 +191,5 @@
     void setVmCpuStatBuilder(VmCpuStatBuilder vmCpuStatBuilder) {
         this.vmCpuStatBuilder = vmCpuStatBuilder;
     }
-    
-    /*
-     * For testing purposes only.
-     */
-    void setHostListener(VmCpuHostListener hostListener) {
-        this.hostListener = hostListener;
-    }
-    
+
 }
--- a/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListener.java	Mon Jan 21 12:17:46 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.cpu.agent.internal;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.logging.Logger;
-
-import sun.jvmstat.monitor.event.HostEvent;
-import sun.jvmstat.monitor.event.HostListener;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.common.utils.LoggingUtils;
-
-public class VmCpuHostListener implements HostListener {
-    
-    private static final Logger LOGGER = LoggingUtils.getLogger(VmCpuHostListener.class);
-    
-    private final Set<Integer> pidsToMonitor = new CopyOnWriteArraySet<Integer>();
-    private VmCpuStatBuilder vmCpuStatBuilder;
-    
-    public VmCpuHostListener(VmCpuStatBuilder builder) {
-        this.vmCpuStatBuilder = builder;
-    }
-
-    @Override
-    public void vmStatusChanged(VmStatusChangeEvent event) {
-        for (Object newVm : event.getStarted()) {
-            Integer vmId = (Integer) newVm;
-            LOGGER.fine("New vm: " + vmId);
-            pidsToMonitor.add(vmId);
-        }
-
-        for (Object stoppedVm : event.getTerminated()) {
-            Integer vmId = (Integer) stoppedVm;
-            LOGGER.fine("stopped vm: " + vmId);
-            pidsToMonitor.remove(vmId);
-            vmCpuStatBuilder.forgetAbout(vmId);
-        }
-    }
-
-    @Override
-    public void disconnected(HostEvent event) {
-        LOGGER.warning("Disconnected from host");
-    }
-    
-    public Set<Integer> getPidsToMonitor() {
-        return pidsToMonitor;
-    }
-    
-}
--- a/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/ActivatorTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/ActivatorTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -93,6 +93,9 @@
         VmCpuBackend backend = activator.getBackend();
         assertNotNull(backend);
 
+        // core thermostat will activate the backend once it's registered
+        backend.activate();
+
         activator.stop(context);
         
         assertFalse(backend.isActive());
--- a/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackendTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackendTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -40,8 +40,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.HashSet;
@@ -53,10 +56,9 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.event.HostListener;
-
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.storage.model.VmCpuStat;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
@@ -65,8 +67,8 @@
     
     private VmCpuBackend backend;
     private ScheduledExecutorService executor;
-    private MonitoredHost host;
     private VmCpuStatDAO vmCpuStatDao;
+    private VmStatusListenerRegistrar registrar;
 
     @Before
     public void setup() {
@@ -76,21 +78,58 @@
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
         
-        backend = new VmCpuBackend(executor, vmCpuStatDao, version);
+        registrar = mock(VmStatusListenerRegistrar.class);
         
-        host = mock(MonitoredHost.class);
-        backend.setHost(host);
+        backend = new VmCpuBackend(executor, vmCpuStatDao, version, registrar);
+    }
+
+    @Test
+    public void testActivate() {
+        backend.activate();
+
+        verify(executor).scheduleAtFixedRate(isA(Runnable.class), eq(0l), eq(1000l), eq(TimeUnit.MILLISECONDS));
+        verify(registrar).register(backend);
+        assertTrue(backend.isActive());
     }
 
     @Test
-    public void testStart() throws MonitorException {
+    public void testActivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.deactivate());
+    }
+
+    @Test
+    public void testDeactivate() {
+        backend.activate();
+        backend.deactivate();
+
+        verify(executor).shutdown();
+        verify(registrar).unregister(backend);
+        assertFalse(backend.isActive());
+    }
+
+
+    @Test
+    public void testDeactivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+    }
+
+    @Test
+    public void testStart() {
         // Setup Runnable mocks
         final Set<Integer> pids = new HashSet<>();
         pids.add(0);
         pids.add(1);
-        VmCpuHostListener listener = mock(VmCpuHostListener.class);
-        when(listener.getPidsToMonitor()).thenReturn(pids);
-        backend.setHostListener(listener);
         
         VmCpuStatBuilder builder = mock(VmCpuStatBuilder.class);
         VmCpuStat stat0 = mock(VmCpuStat.class);
@@ -100,11 +139,15 @@
         backend.setVmCpuStatBuilder(builder);
         
         backend.activate();
+
+        verify(registrar).register(backend);
         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         verify(executor).scheduleAtFixedRate(captor.capture(), any(Long.class), any(Long.class), any(TimeUnit.class));
         assertTrue(backend.isActive());
-        verify(host).addHostListener(any(HostListener.class));
         
+        backend.vmStatusChanged(Status.VM_ACTIVE, 0);
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+
         Runnable runnable = captor.getValue();
         runnable.run();
         verify(builder).learnAbout(0);
@@ -114,15 +157,24 @@
         runnable.run();
         verify(vmCpuStatDao).putVmCpuStat(stat0);
         verify(vmCpuStatDao).putVmCpuStat(stat1);
+
+        backend.vmStatusChanged(Status.VM_STOPPED, 0);
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verify(builder).forgetAbout(0);
+        verify(builder).forgetAbout(1);
+
+        when(builder.knowsAbout(anyInt())).thenReturn(false);
+        runnable.run();
+
+        verifyNoMoreInteractions(vmCpuStatDao);
     }
-    
+
     @Test
-    public void testStop() throws MonitorException {
-        backend.activate();
-        backend.deactivate();
-        verify(executor).shutdown();
-        assertFalse(backend.isActive());
-        verify(host).removeHostListener(any(HostListener.class));
+    public void testOrderValue() {
+        int orderValue = backend.getOrderValue();
+
+        assertTrue(orderValue > Ordered.ORDER_CPU_GROUP);
+        assertTrue(orderValue < Ordered.ORDER_MEMORY_GROUP);
     }
-    
 }
--- a/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListenerTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.cpu.agent.internal;
-
-import static org.junit.Assert.assertTrue;
-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.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-public class VmCpuHostListenerTest {
-    
-    private VmCpuHostListener hostListener;
-    private VmCpuStatBuilder builder;
-
-    @Before
-    public void setup() {
-        builder = mock(VmCpuStatBuilder.class);
-        
-        hostListener = new VmCpuHostListener(builder);
-    }
-
-    @Test
-    public void testNewVM() throws InterruptedException {
-        startVMs();
-        
-        // Check that pids are added to set
-        Set<Integer> pids = hostListener.getPidsToMonitor();
-        assertTrue(pids.contains(1));
-        assertTrue(pids.contains(2));
-    }
-    
-    @Test
-    public void testStoppedVM() throws InterruptedException {
-        final Set<Integer> stopped = new HashSet<>();
-        stopped.add(1);
-        
-        startVMs();
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getStarted()).thenReturn(Collections.emptySet());
-        when(event.getTerminated()).thenReturn(stopped);
-        hostListener.vmStatusChanged(event);
-        
-        // Ensure only 1 removed
-        verify(builder).forgetAbout(1);
-        verify(builder, never()).forgetAbout(2);
-    }
-
-    private void startVMs() throws InterruptedException {
-        final Set<Integer> started = new HashSet<>();
-        started.add(1);
-        started.add(2);
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getStarted()).thenReturn(started);
-        when(event.getTerminated()).thenReturn(Collections.emptySet());
-        hostListener.vmStatusChanged(event);
-    }
-    
-}
--- a/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java	Mon Jan 21 13:48:50 2013 -0500
@@ -54,6 +54,7 @@
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
+import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
@@ -65,7 +66,7 @@
     private final VmGcStatDAO vmGcStats;
     private final VmStatusListenerRegistrar registerer;
 
-    private final Map<Integer, VmAndListener> registeredListeners = new HashMap<>();
+    private final Map<Integer, Pair<MonitoredVm, ? extends VmListener>> pidToData = new HashMap<>();
     private MonitoredHost host;
     private boolean started;
 
@@ -92,7 +93,7 @@
 
     @Override
     public boolean activate() {
-        if (!started) {
+        if (!started && host != null) {
             registerer.register(this);
             started = true;
         }
@@ -151,7 +152,7 @@
                 if (vm != null) {
                     VmGcVmListener listener = new VmGcVmListener(vmGcStats, pid);
                     vm.addVmListener(listener);
-                    registeredListeners.put(pid, new VmAndListener(vm, listener));
+                    pidToData.put(pid, new Pair<>(vm, listener));
                     LOGGER.finer("Attached VmListener for VM: " + pid);
                 } else {
                     LOGGER.warning("could not connect to vm " + pid);
@@ -165,14 +166,14 @@
     }
 
     private void vmStopped(int pid) {
-        VmAndListener tuple = registeredListeners.remove(pid);
-        if (tuple == null) {
-            LOGGER.warning("received vm stopped for an unknown VM");
+        Pair<MonitoredVm, ? extends VmListener> data = pidToData.remove(pid);
+        // if there is no data, we must never have attached to it. Nothing to do.
+        if (data == null) {
             return;
         }
 
-        MonitoredVm vm = tuple.vm;
-        VmListener listener = tuple.listener;
+        MonitoredVm vm = data.getFirst();
+        VmListener listener = data.getSecond();
         try {
             if (listener != null) {
                 vm.removeVmListener(listener);
@@ -190,14 +191,4 @@
         this.host = host;
     }
 
-    private static class VmAndListener {
-        private MonitoredVm vm;
-        private VmListener listener;
-
-        public VmAndListener(MonitoredVm vm, VmListener listener) {
-            this.vm = vm;
-            this.listener = listener;
-        }
-
-    }
 }
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/Activator.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/Activator.java	Mon Jan 21 13:48:50 2013 -0500
@@ -42,6 +42,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.common.MultipleServiceTracker;
@@ -57,6 +58,9 @@
     
     @Override
     public void start(final BundleContext context) throws Exception {
+
+        final VmStatusListenerRegistrar registrar = new VmStatusListenerRegistrar(context);
+
         Class<?>[] deps = new Class<?>[] {
                 BackendService.class,
                 VmMemoryStatDAO.class
@@ -67,7 +71,7 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmMemoryStatDAO vmMemoryStatDao = (VmMemoryStatDAO) services.get(VmMemoryStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmMemoryBackend(vmMemoryStatDao, version);
+                backend = new VmMemoryBackend(vmMemoryStatDao, version, registrar);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackend.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackend.java	Mon Jan 21 13:48:50 2013 -0500
@@ -37,42 +37,51 @@
 package com.redhat.thermostat.vm.memory.agent.internal;
 
 import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import sun.jvmstat.monitor.HostIdentifier;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.VmIdentifier;
+import sun.jvmstat.monitor.event.VmListener;
 
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
+import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
-public class VmMemoryBackend extends Backend {
+public class VmMemoryBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmMemoryBackend.class);
 
     private VmMemoryStatDAO vmMemoryStats;
-    private HostIdentifier hostId;
     private MonitoredHost host;
-    private VmMemoryHostListener hostListener;
+    private final VmStatusListenerRegistrar registrar;
+
     private boolean started;
+    private Map<Integer, Pair<MonitoredVm, ? extends VmListener>> pidToData = new HashMap<>();
 
-    public VmMemoryBackend(VmMemoryStatDAO vmMemoryStatDAO, Version version) {
+    public VmMemoryBackend(VmMemoryStatDAO vmMemoryStatDAO, Version version, VmStatusListenerRegistrar registrar) {
         super(new BackendID("VM Memory Backend", VmMemoryBackend.class.getName()));
         this.vmMemoryStats = vmMemoryStatDAO;
+        this.registrar = registrar;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers memory statistics about a JVM");
         setConfigurationValue(BackendsProperties.VERSION.name(), version.getVersionNumber());
         
         try {
-            hostId = new HostIdentifier((String) null);
+            HostIdentifier hostId = new HostIdentifier((String) null);
             host = MonitoredHost.getMonitoredHost(hostId);
-            hostListener = new VmMemoryHostListener(vmMemoryStats, attachToNewProcessByDefault());
         } catch (MonitorException me) {
             LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
         } catch (URISyntaxException use) {
@@ -83,25 +92,17 @@
     @Override
     public boolean activate() {
         if (!started && host != null) {
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "Failed to add host listener", me);
-            }
+            registrar.register(this);
+            started = true;
         }
         return started;
     }
 
     @Override
     public boolean deactivate() {
-        if (started && host != null) {
-            try {
-                host.removeHostListener(hostListener);
-                started = false;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.INFO, "Failed to remove host listener");
-            }
+        if (started) {
+            registrar.unregister(this);
+            started = false;
         }
         return !started;
     }
@@ -127,6 +128,58 @@
     }
 
     /*
+     * Methods for VmStatusListener
+     */
+    public void vmStatusChanged(Status newStatus, int pid) {
+        switch (newStatus) {
+        case VM_STARTED:
+            /* fall-through */
+        case VM_ACTIVE:
+            handleNewVm(pid);
+            break;
+        case VM_STOPPED:
+            handleStoppedVm(pid);
+            break;
+        default:
+            break;
+        }
+    };
+
+    private void handleNewVm(int pid) {
+        if (attachToNewProcessByDefault()) {
+            try {
+                MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(new VmIdentifier(String.valueOf(pid))));
+                VmMemoryVmListener listener = new VmMemoryVmListener(vmMemoryStats, pid);
+                vm.addVmListener(listener);
+
+                pidToData.put(pid, new Pair<>(vm, listener));
+                LOGGER.finer("Attached VmListener for VM: " + pid);
+            } catch (MonitorException | URISyntaxException e) {
+                LOGGER.log(Level.WARNING, "unable to attach to vm " + pid, e);
+            }
+        } else {
+            LOGGER.log(Level.FINE, "skipping new vm " + pid);
+        }
+    }
+
+    private void handleStoppedVm(int pid) {
+        Pair<MonitoredVm, ? extends VmListener> data = pidToData.remove(pid);
+        // we were not monitoring pid at all, so nothing to do
+        if (data == null) {
+            return;
+        }
+
+        MonitoredVm vm = data.getFirst();
+        VmListener listener = data.getSecond();
+        try {
+            vm.removeVmListener(listener);
+        } catch (MonitorException e) {
+            LOGGER.log(Level.WARNING, "can't remove vm listener", e);
+        }
+        vm.detach();
+    }
+
+    /*
      * For testing purposes only.
      */
     void setHost(MonitoredHost host) {
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListener.java	Mon Jan 21 12:17:46 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.memory.agent.internal;
-
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.MonitoredVm;
-import sun.jvmstat.monitor.VmIdentifier;
-import sun.jvmstat.monitor.event.HostEvent;
-import sun.jvmstat.monitor.event.HostListener;
-import sun.jvmstat.monitor.event.VmListener;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
-
-public class VmMemoryHostListener implements HostListener {
-
-    private static final Logger logger = LoggingUtils.getLogger(VmMemoryHostListener.class);
-
-    private boolean attachNew;
-
-    private final VmMemoryStatDAO vmMemoryStatDAO;
-
-    private Map<Integer, MonitoredVm> monitoredVms  = new HashMap<>();
-    private Map<MonitoredVm, VmMemoryVmListener> registeredListeners  = new ConcurrentHashMap<>();
-    
-    VmMemoryHostListener(VmMemoryStatDAO vmMemoryStatDAO, boolean attachNew) {
-        this.vmMemoryStatDAO = vmMemoryStatDAO;
-        this.attachNew = attachNew;        
-    }
-
-    void removeAllListeners() {
-        for (MonitoredVm vm : monitoredVms.values()) {
-            VmListener listener = registeredListeners.get(vm);
-            try {
-                if (listener != null) {
-                    vm.removeVmListener(listener);
-                }
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "can't remove vm listener", e);
-            }
-        }
-    }
-    
-    @Override
-    public void disconnected(HostEvent event) {
-        logger.warning("Disconnected from host");
-    }
-
-    @SuppressWarnings("unchecked") // Unchecked casts to (Set<Integer>).
-    @Override
-    public void vmStatusChanged(VmStatusChangeEvent event) {
-        MonitoredHost host = event.getMonitoredHost();
-
-        for (Integer newVm : (Set<Integer>) event.getStarted()) {
-            try {
-                logger.fine("New vm: " + newVm);
-                sendNewVM(newVm, host);
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "error getting info for new vm" + newVm, e);
-            } catch (URISyntaxException e) {
-                logger.log(Level.WARNING, "error getting info for new vm" + newVm, e);
-            }
-        }
-
-        for (Integer stoppedVm : (Set<Integer>) event.getTerminated()) {
-            try {
-                logger.fine("stopped vm: " + stoppedVm);
-                sendStoppedVM(stoppedVm, host);
-            } catch (URISyntaxException e) {
-                logger.log(Level.WARNING, "error getting info for stopped vm" + stoppedVm, e);
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "error getting info for stopped vm" + stoppedVm, e);
-            }
-        }
-    }
-
-    private void sendNewVM(Integer vmId, MonitoredHost host)
-            throws MonitorException, URISyntaxException {
-        MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(
-                new VmIdentifier(vmId.toString())));
-        if (vm != null) {
-
-            if (attachNew) {
-                VmMemoryVmListener listener = new VmMemoryVmListener(vmMemoryStatDAO, vmId);
-                vm.addVmListener(listener);
-                registeredListeners.put(vm, listener);
-                logger.finer("Attached VmListener for VM: " + vmId);
-                
-            } else {
-                logger.log(Level.FINE, "skipping new vm " + vmId);
-            }
-
-            monitoredVms.put(vmId, vm);
-        }
-    }
-
-    private void sendStoppedVM(Integer vmId, MonitoredHost host) throws URISyntaxException, MonitorException {
-        
-        VmIdentifier resolvedVmID = host.getHostIdentifier().resolve(new VmIdentifier(vmId.toString()));
-        if (resolvedVmID != null) {
-            MonitoredVm vm = monitoredVms.remove(vmId);
-            VmMemoryVmListener listener = registeredListeners.remove(vm);
-            try {
-                if (listener != null) {
-                    vm.removeVmListener(listener);
-                }
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "can't remove vm listener", e);
-            }
-            vm.detach();
-        }
-    }
-    
-    /*
-     * For testing purposes only.
-     */
-    Map<Integer, MonitoredVm> getMonitoredVms() {
-        return monitoredVms;
-    }
-    
-    /*
-     * For testing purposes only.
-     */
-    Map<MonitoredVm, VmMemoryVmListener> getRegisteredListeners() {
-        return registeredListeners;
-    }
-
-}
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListener.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListener.java	Mon Jan 21 13:48:50 2013 -0500
@@ -76,15 +76,12 @@
     @Override
     public void monitorsUpdated(VmEvent event) {
         MonitoredVm vm = event.getMonitoredVm();
-        if (vm == null) {
-            throw new NullPointerException();
-        }
-        
+
         VmMemoryDataExtractor extractor = new VmMemoryDataExtractor(vm);
-        recordMemoryStat(vm, extractor);
+        recordMemoryStat(extractor);
     }
 
-    void recordMemoryStat(MonitoredVm vm, VmMemoryDataExtractor extractor) {
+    void recordMemoryStat(VmMemoryDataExtractor extractor) {
         try {
             long timestamp = System.currentTimeMillis();
             int maxGenerations = (int) extractor.getTotalGcGenerations();
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/ActivatorTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/ActivatorTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -93,6 +93,10 @@
         VmMemoryBackend backend = activator.getBackend();
         assertNotNull(backend);
 
+        // core thermostat activates the backend when the backend is detected
+        // do it manually for the test
+        backend.activate();
+
         activator.stop(context);
         
         assertFalse(backend.isActive());
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackendTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackendTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -38,20 +38,31 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.net.URISyntaxException;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
+import sun.jvmstat.monitor.HostIdentifier;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.event.HostListener;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.VmIdentifier;
+import sun.jvmstat.monitor.event.VmListener;
 
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
@@ -59,32 +70,158 @@
     
     private VmMemoryBackend backend;
     private MonitoredHost host;
+    private VmStatusListenerRegistrar registrar;
+    private VmMemoryStatDAO vmMemoryStatDao;
 
     @Before
     public void setup() throws MonitorException, URISyntaxException {
-        VmMemoryStatDAO vmMemoryStatDao = mock(VmMemoryStatDAO.class);
+        vmMemoryStatDao = mock(VmMemoryStatDAO.class);
         
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
-        backend = new VmMemoryBackend(vmMemoryStatDao, version);
+
+        registrar = mock(VmStatusListenerRegistrar.class);
+
+        backend = new VmMemoryBackend(vmMemoryStatDao, version, registrar);
         
+        HostIdentifier hostIdentifier = mock(HostIdentifier.class);
+        when(hostIdentifier.resolve(isA(VmIdentifier.class))).then(new Answer<VmIdentifier>() {
+            @Override
+            public VmIdentifier answer(InvocationOnMock invocation) throws Throwable {
+                return (VmIdentifier) invocation.getArguments()[0];
+            }
+        });
         host = mock(MonitoredHost.class);
+        when(host.getHostIdentifier()).thenReturn(hostIdentifier);
+
         backend.setHost(host);
     }
 
     @Test
-    public void testStart() throws MonitorException {
-        backend.activate();
-        verify(host).addHostListener(any(HostListener.class));
+    public void testActivate() {
+        assertTrue(backend.activate());
+
+        verify(registrar).register(backend);
         assertTrue(backend.isActive());
     }
 
     @Test
-    public void testStop() throws MonitorException {
+    public void testActivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        verify(registrar).register(backend);
+    }
+
+    @Test
+    public void testActivateFailsIfHostIsNull() {
+        backend.setHost(null);
+
+        assertFalse(backend.activate());
+    }
+
+    @Test
+    public void testDeactivate() {
         backend.activate();
         backend.deactivate();
-        verify(host).removeHostListener(any(HostListener.class));
+
+        verify(registrar).unregister(backend);
         assertFalse(backend.isActive());
     }
-    
+
+    @Test
+    public void testDeactiveTwice() {
+        assertTrue(backend.activate());
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+
+        verify(registrar).unregister(backend);
+    }
+
+    @Test
+    public void testNewVmStarted() throws URISyntaxException, MonitorException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm);
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+
+        verify(monitoredVm).addVmListener(isA(VmMemoryVmListener.class));
+    }
+
+    @Test
+    public void testErrorInAttachingToNewVm() throws MonitorException, URISyntaxException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+
+        when(host.getMonitoredVm(VM_ID)).thenThrow(new MonitorException());
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+    }
+
+    @Test
+    public void testVmStopped() throws URISyntaxException, MonitorException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm);
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+
+        ArgumentCaptor<VmListener> listenerCaptor = ArgumentCaptor.forClass(VmListener.class);
+        verify(monitoredVm).addVmListener(listenerCaptor.capture());
+
+        backend.vmStatusChanged(Status.VM_STOPPED, VM_PID);
+
+        verify(monitoredVm).removeVmListener(listenerCaptor.getValue());
+        verify(monitoredVm).detach();
+    }
+
+    @Test
+    public void testUnknownVmStoppedIsIgnored() {
+        int VM_PID = 10;
+
+        backend.vmStatusChanged(Status.VM_STOPPED, VM_PID);
+
+        verifyNoMoreInteractions(host, vmMemoryStatDao);
+    }
+
+    @Test
+    public void testStoppedVmIsDetachedEvenInPresenceOfErrors() throws URISyntaxException, MonitorException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm);
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+
+        ArgumentCaptor<VmListener> listenerCaptor = ArgumentCaptor.forClass(VmListener.class);
+        verify(monitoredVm).addVmListener(listenerCaptor.capture());
+
+        VmListener vmListener = listenerCaptor.getValue();
+        doThrow(new MonitorException("test")).when(monitoredVm).removeVmListener(vmListener);
+
+        backend.vmStatusChanged(Status.VM_STOPPED, VM_PID);
+
+        verify(monitoredVm).detach();
+    }
+
+    @Test
+    public void testOrderValue() {
+        int orderValue = backend.getOrderValue();
+
+        assertTrue(orderValue > Ordered.ORDER_MEMORY_GROUP);
+        assertTrue(orderValue < Ordered.ORDER_NETWORK_GROUP);
+    }
 }
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryDataExtractorTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryDataExtractorTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -44,6 +44,8 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
+
 import sun.jvmstat.monitor.LongMonitor;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredVm;
@@ -136,6 +138,18 @@
     }
 
     @Test
+    public void testGenerationCollectorNone() throws MonitorException {
+        final String MONITOR_NAME = "sun.gc.collector.0.name";
+        MonitoredVm vm = mock(MonitoredVm.class);
+        when(vm.findByName(MONITOR_NAME)).thenReturn(null);
+
+        VmMemoryDataExtractor extractor = new VmMemoryDataExtractor(vm);
+        String returned = extractor.getGenerationCollector(0);
+
+        assertEquals(Generation.COLLECTOR_NONE, returned);
+    }
+
+    @Test
     public void testTotalSpaces() throws MonitorException {
         final Long TOTAL_SPACES = 99l;
         final LongMonitor monitor = mock(LongMonitor.class);
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListenerTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.memory.agent.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import sun.jvmstat.monitor.HostIdentifier;
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.MonitoredVm;
-import sun.jvmstat.monitor.VmIdentifier;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
-
-public class VmMemoryHostListenerTest {
-    
-    private VmMemoryHostListener hostListener;
-    private MonitoredHost host;
-    private MonitoredVm monitoredVm1;
-    private MonitoredVm monitoredVm2;
-
-    @Before
-    public void setup() throws MonitorException, URISyntaxException {
-        VmMemoryStatDAO vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
-        hostListener = new VmMemoryHostListener(vmMemoryStatDAO, true);
-        
-        host = mock(MonitoredHost.class);
-        HostIdentifier hostId = mock(HostIdentifier.class);
-        monitoredVm1 = mock(MonitoredVm.class);
-        monitoredVm2 = mock(MonitoredVm.class);
-        VmIdentifier vmId1 = new VmIdentifier("1");
-        VmIdentifier vmId2 = new VmIdentifier("2");
-        when(host.getHostIdentifier()).thenReturn(hostId);
-        when(host.getMonitoredVm(eq(vmId1))).thenReturn(monitoredVm1);
-        when(host.getMonitoredVm(eq(vmId2))).thenReturn(monitoredVm2);
-        when(hostId.resolve(eq(vmId1))).thenReturn(vmId1);
-        when(hostId.resolve(eq(vmId2))).thenReturn(vmId2);
-    }
-    
-    @Test
-    public void testNewVM() throws InterruptedException, MonitorException {
-        startVMs();
-        
-        assertTrue(hostListener.getMonitoredVms().containsKey(1));
-        assertTrue(hostListener.getMonitoredVms().containsKey(2));
-        assertEquals(monitoredVm1, hostListener.getMonitoredVms().get(1));
-        assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
-        
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm1));
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm2));
-    }
-    
-    @Test
-    public void testStoppedVM() throws InterruptedException, MonitorException {
-        final Set<Integer> stopped = new HashSet<>();
-        stopped.add(1);
-        
-        startVMs();
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getMonitoredHost()).thenReturn(host);
-        when(event.getStarted()).thenReturn(Collections.emptySet());
-        when(event.getTerminated()).thenReturn(stopped);
-        hostListener.vmStatusChanged(event);
-        
-        // Ensure only 1 removed
-        assertFalse(hostListener.getMonitoredVms().containsKey(1));
-        assertTrue(hostListener.getMonitoredVms().containsKey(2));
-        assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
-        
-        assertFalse(hostListener.getRegisteredListeners().containsKey(monitoredVm1));
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm2));
-    }
-
-    private void startVMs() throws InterruptedException, MonitorException {
-        final Set<Integer> started = new HashSet<>();
-        started.add(1);
-        started.add(2);
-
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getMonitoredHost()).thenReturn(host);
-        when(event.getStarted()).thenReturn(started);
-        when(event.getTerminated()).thenReturn(Collections.emptySet());
-        hostListener.vmStatusChanged(event);
-    }
-}
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListenerTest.java	Mon Jan 21 12:17:46 2013 -0500
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListenerTest.java	Mon Jan 21 13:48:50 2013 -0500
@@ -37,16 +37,21 @@
 package com.redhat.thermostat.vm.memory.agent.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import sun.jvmstat.monitor.Monitor;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.event.VmEvent;
 
 import com.redhat.thermostat.storage.model.VmMemoryStat;
 import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
@@ -77,19 +82,20 @@
     };
     
     private VmMemoryVmListener vmListener;
-    private MonitoredVm monitoredVm;
     private VmMemoryDataExtractor extractor;
     private VmMemoryStatDAO vmMemoryStatDAO;
+    private MonitoredVm monitoredVm;
     
     @Before
     public void setup() throws MonitorException {
         final int numGens = 2;
         vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
         vmListener = new VmMemoryVmListener(vmMemoryStatDAO, 0);
-        
         monitoredVm = mock(MonitoredVm.class);
         extractor = mock(VmMemoryDataExtractor.class);
-        
+
+        mockTotalGenerations(numGens);
+
         for (int i = 0; i < numGens; i++) {
             mockGenerationName(i);
             mockGenerationCapacity(i);
@@ -106,6 +112,10 @@
         }
     }
 
+    private void mockTotalGenerations(long gens) throws MonitorException {
+        when(extractor.getTotalGcGenerations()).thenReturn(gens);
+    }
+
     private void mockGenerationName(int gen) throws MonitorException {
         when(extractor.getGenerationName(gen)).thenReturn(GEN_NAMES[gen]);
     }
@@ -141,15 +151,43 @@
     private void mockSpaceUsed(int gen, int space) throws MonitorException {
         when(extractor.getSpaceUsed(gen, space)).thenReturn(SPACE_USED[gen][space]);
     }
-    
+
+    @Test
+    public void testDisconnectedIsNoOp() {
+        vmListener.disconnected(null);
+
+        verifyNoMoreInteractions(vmMemoryStatDAO, extractor);
+    }
+
+    @Test
+    public void testMonitorStatusChangeIsNoOp() {
+        vmListener.monitorStatusChanged(null);
+
+        verifyNoMoreInteractions(vmMemoryStatDAO, extractor);
+    }
+
+    @Test
+    public void testMonitorsUpdated() throws MonitorException {
+        Monitor monitor = mock(Monitor.class);
+        when(monitor.getValue()).thenReturn(Long.valueOf(0));
+        when(monitoredVm.findByName(anyString())).thenReturn(monitor);
+        VmEvent monitorUpdateEvent = mock(VmEvent.class);
+        when(monitorUpdateEvent.getMonitoredVm()).thenReturn(monitoredVm);
+
+        vmListener.monitorsUpdated(monitorUpdateEvent);
+
+        verify(vmMemoryStatDAO).putVmMemoryStat(isA(VmMemoryStat.class));
+    }
+
     @Test
     public void testRecordMemoryStat() {
-        vmListener.recordMemoryStat(monitoredVm, extractor);
+        vmListener.recordMemoryStat(extractor);
         ArgumentCaptor<VmMemoryStat> captor = ArgumentCaptor.forClass(VmMemoryStat.class);
         verify(vmMemoryStatDAO).putVmMemoryStat(captor.capture());
         VmMemoryStat memoryStat = captor.getValue();
         
         Generation[] gens = memoryStat.getGenerations();
+        assertEquals(2, gens.length);
         for (int i = 0; i < gens.length; i++) {
             Generation gen = gens[i];
             assertEquals(GEN_NAMES[i], gen.getName());
@@ -167,4 +205,12 @@
             }
         }
     }
+
+    @Test
+    public void testRecordingMemoryInPresenseOfExtrationErrors() throws MonitorException {
+        when(extractor.getTotalGcGenerations()).thenThrow(new MonitorException());
+        vmListener.recordMemoryStat(extractor);
+
+        verifyNoMoreInteractions(vmMemoryStatDAO);
+    }
 }