changeset 260:e5ca1ae73748

PR959: Show exact memory sizes in the JVM's memory tab Reviewed-by: rkennke Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-April/001045.html
author Omair Majid <omajid@redhat.com>
date Mon, 23 Apr 2012 16:24:41 -0400
parents 04338ff62c91
children 0ad7fdcad47e
files client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java client/src/main/java/com/redhat/thermostat/client/ui/IconResource.java client/src/main/java/com/redhat/thermostat/client/ui/MemorySpacePanel.java client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryPanel.java client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryView.java client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties client/src/test/java/com/redhat/thermostat/client/ui/VmMemoryControllerTest.java
diffstat 8 files changed, 392 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Mon Apr 23 16:56:10 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Mon Apr 23 16:24:41 2012 -0400
@@ -61,7 +61,7 @@
     MENU_EDIT_ENABLE_HISTORY_MODE,
     MENU_HELP,
     MENU_HELP_ABOUT,
-    
+
     GARBAGE_COLLECTION,
     YOUNG_GEN,
     EDEN_GEN,
@@ -164,11 +164,10 @@
     VM_CPU_CHART_LOAD_LABEL,
     VM_CPU_CHART_TIME_LABEL,
 
-    VM_CURRENT_MEMORY_CHART_USED,
-    VM_CURRENT_MEMORY_CHART_CAPACITY,
-    VM_CURRENT_MEMORY_CHART_MAX_CAPACITY,
-    VM_CURRENT_MEMORY_CHART_SPACE,
-    VM_CURRENT_MEMORY_CHART_SIZE,
+    VM_MEMORY_SPACE_TITLE,
+    VM_MEMORY_SPACE_USED,
+    VM_MEMORY_SPACE_FREE,
+    VM_MEMORY_SPACE_ADDITIONAL,
 
     VM_GC_COLLECTOR_OVER_GENERATION,
     VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL,
--- a/client/src/main/java/com/redhat/thermostat/client/ui/IconResource.java	Mon Apr 23 16:56:10 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/IconResource.java	Mon Apr 23 16:24:41 2012 -0400
@@ -58,6 +58,8 @@
     public static final IconResource NETWORK_SERVER = new IconResource(ICON_PREFIX + "48x48/places/network-server.png");
     public static final IconResource NETWORK_GROUP = new IconResource(ICON_PREFIX + "48x48/places/network-workgroup.png");
 
+    public static final IconResource ARROW_RIGHT = new IconResource(ICON_PREFIX + "48x48/actions/go-next.png");
+
     public static final IconResource SEARCH = new IconResource(ICON_PREFIX + "16x16/actions/search.png");
 
     private final String path;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/MemorySpacePanel.java	Mon Apr 23 16:24:41 2012 -0400
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.LayoutStyle.ComponentPlacement;
+
+public class MemorySpacePanel extends JPanel {
+
+    private final JProgressBar percentagePanel;
+    private final JLabel additionalDetailsIcon;
+    private final JLabel lblUsed;
+    private final JLabel lblAvailable;
+
+    public MemorySpacePanel(String regionName) {
+        JLabel lblRegionName = new JLabel(regionName);
+
+        percentagePanel = new JProgressBar(0, 100);
+
+        additionalDetailsIcon = new JLabel(IconResource.ARROW_RIGHT.getIcon());
+
+        lblUsed = new JLabel("${USED}");
+        lblAvailable = new JLabel("${AVAILABLE}");
+
+        GroupLayout groupLayout = new GroupLayout(this);
+        groupLayout.setHorizontalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addComponent(lblRegionName)
+                        .addGroup(groupLayout.createSequentialGroup()
+                            .addGap(12)
+                            .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                                .addComponent(percentagePanel, GroupLayout.DEFAULT_SIZE, 574, Short.MAX_VALUE)
+                                .addGroup(groupLayout.createSequentialGroup()
+                                    .addComponent(lblUsed)
+                                    .addPreferredGap(ComponentPlacement.RELATED, 441, Short.MAX_VALUE)
+                                    .addComponent(lblAvailable)))))
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(additionalDetailsIcon)
+                    .addContainerGap())
+        );
+        groupLayout.setVerticalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(lblRegionName)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addComponent(additionalDetailsIcon, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addComponent(percentagePanel, GroupLayout.DEFAULT_SIZE, 44, Short.MAX_VALUE))
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
+                        .addComponent(lblUsed)
+                        .addComponent(lblAvailable))
+                    .addGap(5))
+        );
+        setLayout(groupLayout);
+    }
+
+
+    public void updateRegionData(int percentageUsed, String currentlyUsed, String currentlyAvailable, String allocatable) {
+        percentagePanel.setValue(percentageUsed);
+
+        lblUsed.setText(currentlyUsed);
+        lblAvailable.setText(currentlyAvailable);
+        additionalDetailsIcon.setToolTipText(allocatable);
+    }
+
+}
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java	Mon Apr 23 16:56:10 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java	Mon Apr 23 16:24:41 2012 -0400
@@ -36,13 +36,17 @@
 
 package com.redhat.thermostat.client.ui;
 
