changeset 741:e9531651f79d

Eclipse Host CPU chart This commit adds a new view in Eclipse for monitoring a host's CPU, similar to the host's Processor tab in the Swing GUI. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-September/003349.html
author Elliott Baron <ebaron@redhat.com>
date Thu, 25 Oct 2012 13:34:48 -0400
parents 2f1cedb72af5
children 061618d8bcba
files eclipse/com.redhat.thermostat.eclipse.chart.common/src/com/redhat/thermostat/eclipse/chart/common/HostCpuViewPart.java eclipse/com.redhat.thermostat.eclipse.chart.common/src/com/redhat/thermostat/eclipse/chart/common/SWTHostCpuView.java eclipse/com.redhat.thermostat.eclipse.chart.common/src/com/redhat/thermostat/eclipse/chart/common/SWTHostCpuViewProvider.java eclipse/com.redhat.thermostat.eclipse.test.ui/src/com/redhat/thermostat/eclipse/test/ui/SWTHostCpuViewTest.java eclipse/com.redhat.thermostat.eclipse.test/src/com/redhat/thermostat/eclipse/test/views/HostCpuViewPartTest.java
diffstat 5 files changed, 904 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/com.redhat.thermostat.eclipse.chart.common/src/com/redhat/thermostat/eclipse/chart/common/HostCpuViewPart.java	Thu Oct 25 13:34:48 2012 -0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.eclipse.chart.common;
+
+import com.redhat.thermostat.client.core.views.HostCpuViewProvider;
+import com.redhat.thermostat.client.ui.HostCpuController;
+import com.redhat.thermostat.common.dao.CpuStatDAO;
+import com.redhat.thermostat.common.dao.HostInfoDAO;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.eclipse.views.SWTComponent;
+
+public class HostCpuViewPart extends HostRefViewPart {
+
+    private HostCpuController cpuController;
+
+    @Override
+    protected void createControllerView(HostRef ref) {
+        HostInfoDAO hostInfoDao = OSGIUtils.getInstance().getService(
+                HostInfoDAO.class);
+        CpuStatDAO cpuStatDao = OSGIUtils.getInstance().getService(
+                CpuStatDAO.class);
+        HostCpuViewProvider viewProvider = OSGIUtils.getInstance().getService(
+                HostCpuViewProvider.class);
+        cpuController = createController(hostInfoDao, cpuStatDao, ref,
+                viewProvider);
+        SWTComponent view = (SWTComponent) cpuController.getView();
+        view.createControl(top);
+    }
+
+    public HostCpuController createController(HostInfoDAO hostInfoDao,
+            CpuStatDAO cpuStatDao, HostRef ref, HostCpuViewProvider viewProvider) {
+        return new HostCpuController(hostInfoDao, cpuStatDao, ref, viewProvider);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/com.redhat.thermostat.eclipse.chart.common/src/com/redhat/thermostat/eclipse/chart/common/SWTHostCpuView.java	Thu Oct 25 13:34:48 2012 -0400
@@ -0,0 +1,314 @@
+/*
+ * 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.eclipse.chart.common;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.PlatformUI;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.RegularTimePeriod;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
+import com.redhat.thermostat.client.core.views.HostCpuView;
+import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.ui.ChartColors;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.model.DiscreteTimeData;
+import com.redhat.thermostat.eclipse.ThermostatConstants;
+import com.redhat.thermostat.eclipse.views.SWTComponent;
+
+public class SWTHostCpuView extends HostCpuView implements SWTComponent {
+    public static final String TEST_ID_CPU_MODEL = "SWTHostCpuView.cpuModel";
+    public static final String TEST_ID_CPU_COUNT = "SWTHostCpuView.cpuCount";
+    public static final String TEST_ID_LEGEND_ITEM = "SWTHostCpuView.legendItem";
+    
+    private static final String LEGEND_COLOUR_BLOCK = "\u2588";
+    private static final int H_INDENT = 20;
+    private static final int SPACER_WIDTH = 10;
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    
+    private JFreeChart chart;
+    private TimeSeriesCollection datasetCollection;
+    private Label cpuModel;
+    private Label cpuCount;
+    
+    private final Map<Integer, TimeSeries> datasets;
+    private final Map<String, Color> colors;
+    private Composite chartTop;
+    private Composite legendTop;
+    private Composite parent;
+    private ViewVisibilityWatcher watcher;
+    private CountDownLatch latch;
+    
+    public SWTHostCpuView() {
+        datasetCollection = new TimeSeriesCollection();
+        datasets = new HashMap<Integer, TimeSeries>();
+        colors = new HashMap<String, Color>();
+        watcher = new ViewVisibilityWatcher(notifier);
+        chart = createCpuChart();
+        latch = new CountDownLatch(1);
+    }
+    
+    public void createControl(Composite parent) {
+        this.parent = parent;
+        
+        Label summaryLabel = new Label(parent, SWT.LEAD);
+        Font stdFont = summaryLabel.getFont();
+        Font boldFont = new Font(stdFont.getDevice(),
+                stdFont.getFontData()[0].getName(),
+                stdFont.getFontData()[0].getHeight(), SWT.BOLD);
+        
+        summaryLabel.setText(translator.localize(LocaleResources.HOST_CPU_SECTION_OVERVIEW));
+        summaryLabel.setFont(boldFont);
+        
+        Composite detailsTop = new Composite(parent, SWT.NONE);
+        detailsTop.setLayout(new GridLayout(3, false));
+
+        Label cpuModelLabel = new Label(detailsTop, SWT.TRAIL);
+        cpuModelLabel.setText(translator.localize(LocaleResources.HOST_INFO_CPU_MODEL));
+        GridData hIndentLayoutData = new GridData();
+        hIndentLayoutData.horizontalIndent = H_INDENT;
+        cpuModelLabel.setLayoutData(hIndentLayoutData);
+        
+        Label cpuModelSpacer = new Label(detailsTop, SWT.NONE);
+        cpuModelSpacer.setLayoutData(new GridData(SPACER_WIDTH, SWT.DEFAULT));
+        
+        cpuModel = new Label(detailsTop, SWT.LEAD);
+        cpuModel.setData(ThermostatConstants.TEST_TAG, TEST_ID_CPU_MODEL);
+        cpuModel.setText("Unknown");
+
+        Label cpuCountLabel = new Label(detailsTop, SWT.TRAIL);
+        cpuCountLabel.setText(translator.localize(LocaleResources.HOST_INFO_CPU_COUNT));
+        cpuCountLabel.setLayoutData(hIndentLayoutData);
+        
+        Label cpuCountSpacer = new Label(detailsTop, SWT.NONE);
+        cpuCountSpacer.setLayoutData(new GridData(SPACER_WIDTH, SWT.DEFAULT));
+        
+        cpuCount = new Label(detailsTop, SWT.LEAD);
+        cpuCount.setData(ThermostatConstants.TEST_TAG, TEST_ID_CPU_COUNT);
+        cpuCount.setText("Unknown");
+        
+        chartTop = new RecentTimeSeriesChartComposite(parent, SWT.NONE, chart);
+        chartTop.setLayout(new GridLayout());
+        chartTop.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+        
+        legendTop = new Composite(parent, SWT.NONE);
+        RowLayout legendLayout = new RowLayout(SWT.HORIZONTAL);
+        legendLayout.center = true;
+        legendLayout.wrap = false;
+        legendLayout.marginHeight = 0;
+        legendTop.setLayout(legendLayout);
+        
+        // Notify threads that controls are created
+        latch.countDown();
+        
+        // Don't start giving updates until controls are created
+        watcher.watch(parent, ThermostatConstants.VIEW_ID_HOST_CPU);
+    }
+
+    private JFreeChart createCpuChart() {
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(null,
+                translator.localize(LocaleResources.HOST_CPU_USAGE_CHART_TIME_LABEL),
+                translator.localize(LocaleResources.HOST_CPU_USAGE_CHART_VALUE_LABEL),
+                datasetCollection, false, false, false);
+
+        chart.getPlot().setBackgroundPaint(new Color(255, 255, 255, 0));
+        chart.getPlot().setBackgroundImageAlpha(0.0f);
+        chart.getPlot().setOutlinePaint(new Color(0, 0, 0, 0));
+
+        return chart;
+    }
+
+    public void setCpuCount(final String count) {
+        PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
+            @Override
+            public void run() {
+                if (!cpuCount.isDisposed()) {
+                    cpuCount.setText(count);
+                }
+            }
+        });
+    }
+
+    public void setCpuModel(final String model) {
+        PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
+            @Override
+            public void run() {
+                if (!cpuModel.isDisposed()) {
+                    cpuModel.setText(model);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void addCpuUsageChart(final int cpuIndex, final String humanReadableName) {
+        EventQueue.invokeLater(new Runnable() {
+
+            @Override
+            public void run() {
+                TimeSeries series = new TimeSeries(humanReadableName);
+                Color color = ChartColors.getColor(colors.size());
+                colors.put(humanReadableName, color);
+
+                datasets.put(cpuIndex, series);
+                datasetCollection.addSeries(series);
+
+                updateColors();
+
+                addLegendItem(humanReadableName, color);
+            }
+        });
+    }
+
+    @Override
+    public void addCpuUsageData(final int cpuIndex, List<DiscreteTimeData<Double>> data) {
+        final ArrayList<DiscreteTimeData<Double>> copy = new ArrayList<>(data);
+        EventQueue.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                TimeSeries dataset = datasets.get(cpuIndex);
+                for (DiscreteTimeData<Double> timeData: copy) {
+                    RegularTimePeriod period = new FixedMillisecond(timeData.getTimeInMillis());
+                    if (dataset.getDataItem(period) == null) {
+                        dataset.add(period, timeData.getData(), false);
+                    }
+                }
+                dataset.fireSeriesChanged();
+            }
+        });
+    }
+
+    @Override
+    public void clearCpuUsageData() {
+        EventQueue.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                for (Iterator<Map.Entry<Integer, TimeSeries>> iter = datasets.entrySet().iterator(); iter.hasNext();) {
+                    Map.Entry<Integer, TimeSeries> entry = iter.next();
+                    datasetCollection.removeSeries(entry.getValue());
+                    entry.getValue().clear();
+
+                    iter.remove();
+
+                }
+                updateColors();
+            }
+        });
+    }
+    
+    /**
+     * Adding or removing series to the series collection may change the order
+     * of existing items. Plus the paint for the index is now out-of-date. So
+     * let's walk through all the series and set the right paint for those.
+     */
+    private void updateColors() {
+        XYItemRenderer itemRenderer = chart.getXYPlot().getRenderer();
+        for (int i = 0; i < datasetCollection.getSeriesCount(); i++) {
+            String tag = (String) datasetCollection.getSeriesKey(i);
+            Color color = colors.get(tag);
+            itemRenderer.setSeriesPaint(i, color);
+        }
+    }
+    
+    
+    private Composite createLabelWithLegend(Composite parent, String text, Color color) {
+        Composite top = new Composite(parent, SWT.NONE);
+        GridLayout topLayout = new GridLayout(2, false);
+        topLayout.marginHeight = 0;
+        top.setLayout(topLayout);
+        
+        Label colourBlock = new Label(top, SWT.NONE);
+        colourBlock.setText(LEGEND_COLOUR_BLOCK);
+        
+        // Convert to SWT colour
+        final org.eclipse.swt.graphics.Color swtColour = new org.eclipse.swt.graphics.Color(
+                PlatformUI.getWorkbench().getDisplay(), color.getRed(),
+                color.getGreen(), color.getBlue());
+        colourBlock.addDisposeListener(new DisposeListener() {
+
+            @Override
+            public void widgetDisposed(DisposeEvent e) {
+                swtColour.dispose();
+            }
+        });
+        colourBlock.setForeground(swtColour);
+        
+        Label colourText = new Label(top, SWT.NONE);
+        colourText.setData(ThermostatConstants.TEST_TAG, TEST_ID_LEGEND_ITEM);
+        colourText.setText(text);
+        return top;
+    }
+    
+    private void addLegendItem(final String humanReadableName, final Color color) {
+        // We need to wait for the controls to be fully constructed
+        // before modifying the legend
+        ChartUtils.runAfterCreated(latch, new Runnable() {
+            @Override
+            public void run() {
+                createLabelWithLegend(legendTop, humanReadableName,
+                        color);
+                parent.layout();
+            }
+        });
+    }
+    
+    public JFreeChart getChart() {
+        return chart;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/com.redhat.thermostat.eclipse.chart.common/src/com/redhat/thermostat/eclipse/chart/common/SWTHostCpuViewProvider.java	Thu Oct 25 13:34:48 2012 -0400
@@ -0,0 +1,49 @@
+/*
+ * 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.eclipse.chart.common;
+
+import com.redhat.thermostat.client.core.views.HostCpuView;
+import com.redhat.thermostat.client.core.views.HostCpuViewProvider;
+
+public class SWTHostCpuViewProvider implements HostCpuViewProvider {
+
+    @Override
+    public HostCpuView createView() {
+        return new SWTHostCpuView();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/com.redhat.thermostat.eclipse.test.ui/src/com/redhat/thermostat/eclipse/test/ui/SWTHostCpuViewTest.java	Thu Oct 25 13:34:48 2012 -0400
@@ -0,0 +1,335 @@
+/*
+ * 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.eclipse.test.ui;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
+import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
+import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotLabel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.redhat.thermostat.common.model.DiscreteTimeData;
+import com.redhat.thermostat.eclipse.ThermostatConstants;
+import com.redhat.thermostat.eclipse.chart.common.SWTHostCpuView;
+
+@RunWith(SWTBotJunit4ClassRunner.class)
+public class SWTHostCpuViewTest {
+    private SWTWorkbenchBot bot;
+    private SWTHostCpuView view;
+    private Shell shell;
+
+    @Before
+    public void beforeTest() throws Exception {
+        bot = new SWTWorkbenchBot();
+        
+        Display.getDefault().syncExec(new Runnable() {
+
+            @Override
+            public void run() {
+                shell = new Shell(Display.getCurrent());
+                Composite parent = new Composite(shell, SWT.NONE);
+                parent.setLayout(new GridLayout());
+                parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
+                        true));
+                view = new SWTHostCpuView();
+                view.createControl(parent);
+                shell.open();
+            }
+        });
+    }
+
+    @After
+    public void afterTest() throws Exception {
+        Display.getDefault().syncExec(new Runnable() {
+
+            @Override
+            public void run() {
+                if (shell != null) {
+                    shell.close();
+                    view = null;
+                }
+            }
+        });
+    }
+
+    @Test
+    public void testSetCpuModel() throws Exception {
+        String model = "Test CPU";
+
+        view.setCpuModel(model);
+
+        bot.waitUntil(new DefaultCondition() {
+
+            @Override
+            public boolean test() throws Exception {
+                SWTBotLabel label = bot.labelWithId(
+                        ThermostatConstants.TEST_TAG,
+                        SWTHostCpuView.TEST_ID_CPU_MODEL);
+                return !label.getText().equals("Unknown"); // TODO Externalize
+            }
+
+            @Override
+            public String getFailureMessage() {
+                return "CPU Model label unchanged after set";
+            }
+
+        });
+    }
+
+    @Test
+    public void testSetCpuCount() throws Exception {
+        String count = "8";
+
+        view.setCpuCount(count);
+
+        bot.waitUntil(new DefaultCondition() {
+
+            @Override
+            public boolean test() throws Exception {
+                SWTBotLabel label = bot.labelWithId(
+                        ThermostatConstants.TEST_TAG,
+                        SWTHostCpuView.TEST_ID_CPU_COUNT);
+                return !label.getText().equals("Unknown"); // TODO Externalize
+            }
+
+            @Override
+            public String getFailureMessage() {
+                return "CPU Model label unchanged after set";
+            }
+
+        });
+    }
+
+    @Test
+    public void testAddCpuUsageChart() throws Exception {
+        String humanReadableName = "Test";
+
+        // Test series added
+        addSeries(0, humanReadableName, 1);
+
+        // Verify legend added
+        SWTBotLabel label = bot.labelWithId(ThermostatConstants.TEST_TAG,
+                SWTHostCpuView.TEST_ID_LEGEND_ITEM);
+        assertEquals(humanReadableName, label.getText());
+    }
+
+    private void addSeries(int seriesIndex, String humanReadableName,
+            final int numSeries) {
+        view.addCpuUsageChart(seriesIndex, humanReadableName);
+
+        bot.waitUntil(new DefaultCondition() {
+
+            @Override
+            public boolean test() throws Exception {
+                JFreeChart chart = view.getChart();
+                XYPlot plot = (XYPlot) chart.getPlot();
+                int count = plot.getSeriesCount();
+                return count == numSeries;
+            }
+
+            @Override
+            public String getFailureMessage() {
+                return "Data series never added";
+            }
+        });
+
+        // Wait until legend added
+        bot.labelWithId(ThermostatConstants.TEST_TAG,
+                SWTHostCpuView.TEST_ID_LEGEND_ITEM);
+    }
+
+    @Test
+    public void testAddCpuUsageChartMultiple() throws Exception {
+        String humanReadableName1 = "Test 1";
+        String humanReadableName2 = "Test 2";
+
+        addSeries(0, humanReadableName1, 1);
+        addSeries(1, humanReadableName2, 2);
+
+        // Verify legend added
+        SWTBotLabel label = bot.labelWithId(ThermostatConstants.TEST_TAG,
+                SWTHostCpuView.TEST_ID_LEGEND_ITEM, 0);
+        assertEquals(humanReadableName1, label.getText());
+        label = bot.labelWithId(ThermostatConstants.TEST_TAG,
+                SWTHostCpuView.TEST_ID_LEGEND_ITEM, 1);
+        assertEquals(humanReadableName2, label.getText());
+    }
+
+    @Test
+    public void testAddCpuUsageData() throws Exception {
+        List<DiscreteTimeData<Double>> data = new ArrayList<DiscreteTimeData<Double>>();
+
+        data.add(new DiscreteTimeData<Double>(1000L, 50D));
+        data.add(new DiscreteTimeData<Double>(2000L, 75D));
+        data.add(new DiscreteTimeData<Double>(3000L, 25D));
+
+        addSeries(0, "Test", 1);
+
+        addData(0, data);
+
+        JFreeChart chart = view.getChart();
+        TimeSeriesCollection dataset = (TimeSeriesCollection) chart.getXYPlot()
+                .getDataset();
+
+        assertEquals(1000L, dataset.getX(0, 0));
+        assertEquals(2000L, dataset.getX(0, 1));
+        assertEquals(3000L, dataset.getX(0, 2));
+
+        assertEquals(50D, dataset.getY(0, 0));
+        assertEquals(75D, dataset.getY(0, 1));
+        assertEquals(25D, dataset.getY(0, 2));
+    }
+
+    private void addData(final int seriesIndex,
+            final List<DiscreteTimeData<Double>> data) {
+        view.addCpuUsageData(seriesIndex, data);
+
+        bot.waitUntil(new DefaultCondition() {
+
+            @Override
+            public boolean test() throws Exception {
+                JFreeChart chart = view.getChart();
+                TimeSeriesCollection dataset = (TimeSeriesCollection) chart
+                        .getXYPlot().getDataset();
+                return dataset.getItemCount(seriesIndex) == data.size();
+            }
+
+            @Override
+            public String getFailureMessage() {
+                return "Data not added";
+            }
+        });
+    }
+
+    @Test
+    public void testAddCpuUsageDataMultiple() throws Exception {
+        List<DiscreteTimeData<Double>> data1 = new ArrayList<DiscreteTimeData<Double>>();
+
+        data1.add(new DiscreteTimeData<Double>(1000L, 50D));
+        data1.add(new DiscreteTimeData<Double>(2000L, 75D));
+        data1.add(new DiscreteTimeData<Double>(3000L, 25D));
+
+        List<DiscreteTimeData<Double>> data2 = new ArrayList<DiscreteTimeData<Double>>();
+
+        data2.add(new DiscreteTimeData<Double>(1500L, 30D));
+        data2.add(new DiscreteTimeData<Double>(2500L, 60D));
+        data2.add(new DiscreteTimeData<Double>(3500L, 90D));
+
+        addSeries(0, "Test 1", 1);
+        addSeries(1, "Test 2", 2);
+
+        addData(0, data1);
+        addData(1, data2);
+
+        JFreeChart chart = view.getChart();
+        TimeSeriesCollection dataset = (TimeSeriesCollection) chart.getXYPlot()
+                .getDataset();
+
+        assertEquals(1000L, dataset.getX(0, 0));
+        assertEquals(2000L, dataset.getX(0, 1));
+        assertEquals(3000L, dataset.getX(0, 2));
+
+        assertEquals(50D, dataset.getY(0, 0));
+        assertEquals(75D, dataset.getY(0, 1));
+        assertEquals(25D, dataset.getY(0, 2));
+
+        assertEquals(1500L, dataset.getX(1, 0));
+        assertEquals(2500L, dataset.getX(1, 1));
+        assertEquals(3500L, dataset.getX(1, 2));
+
+        assertEquals(30D, dataset.getY(1, 0));
+        assertEquals(60D, dataset.getY(1, 1));
+        assertEquals(90D, dataset.getY(1, 2));
+    }
+
+    @Test
+    public void testClearCpuUsageData() {
+        List<DiscreteTimeData<Double>> data1 = new ArrayList<DiscreteTimeData<Double>>();
+
+        data1.add(new DiscreteTimeData<Double>(1000L, 50D));
+        data1.add(new DiscreteTimeData<Double>(2000L, 75D));
+        data1.add(new DiscreteTimeData<Double>(3000L, 25D));
+
+        List<DiscreteTimeData<Double>> data2 = new ArrayList<DiscreteTimeData<Double>>();
+
+        data2.add(new DiscreteTimeData<Double>(1500L, 30D));
+        data2.add(new DiscreteTimeData<Double>(2500L, 60D));
+        data2.add(new DiscreteTimeData<Double>(3500L, 90D));
+
+        addSeries(0, "Test 1", 1);
+        addSeries(1, "Test 2", 2);
+
+        addData(0, data1);
+        addData(1, data2);
+
+        view.clearCpuUsageData();
+
+        // Wait until data cleared
+        bot.waitUntil(new DefaultCondition() {
+
+            @Override
+            public boolean test() throws Exception {
+                JFreeChart chart = view.getChart();
+                int count = chart.getXYPlot().getSeriesCount();
+                return count == 0;
+            }
+
+            @Override
+            public String getFailureMessage() {
+                return "Data not cleared";
+            }
+        });
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/com.redhat.thermostat.eclipse.test/src/com/redhat/thermostat/eclipse/test/views/HostCpuViewPartTest.java	Thu Oct 25 13:34:48 2012 -0400
@@ -0,0 +1,136 @@
+/*
+ * 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.eclipse.test.views;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doReturn;
+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 org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Composite;
+import org.junit.Test;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.core.views.HostCpuViewProvider;
+import com.redhat.thermostat.client.ui.HostCpuController;
+import com.redhat.thermostat.common.dao.CpuStatDAO;
+import com.redhat.thermostat.common.dao.HostInfoDAO;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.eclipse.chart.common.HostCpuViewPart;
+import com.redhat.thermostat.eclipse.chart.common.RefViewPart;
+import com.redhat.thermostat.eclipse.chart.common.SWTHostCpuView;
+
+public class HostCpuViewPartTest extends AbstractRefViewPartTest<HostRef> {
+
+    @Test
+    public void testSetFocus() throws Exception {
+        view.createPartControl(parent);
+        view.setFocus();
+
+        verify(parent).setFocus();
+    }
+
+    @Test
+    public void testNoSelection() throws Exception {
+        view.createPartControl(parent);
+        verify(view).createNoSelectionLabel();
+    }
+
+    @Test
+    public void testSelectionBefore() throws Exception {
+        HostRef hostRef = new HostRef("TEST", "Test");
+        mockSelection(hostRef);
+        view.createPartControl(parent);
+
+        verify(view, never()).createNoSelectionLabel();
+
+        verify(thermoView).createControl(any(Composite.class));
+    }
+
+    @Test
+    public void testSelectionAfter() throws Exception {
+        view.createPartControl(parent);
+
+        HostRef hostRef = new HostRef("TEST", "Test");
+        IStructuredSelection selection = mockSelection(hostRef);
+        view.selectionChanged(hostVMView, selection);
+
+        verify(thermoView).createControl(any(Composite.class));
+    }
+
+    @Test
+    public void testSelectionVmRef() throws Exception {
+        view.createPartControl(parent);
+
+        HostRef hostRef = new HostRef("TEST", "Test");
+        VmRef vmRef = new VmRef(hostRef, 0, "Test");
+        IStructuredSelection selection = mockSelection(vmRef);
+        view.selectionChanged(hostVMView, selection);
+
+        verify(thermoView).createControl(any(Composite.class));
+    }
+
+    @Override
+    protected RefViewPart<HostRef> createViewPart() {
+        return new HostCpuViewPart();
+    }
+
+    @Override
+    protected void mockController() {
+        HostCpuController controller = mock(HostCpuController.class);
+        thermoView = mock(SWTHostCpuView.class);
+
+        HostInfoDAO hostInfoDao = mock(HostInfoDAO.class);
+        CpuStatDAO cpuStatDao = mock(CpuStatDAO.class);
+        HostCpuViewProvider viewProvider = mock(HostCpuViewProvider.class);
+        when(osgi.getService(HostInfoDAO.class)).thenReturn(hostInfoDao);
+        when(osgi.getService(CpuStatDAO.class)).thenReturn(cpuStatDao);
+        when(osgi.getService(HostCpuViewProvider.class)).thenReturn(
+                viewProvider);
+
+        doReturn(controller).when(((HostCpuViewPart) view)).createController(
+                same(hostInfoDao), same(cpuStatDao), any(HostRef.class),
+                same(viewProvider));
+        when(controller.getView()).thenReturn((BasicView) thermoView);
+    }
+
+}