+import static com.redhat.thermostat.client.locale.Translate.localize;
+
 import java.awt.Component;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
 import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.client.AsyncUiFacade;
+import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
@@ -52,21 +56,19 @@
 
 class VmMemoryController implements AsyncUiFacade {
 
-    private final VmRef ref;
     private final VmMemoryView view;
     private final VmMemoryStatDAO dao;
 
-    private final Timer timer = new Timer();
+    private final Timer timer;
+
+    private final List<String> spacesInView =  new ArrayList<>();
 
-    public VmMemoryController(VmRef ref) {
-        this.ref = ref;
+    public VmMemoryController(final VmRef ref) {
         dao = ApplicationContext.getInstance().getDAOFactory().getVmMemoryStatDAO();
+        timer = ApplicationContext.getInstance().getTimerFactory().createTimer();
         view = ApplicationContext.getInstance().getViewFactory().getView(VmMemoryView.class);
-    }
 
-    @Override
-    public void start() {
-        timer.scheduleAtFixedRate(new TimerTask() {
+        timer.setAction(new Runnable() {
             @Override
             public void run() {
                 VmMemoryStat info = dao.getLatestMemoryStat(ref);
@@ -74,18 +76,37 @@
                 for (Generation generation: generations) {
                     List<Space> spaces = generation.spaces;
                     for (Space space: spaces) {
-                        view.setMemoryRegionSize(space.name, space.used, space.capacity, space.maxCapacity);
+
+                        if (!spacesInView.contains(space.name)) {
+                            view.addRegion(space.name);
+                            spacesInView.add(space.name);
+                        }
+
+                        int percentageUsed = (int) (100.0 * space.used/space.capacity);
+                        String currentlyUsed = localize(LocaleResources.VM_MEMORY_SPACE_USED, String.valueOf(space.used));
+                        String currentlyUnused = localize(LocaleResources.VM_MEMORY_SPACE_FREE, String.valueOf(space.capacity - space.used));
+                        String allocatable = localize(LocaleResources.VM_MEMORY_SPACE_ADDITIONAL, String.valueOf(space.maxCapacity-space.capacity));
+                        String name = space.name; // FIXME
+                        view.updateRegionSize(name, percentageUsed, currentlyUsed, currentlyUnused, allocatable);
+
                     }
                 }
+            }
+        });
+        timer.setInitialDelay(0);
+        timer.setDelay(10);
+        timer.setTimeUnit(TimeUnit.MILLISECONDS);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+    }
 
-            }
-
-        }, 0, TimeUnit.SECONDS.toMillis(5));
+    @Override
+    public void start() {
+        timer.start();
     }
 
     @Override
     public void stop() {
-        timer.cancel();
+        timer.stop();
     }
 
     public Component getComponent() {
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryPanel.java	Mon Apr 23 16:56:10 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryPanel.java	Mon Apr 23 16:24:41 2012 -0400
@@ -37,18 +37,17 @@
 package com.redhat.thermostat.client.ui;
 
 import static com.redhat.thermostat.client.locale.Translate.localize;
+import java.awt.Component;
+import java.util.HashMap;
+import java.util.Map;
 
-import java.awt.Component;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JLabel;
 import javax.swing.JPanel;
-
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.data.category.DefaultCategoryDataset;
+import javax.swing.LayoutStyle.ComponentPlacement;
+import javax.swing.BoxLayout;
+import javax.swing.SwingUtilities;
 
 import com.redhat.thermostat.client.locale.LocaleResources;
 
@@ -56,58 +55,77 @@
 
     private static final long serialVersionUID = -2882890932814218436L;
 
-    private final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
+    private final Map<String, MemorySpacePanel> regions = new HashMap<>();
+
+    private final JPanel currentRegionSizePanel;
 
     public VmMemoryPanel() {
-        initializePanel();
+        JLabel lblMem = new JLabel(localize(LocaleResources.VM_MEMORY_SPACE_TITLE));
+
+        currentRegionSizePanel = new JPanel();
+
+        GroupLayout groupLayout = new GroupLayout(this);
+        groupLayout.setHorizontalGroup(
+            groupLayout.createParallelGroup(Alignment.TRAILING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addGroup(groupLayout.createSequentialGroup()
+                            .addComponent(currentRegionSizePanel, GroupLayout.DEFAULT_SIZE, 630, Short.MAX_VALUE)
+                            .addContainerGap())
+                        .addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
+                            .addComponent(lblMem)
+                            .addGap(491))))
+        );
+        groupLayout.setVerticalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(lblMem)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(currentRegionSizePanel, GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE)
+                    .addContainerGap())
+        );
+        currentRegionSizePanel.setLayout(new BoxLayout(currentRegionSizePanel, BoxLayout.PAGE_AXIS));
+        setLayout(groupLayout);
+
     }
 
     @Override
-    public void setMemoryRegionSize(String name, long used, long allocated, long max) {
-        dataset.addValue(used, localize(LocaleResources.VM_CURRENT_MEMORY_CHART_USED), name);
-        dataset.addValue(allocated - used,
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_CAPACITY), name);
-        dataset.addValue(max - allocated,
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_MAX_CAPACITY), name);
-    }
-
-    private void initializePanel() {
-        JPanel panel = this;
-        panel.setLayout(new GridBagLayout());
-        GridBagConstraints c = new GridBagConstraints();
-        c.gridx = 0;
-        c.gridy = 0;
-        c.fill = GridBagConstraints.BOTH;
-        c.weightx = 1;
-        c.weighty = 1;
-        panel.add(createCurrentMemoryDisplay(), c);
-        c.gridy++;
-        panel.add(createMemoryHistoryPanel(), c);
+    public void addRegion(final String humanReadableName) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MemorySpacePanel regionInfo = new MemorySpacePanel(humanReadableName);
+                regions.put(humanReadableName, regionInfo);
+                currentRegionSizePanel.add(regionInfo);
+                currentRegionSizePanel.revalidate();
+            }
+        });
     }
 
-    private Component createCurrentMemoryDisplay() {
 
-        JFreeChart chart = ChartFactory.createStackedBarChart(
-                null,
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_SPACE),
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_SIZE),
-                dataset,
-                PlotOrientation.HORIZONTAL, true, false, false);
-
-        ChartPanel chartPanel = new ChartPanel(chart);
-        // make this chart non-interactive
-        chartPanel.setDisplayToolTips(true);
-        chartPanel.setDoubleBuffered(true);
-        chartPanel.setMouseZoomable(false);
-        chartPanel.setPopupMenu(null);
-
-        return chartPanel;
+    @Override
+    public void removeAllRegions() {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                regions.clear();
+                currentRegionSizePanel.removeAll();
+                currentRegionSizePanel.revalidate();
+            }
+        });
     }
 
-    private Component createMemoryHistoryPanel() {
-        JPanel historyPanel = new JPanel();
-        // TODO implement this
-        return historyPanel;
+    @Override
+    public void updateRegionSize(final String name, final int percentageUsed, final String currentlyUsed, final String currentlyAvailable, final String allocatable) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                regions.get(name).updateRegionData(percentageUsed, currentlyUsed, currentlyAvailable, allocatable);
+            }
+        });
+
     }
 
     @Override
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryView.java	Mon Apr 23 16:56:10 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryView.java	Mon Apr 23 16:24:41 2012 -0400
@@ -42,7 +42,11 @@
 
 public interface VmMemoryView extends View {
 
-    void setMemoryRegionSize(String name, long used, long allocated, long max);
+    void addRegion(String humanReadableName);
+
+    void removeAllRegions();
+
+    void updateRegionSize(String name, int percentageUsed, String currentlyUsed, String currentlyAvailable, String allocatable);
 
     Component getUiComponent();
 
--- a/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Mon Apr 23 16:56:10 2012 +0200
+++ b/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Mon Apr 23 16:24:41 2012 -0400
@@ -125,11 +125,10 @@
 VM_CPU_CHART_LOAD_LABEL = % CPU
 VM_CPU_CHART_TIME_LABEL = Time
 
-VM_CURRENT_MEMORY_CHART_USED = Used
-VM_CURRENT_MEMORY_CHART_CAPACITY = Capacity
-VM_CURRENT_MEMORY_CHART_MAX_CAPACITY = Max Capacity
-VM_CURRENT_MEMORY_CHART_SPACE = Memory Region
-VM_CURRENT_MEMORY_CHART_SIZE = Size
+VM_MEMORY_SPACE_TITLE = Memory Region Sizes
+VM_MEMORY_SPACE_USED = {0} bytes used
+VM_MEMORY_SPACE_FREE = {0} bytes unused
+VM_MEMORY_SPACE_ADDITIONAL = An additional {0} bytes can be allocated
 
 VM_GC_COLLECTOR_OVER_GENERATION = Collector {0} running on {1}
 VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL = Time
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/ui/VmMemoryControllerTest.java	Mon Apr 23 16:24:41 2012 -0400
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.ViewFactory;
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmMemoryStat;
+import com.redhat.thermostat.common.model.VmMemoryStat.Generation;
+import com.redhat.thermostat.common.model.VmMemoryStat.Space;
+
+public class VmMemoryControllerTest {
+
+    private final long TIMESTAMP = 1;
+    private final int VM_ID = 99;
+
+    private List<Generation> generations = new ArrayList<>();
+
+    private Timer timer;
+    private Space space;
+    private Generation gen;
+    private VmMemoryController controller;
+    private VmMemoryView view;
+    private Runnable timerAction;
+
+
+    @Before()
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+
+        // Setup timer.
+        timer = mock(Timer.class);
+        ArgumentCaptor<Runnable> actionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(actionCaptor.capture());
+
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        when(timerFactory.createTimer()).thenReturn(timer);
+        ApplicationContext.getInstance().setTimerFactory(timerFactory);
+
+        space = new Space();
+        space.name = "space";
+        space.index = 0;
+        space.used = 10;
+        space.capacity = 100;
+        space.maxCapacity = 1000;
+
+        gen = new Generation();
+        gen.spaces = new ArrayList<>();
+        gen.spaces.add(space);
+
+        generations.add(gen);
+
+        // Setup dao
+        VmMemoryStat vmMemory = new VmMemoryStat(TIMESTAMP, VM_ID, generations);
+        VmMemoryStatDAO memoryStatDao = mock(VmMemoryStatDAO.class);
+        when(memoryStatDao.getLatestMemoryStat(any(VmRef.class))).thenReturn(vmMemory);
+
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getVmMemoryStatDAO()).thenReturn(memoryStatDao);
+
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        // Setup view
+        view = mock(VmMemoryView.class);
+        ViewFactory viewFactory = mock(ViewFactory.class);
+        when(viewFactory.getView(eq(VmMemoryView.class))).thenReturn(view);
+        ApplicationContext.getInstance().setViewFactory(viewFactory);
+
+
+        VmRef ref = mock(VmRef.class);
+
+        controller = new VmMemoryController(ref);
+        timerAction = actionCaptor.getValue();
+
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @Test
+    public void testTimer() {
+
+        controller.start();
+
+        verify(timer).start();
+        verify(timer).setSchedulingType(SchedulingType.FIXED_RATE);
+
+        controller.stop();
+
+        verify(timer).stop();
+    }
+
+
+    @Test
+    public void testControllerUpdatesView() {
+
+        timerAction.run();
+
+        verify(view).addRegion(eq("space"));
+        verify(view).updateRegionSize(eq("space"), eq(10), contains("10"), contains("90"), contains("900"));
+    }
+}