changeset 65:ad21cb32aad1

Update many charts asynchronously
author Omair Majid <omajid@redhat.com>
date Wed, 01 Feb 2012 17:00:45 -0500
parents 41210ed840cf
children 1fdd16a78761
files src/com/redhat/thermostat/backend/system/JvmStatVmListener.java src/com/redhat/thermostat/client/HostPanelFacade.java src/com/redhat/thermostat/client/HostPanelFacadeImpl.java src/com/redhat/thermostat/client/VmPanelFacade.java src/com/redhat/thermostat/client/VmPanelFacadeImpl.java src/com/redhat/thermostat/client/strings.properties src/com/redhat/thermostat/client/ui/HostPanel.java src/com/redhat/thermostat/client/ui/RecentTimeSeriesChartController.java src/com/redhat/thermostat/client/ui/RecentTimeSeriesChartPanel.java src/com/redhat/thermostat/client/ui/VmPanel.java
diffstat 10 files changed, 585 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/src/com/redhat/thermostat/backend/system/JvmStatVmListener.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/backend/system/JvmStatVmListener.java	Wed Feb 01 17:00:45 2012 -0500
@@ -68,6 +68,7 @@
     private static final Key vmGcStatTimeStampKey = new Key("timestamp", false);
     private static final Key vmGcStatCollectorKey = new Key("collector", false);
     private static final Key vmGcStatRunCountKey = new Key("runtime-count", false);
+    /** time in microseconds */
     private static final Key vmGCstatWallTimeKey = new Key("wall-time", false);
 
     private static final Key vmMemoryStatVmIdKey = new Key("vm-id", false);
--- a/src/com/redhat/thermostat/client/HostPanelFacade.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/HostPanelFacade.java	Wed Feb 01 17:00:45 2012 -0500
@@ -38,8 +38,9 @@
 
 import javax.swing.table.TableModel;
 
+import org.jfree.data.time.TimeSeriesCollection;
+
 public interface HostPanelFacade extends AsyncUiFacade {
-    public DiscreteTimeData<Double>[] getCpuLoad();
 
     public DiscreteTimeData<Long>[] getMemoryUsage(MemoryType type);
 
@@ -63,4 +64,6 @@
 
     public TableModel getNetworkTableModel();
 
+    public TimeSeriesCollection getCpuLoadDataSet();
+
 }
--- a/src/com/redhat/thermostat/client/HostPanelFacadeImpl.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/HostPanelFacadeImpl.java	Wed Feb 01 17:00:45 2012 -0500
@@ -54,6 +54,10 @@
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableModel;
 
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
 import com.mongodb.BasicDBObject;
 import com.mongodb.DB;
 import com.mongodb.DBCollection;
@@ -80,6 +84,10 @@
     private final DefaultTableModel networkTableModel = new DefaultTableModel();
     private final Vector<String> networkTableColumnVector;
 
+    private final TimeSeriesCollection cpuLoadTimeSeriesCollection = new TimeSeriesCollection();
+    private final TimeSeries cpuLoadSeries = new TimeSeries("cpu-time");
+    private long cpuLoadLastUpdateTime = Long.MIN_VALUE;
+
     private final Timer backgroundUpdateTimer = new Timer();
 
     private Set<MemoryType> toDisplay = new HashSet<MemoryType>();
@@ -98,6 +106,8 @@
         networkTableColumnVector.add(_("NETWORK_IPV4_COLUMN"));
         networkTableColumnVector.add(_("NETWORK_IPV6_COLUMN"));
 
+        cpuLoadTimeSeriesCollection.addSeries(cpuLoadSeries);
+
         toDisplay.addAll(Arrays.asList(MemoryType.values()));
     }
 
@@ -115,8 +125,9 @@
                 memoryTotal.setText((String) hostInfo.get("memory_total"));
 
                 doNetworkTableUpdateAsync();
+
+                doCpuChartUpdateAsync();
             }
-
         }, 0, TimeUnit.SECONDS.toMillis(5));
     }
 
@@ -202,7 +213,7 @@
                     String ifaceName = networkIfaceInfo.getInterfaceName();
                     String ipv4 = networkIfaceInfo.getIp4Addr();
                     String ipv6 = networkIfaceInfo.getIp6Addr();
-                    data.add(new Vector<String>(Arrays.asList(new String[] {ifaceName, ipv4, ipv6})));
+                    data.add(new Vector<String>(Arrays.asList(new String[] { ifaceName, ipv4, ipv6 })));
                 }
                 facade.networkTableModel.setDataVector(data, facade.networkTableColumnVector);
 
@@ -220,26 +231,11 @@
     }
 
     @Override
-    public DiscreteTimeData<Double>[] getCpuLoad() {
-        List<DiscreteTimeData<Double>> load = new ArrayList<DiscreteTimeData<Double>>();
-        DBCursor cursor = cpuStatsCollection.find(new BasicDBObject("agent-id", agent.getAgentId()));
-        long timestamp = 0;
-        double data = 0;
-        while (cursor.hasNext()) {
-            DBObject stat = cursor.next();
-            timestamp = Long.valueOf((String) stat.get("timestamp"));
-            data = Double.valueOf((String) stat.get("5load"));
-            load.add(new DiscreteTimeData<Double>(timestamp, data));
-        }
-        // TODO we may also want to avoid sending out thousands of values.
-        // a subset of values from this entire array should suffice.
-        return (DiscreteTimeData<Double>[]) load.toArray(new DiscreteTimeData<?>[0]);
-    }
-
-    @Override
     public DiscreteTimeData<Long>[] getMemoryUsage(MemoryType type) {
         List<DiscreteTimeData<Long>> data = new ArrayList<DiscreteTimeData<Long>>();
-        DBCursor cursor = memoryStatsCollection.find(new BasicDBObject("agent-id", agent.getAgentId()));
+        BasicDBObject queryObject = new BasicDBObject();
+        queryObject.put("agent-id", agent.getAgentId());
+        DBCursor cursor = memoryStatsCollection.find(queryObject);
         long timestamp = 0;
         long memoryData = 0;
         while (cursor.hasNext()) {
@@ -276,4 +272,94 @@
         }
     }
 
+    @Override
+    public TimeSeriesCollection getCpuLoadDataSet() {
+        return cpuLoadTimeSeriesCollection;
+    }
+
+    private void doCpuChartUpdateAsync() {
+        CpuLoadChartUpdater updater = new CpuLoadChartUpdater(this);
+        updater.execute();
+    }
+
+    private static class CpuLoadChartUpdater extends SwingWorker<DiscreteTimeData<Double>[], Void> {
+
+        private HostPanelFacadeImpl facade;
+
+        public CpuLoadChartUpdater(HostPanelFacadeImpl impl) {
+            this.facade = impl;
+        }
+
+        @Override
+        protected DiscreteTimeData<Double>[] doInBackground() throws Exception {
+            return getCpuLoad(facade.cpuLoadLastUpdateTime);
+        }
+
+        private DiscreteTimeData<Double>[] getCpuLoad(long after) {
+            List<DiscreteTimeData<Double>> load = new ArrayList<DiscreteTimeData<Double>>();
+            BasicDBObject queryObject = new BasicDBObject();
+            queryObject.put("agent-id", facade.agent.getAgentId());
+            if (after != Long.MIN_VALUE) {
+                // TODO once we have an index and the 'column' is of type long, use a
+                // query which can utilize an index. this one doesn't
+                queryObject.put("$where", "this.timestamp > " + after);
+            }
+            DBCursor cursor = facade.cpuStatsCollection.find(queryObject);
+            long timestamp = 0;
+            double data = 0;
+            while (cursor.hasNext()) {
+                DBObject stat = cursor.next();
+                timestamp = Long.valueOf((String) stat.get("timestamp"));
+                data = Double.valueOf((String) stat.get("5load"));
+                load.add(new DiscreteTimeData<Double>(timestamp, data));
+            }
+            // TODO we may also want to avoid sending out thousands of values.
+            // a subset of values from this entire array should suffice.
+            return (DiscreteTimeData<Double>[]) load.toArray(new DiscreteTimeData<?>[0]);
+        }
+
+        @Override
+        protected void done() {
+            try {
+                if (facade.cpuLoadLastUpdateTime == Long.MIN_VALUE) {
+                    /* TODO clear stuff? */
+                    facade.cpuLoadSeries.clear();
+                }
+
+                DiscreteTimeData<Double>[] data = get();
+                appendCpuChartData(data);
+
+            } catch (ExecutionException ee) {
+                ee.printStackTrace();
+            } catch (InterruptedException ie) {
+                ie.printStackTrace();
+            }
+        }
+
+        private void appendCpuChartData(DiscreteTimeData<Double>[] cpuData) {
+            if (cpuData.length > 0) {
+
+                /*
+                 * We have lots of new data to add. we do it in 2 steps:
+                 * 1. Add everything with notify off.
+                 * 2. Notify the chart that there has been a change. It does
+                 * all the expensive computations and redraws itself.
+                 */
+
+                DiscreteTimeData<Double> data;
+                for (int i = 0; i < cpuData.length; i++) {
+                    data = cpuData[i];
+                    facade.cpuLoadLastUpdateTime = Math.max(facade.cpuLoadLastUpdateTime, data.getTimeInMillis());
+                    facade.cpuLoadSeries.add(
+                            new FixedMillisecond(data.getTimeInMillis()), data.getData(),
+                            /* notify = */false);
+                }
+
+                facade.cpuLoadSeries.fireSeriesChanged();
+            }
+        }
+
+    }
+
+
 }
--- a/src/com/redhat/thermostat/client/VmPanelFacade.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/VmPanelFacade.java	Wed Feb 01 17:00:45 2012 -0500
@@ -36,7 +36,9 @@
 
 package com.redhat.thermostat.client;
 
-import com.redhat.thermostat.common.VmMemoryStat;
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.TimeSeriesCollection;
 
 /**
  * Represents information specific to a JVM running on a host somewhere. This is
@@ -44,21 +46,6 @@
  */
 public interface VmPanelFacade extends AsyncUiFacade {
 
-    /**
-     * @return names of the garbage collectors that are operating
-     */
-    public String[] getCollectorNames();
-
-    /**
-     * @param collectorName the name of the garbage collector
-     * @return a list of (time, cumulative time collector has run )
-     */
-    public DiscreteTimeData<Long>[] getCollectorRunTime(String collectorName);
-
-    public String getCollectorGeneration(String collectorName);
-
-    public VmMemoryStat getLatestMemoryInfo();
-
     public ChangeableText getVmPid();
 
     public ChangeableText getMainClass();
@@ -77,4 +64,20 @@
 
     public ChangeableText getVmNameAndVersion();
 
+    /**
+     * @return names of the garbage collectors that are operating
+     */
+    public String[] getCollectorNames();
+
+    public String getCollectorGeneration(String collectorName);
+
+    /**
+     * Returns a {@link TimeSeriesCollection} containing TimeSeries for the collector with a
+     * matching name. The domain is in time ({@link FixedMillisecond}), range is Long, corresponding
+     * to the time spent collecting in microseconds.
+     */
+    public TimeSeriesCollection getCollectorDataSet(String collectorName);
+
+    public DefaultCategoryDataset getCurrentMemory();
+
 }
--- a/src/com/redhat/thermostat/client/VmPanelFacadeImpl.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/VmPanelFacadeImpl.java	Wed Feb 01 17:00:45 2012 -0500
@@ -41,11 +41,21 @@
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
+import javax.swing.SwingWorker;
+
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
 import com.mongodb.BasicDBObject;
 import com.mongodb.DB;
 import com.mongodb.DBCollection;
@@ -76,6 +86,12 @@
     private final ChangeableText vmArguments = new ChangeableText("");
     private final ChangeableText vmNameAndVersion = new ChangeableText("");
 
+    private final DefaultCategoryDataset currentMemoryDataset = new DefaultCategoryDataset();
+
+    private final Map<String, TimeSeriesCollection> collectorSeriesCollection = new HashMap<String, TimeSeriesCollection>();
+    private final Map<String, TimeSeries> collectorSeries = new HashMap<String, TimeSeries>();
+    private final Map<String, Long> collectorSeriesLastUpdateTime = new HashMap<String, Long>();
+
     private final Timer timer = new Timer();
 
     private final DateFormat vmRunningTimeFormat;
@@ -122,7 +138,32 @@
                 vmVersion.setText(actualVmVersion);
                 vmArguments.setText((String) vmInfoObject.get("vm-arguments"));
                 vmNameAndVersion.setText(_("VM_INFO_VM_NAME_AND_VERSION", actualVmName, actualVmVersion));
+
+                String[] collectorNames = getCollectorNames();
+                for (String collectorName: collectorNames) {
+                    TimeSeriesCollection seriesCollection = collectorSeriesCollection.get(collectorName);
+                    if (seriesCollection == null) {
+                        seriesCollection = new TimeSeriesCollection();
+                        collectorSeriesCollection.put(collectorName, seriesCollection);
+                    }
+                    TimeSeries series = collectorSeries.get(collectorName);
+                    if (series == null) {
+                        series = new TimeSeries(collectorName);
+                        collectorSeries.put(collectorName, series);
+                    }
+                    if (seriesCollection.getSeries(collectorName) == null) {
+                        seriesCollection.addSeries(series);
+                    }
+                    if (!collectorSeriesLastUpdateTime.containsKey(collectorName)) {
+                        collectorSeriesLastUpdateTime.put(collectorName, Long.MIN_VALUE);
+                    }
+                }
+
+                doUpdateCurrentMemoryChartAsync();
+                doUpdateCollectorChartsAsync();
+
             }
+
         }, 0, TimeUnit.SECONDS.toMillis(5));
 
     }
@@ -188,24 +229,110 @@
         return collectorNames.toArray(new String[0]);
     }
 
-    @Override
-    public DiscreteTimeData<Long>[] getCollectorRunTime(String collectorName) {
-        ArrayList<DiscreteTimeData<Long>> result = new ArrayList<DiscreteTimeData<Long>>();
-        BasicDBObject queryObject = new BasicDBObject();
-        queryObject.put("agent-id", ref.getAgent().getAgentId());
-        queryObject.put("vm-id", ref.getId());
-        queryObject.put("collector", collectorName);
-        DBCursor cursor = vmGcStatsCollection.find(queryObject);
-        long timestamp;
-        long walltime;
-        while (cursor.hasNext()) {
-            DBObject current = cursor.next();
-            timestamp = Long.valueOf((String) current.get("timestamp"));
-            walltime = Long.valueOf((String) current.get("wall-time"));
-            result.add(new DiscreteTimeData<Long>(timestamp, walltime));
+    private void doUpdateCollectorChartsAsync() {
+        String[] collectorNames = getCollectorNames();
+        for (String name: collectorNames) {
+            CollectorChartUpdater updater = new CollectorChartUpdater(this, name);
+            updater.execute();
+        }
+    }
+
+    public static class CollectorChartUpdater extends SwingWorker<DiscreteTimeData<Double>[], Void> {
+
+        private VmPanelFacadeImpl facade;
+        private String collectorName;
+
+        public CollectorChartUpdater(VmPanelFacadeImpl facade, String collectorName) {
+            this.facade = facade;
+            this.collectorName = collectorName;
+        }
+
+        @Override
+        protected DiscreteTimeData<Double>[] doInBackground() throws Exception {
+            Long after = facade.collectorSeriesLastUpdateTime.get(collectorName);
+            if (after == null) {
+                after = Long.MIN_VALUE;
+            }
+            return getCollectorRunTime(after);
+        }
+
+        private DiscreteTimeData<Double>[] getCollectorRunTime(long after) {
+            ArrayList<DiscreteTimeData<Double>> result = new ArrayList<DiscreteTimeData<Double>>();
+            BasicDBObject queryObject = new BasicDBObject();
+            queryObject.put("agent-id", facade.ref.getAgent().getAgentId());
+            queryObject.put("vm-id", facade.ref.getId());
+            if (after != Long.MIN_VALUE) {
+                // TODO once we have an index and the 'column' is of type long, use a
+                // query which can utilize an index. this one doesn't
+                queryObject.put("$where", "this.timestamp > " + after);
+            }
+            queryObject.put("collector", collectorName);
+            DBCursor cursor = facade.vmGcStatsCollection.find(queryObject);
+            long timestamp;
+            double walltime;
+            while (cursor.hasNext()) {
+                DBObject current = cursor.next();
+                timestamp = Long.valueOf((String) current.get("timestamp"));
+                // convert microseconds to seconds
+                walltime = 1.0E-6 * Long.valueOf((String) current.get("wall-time"));
+                result.add(new DiscreteTimeData<Double>(timestamp, walltime));
+            }
+
+            return (DiscreteTimeData<Double>[]) result.toArray(new DiscreteTimeData<?>[0]);
         }
 
-        return (DiscreteTimeData<Long>[]) result.toArray(new DiscreteTimeData<?>[0]);
+        @Override
+        protected void done() {
+            try {
+                Long after = facade.collectorSeriesLastUpdateTime.get(collectorName);
+                if (after == null) {
+                    after = Long.MIN_VALUE;
+                }
+                after = appendCollectorDataToChart(get(), facade.collectorSeries.get(collectorName), after);
+                facade.collectorSeriesLastUpdateTime.put(collectorName, after);
+            } catch (ExecutionException ee) {
+                ee.printStackTrace();
+            } catch (InterruptedException ie) {
+                ie.printStackTrace();
+            }
+        }
+
+        private long appendCollectorDataToChart(DiscreteTimeData<Double>[] collectorData, TimeSeries collectorSeries, long prevMaxTime) {
+            long maxTime = prevMaxTime;
+
+            if (collectorData.length > 0) {
+
+                /*
+                 * We have lots of new data to add. we do it in 2 steps:
+                 * 1. Add everything with notify off.
+                 * 2. Notify the chart that there has been a change. It
+                 * does all the expensive computations and redraws itself.
+                 */
+
+                DiscreteTimeData<Double> data;
+                for (int i = 0; i < collectorData.length; i++) {
+                    data = collectorData[i];
+                    maxTime = Math.max(maxTime, data.getTimeInMillis());
+                    collectorSeries.add(
+                            new FixedMillisecond(data.getTimeInMillis()), data.getData(),
+                            /* notify = */false);
+                }
+
+                collectorSeries.fireSeriesChanged();
+            }
+
+            return maxTime;
+        }
+    }
+
+    @Override
+    public TimeSeriesCollection getCollectorDataSet(String collectorName) {
+        TimeSeriesCollection seriesCollection = collectorSeriesCollection.get(collectorName);
+        if (seriesCollection == null) {
+            seriesCollection = new TimeSeriesCollection();
+            collectorSeriesCollection.put(collectorName, seriesCollection);
+        }
+        return seriesCollection;
     }
 
     @Override
@@ -221,8 +348,48 @@
         return _("UNKNOWN_GEN");
     }
 
-    @Override
-    public VmMemoryStat getLatestMemoryInfo() {
+    private void doUpdateCurrentMemoryChartAsync() {
+        UpdateCurrentMemory worker = new UpdateCurrentMemory(this);
+        worker.execute();
+    }
+
+    private static class UpdateCurrentMemory extends SwingWorker<VmMemoryStat, Void> {
+
+        private final VmPanelFacadeImpl facade;
+
+        public UpdateCurrentMemory(VmPanelFacadeImpl facade) {
+            this.facade = facade;
+        }
+
+        @Override
+        protected VmMemoryStat doInBackground() throws Exception {
+            return facade.getLatestMemoryInfo();
+        }
+
+        @Override
+        protected void done() {
+            try {
+                VmMemoryStat info = get();
+                DefaultCategoryDataset dataset = facade.currentMemoryDataset;
+                List<Generation> generations = info.getGenerations();
+                for (Generation generation: generations) {
+                    List<Space> spaces = generation.spaces;
+                    for (Space space: spaces) {
+                        dataset.addValue(space.used, _("VM_CURRENT_MEMORY_CHART_USED"), space.name);
+                        dataset.addValue(space.capacity - space.used, _("VM_CURRENT_MEMORY_CHART_CAPACITY"), space.name);
+                        dataset.addValue(space.maxCapacity - space.capacity, _("VM_CURRENT_MEMORY_CHART_MAX_CAPACITY"), space.name);
+                    }
+                }
+            } catch (InterruptedException ie) {
+                ie.printStackTrace();
+            } catch (ExecutionException ee) {
+                ee.printStackTrace();
+            }
+        }
+
+    }
+
+    private VmMemoryStat getLatestMemoryInfo() {
         BasicDBObject query = new BasicDBObject();
         query.put("agent-id", ref.getAgent().getAgentId());
         query.put("vm-id", ref.getId());
@@ -267,7 +434,7 @@
             space.name = spaceName;
             space.capacity = Long.valueOf((String)info.get("capacity"));
             space.maxCapacity = Long.valueOf((String)info.get("max-capacity"));
-            space.used = Long.valueOf((String)info.get("used"));
+            space.used = Long.valueOf((String) info.get("used"));
             if (target.spaces == null) {
                 target.spaces = new ArrayList<Space>();
             }
@@ -278,4 +445,9 @@
         return cached;
     }
 
+    @Override
+    public DefaultCategoryDataset getCurrentMemory() {
+        return currentMemoryDataset;
+    }
+
 }
--- a/src/com/redhat/thermostat/client/strings.properties	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/strings.properties	Wed Feb 01 17:00:45 2012 -0500
@@ -30,6 +30,11 @@
 UNKNOWN_GEN = Unknown
 SOME_GENERATION = {0} Generation
 
+SECONDS = Seconds
+MINUTES = Minutes
+HOURS = Hours
+DAYS = Days
+
 STARTUP_MODE_SELECTION_DIALOG_TITLE = Welcome to Thermostat!
 STARTUP_MODE_SELECTION_INTRO = Which JVMs to do you want to monitor?
 STARTUP_MODE_SELECTION_TYPE_LOCAL = Local
@@ -75,9 +80,8 @@
 NETWORK_IPV6_COLUMN = IPv6 Address
 
 HOST_CPU_SECTION_OVERVIEW = Processor
-HOST_CPU_USAGE_CHART_TITLE = Cpu Usage
 HOST_CPU_USAGE_CHART_TIME_LABEL = Time
-HOST_CPU_USAGE_CHART_VALUE_LABEL = Usage
+HOST_CPU_USAGE_CHART_VALUE_LABEL = Avg Load
 
 HOST_MEMORY_SECTION_OVERVIEW = Memory
 HOST_MEMORY_CHART_TITLE = Memory
@@ -119,4 +123,4 @@
 
 VM_GC_COLLECTOR_OVER_GENERATION = Collector {0} running on {1}
 VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL = Time
-VM_GC_COLLECTOR_CHART_GC_TIME_LABEL = Total Time Spent on GC
+VM_GC_COLLECTOR_CHART_GC_TIME_LABEL = Total Time Spent on GC (s)
--- a/src/com/redhat/thermostat/client/ui/HostPanel.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/ui/HostPanel.java	Wed Feb 01 17:00:45 2012 -0500
@@ -56,8 +56,6 @@
 import org.jfree.chart.ChartFactory;
 import org.jfree.chart.ChartPanel;
 import org.jfree.chart.JFreeChart;
-import org.jfree.data.time.FixedMillisecond;
-import org.jfree.data.time.TimeSeries;
 import org.jfree.data.time.TimeSeriesCollection;
 import org.jfree.data.xy.XYSeries;
 import org.jfree.data.xy.XYSeriesCollection;
@@ -174,26 +172,15 @@
         table.setBorder(Components.smallBorder());
         contentArea.add(table, c);
 
-        DiscreteTimeData<Double>[] cpuData = facade.getCpuLoad();
-        TimeSeries series = new TimeSeries("cpu-load");
-        for (DiscreteTimeData<Double> data : cpuData) {
-            series.add(new FixedMillisecond(data.getTimeInMillis()), data.getData());
-        }
-        TimeSeriesCollection dataset = new TimeSeriesCollection();
-        dataset.addSeries(series);
+        TimeSeriesCollection dataset = facade.getCpuLoadDataSet();
         JFreeChart chart = ChartFactory.createTimeSeriesChart(
-                _("HOST_CPU_USAGE_CHART_TITLE"),
+                null,
                 _("HOST_CPU_USAGE_CHART_TIME_LABEL"),
                 _("HOST_CPU_USAGE_CHART_VALUE_LABEL"),
                 dataset,
                 false, false, false);
 
-        ChartPanel chartPanel = new ChartPanel(chart);
-        // make this chart non-interactive
-        chartPanel.setDisplayToolTips(true);
-        chartPanel.setDoubleBuffered(true);
-        chartPanel.setMouseZoomable(false);
-        chartPanel.setPopupMenu(null);
+        JPanel chartPanel = new RecentTimeSeriesChartPanel(new RecentTimeSeriesChartController(chart));
 
         c.gridy++;
         c.fill = GridBagConstraints.BOTH;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/RecentTimeSeriesChartController.java	Wed Feb 01 17:00:45 2012 -0500
@@ -0,0 +1,99 @@
+/*
+ * 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 java.util.concurrent.TimeUnit;
+
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+
+public class RecentTimeSeriesChartController {
+
+    private final int DEFAULT_VALUE = 10;
+    private final TimeUnit DEFAULT_UNIT = TimeUnit.MINUTES;
+
+    private JFreeChart chart;
+    private ChartPanel panel;
+    private int timeValue = DEFAULT_VALUE;
+    private TimeUnit timeUnit = DEFAULT_UNIT;
+
+    public RecentTimeSeriesChartController(JFreeChart chart) {
+        this.chart = chart;
+        this.panel = new ChartPanel(chart);
+
+        // instead of just disabling display of tooltips, disable their generation too
+        if (chart.getPlot() instanceof XYPlot) {
+            chart.getXYPlot().getRenderer().setBaseToolTipGenerator(null);
+        }
+
+        chart.getXYPlot().getDomainAxis().setAutoRange(true);
+        chart.getXYPlot().getDomainAxis().setFixedAutoRange(timeUnit.toMillis(timeValue));
+
+        chart.getXYPlot().getRangeAxis().setAutoRange(true);
+
+    }
+
+    public ChartPanel getChartPanel() {
+        return panel;
+    }
+
+    public TimeUnit[] getTimeUnits() {
+        return new TimeUnit[] { TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES };
+    }
+
+    public int getTimeValue() {
+        return timeValue;
+    }
+
+    public TimeUnit getTimeUnit() {
+        return timeUnit;
+    }
+
+    public void setTime(int value, TimeUnit unit) {
+        this.timeValue = value;
+        this.timeUnit = unit;
+
+        updateChart();
+    }
+
+    private void updateChart() {
+        chart.getXYPlot().getDomainAxis().setAutoRange(true);
+        chart.getXYPlot().getDomainAxis().setFixedAutoRange(timeUnit.toMillis(timeValue));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/RecentTimeSeriesChartPanel.java	Wed Feb 01 17:00:45 2012 -0500
@@ -0,0 +1,152 @@
+/*
+ * 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 java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+
+import org.jfree.chart.ChartPanel;
+
+public class RecentTimeSeriesChartPanel extends JPanel {
+
+    private static final long serialVersionUID = -1733906800911900456L;
+
+    private final RecentTimeSeriesChartController controller;
+
+    public RecentTimeSeriesChartPanel(RecentTimeSeriesChartController controller) {
+        this.controller = controller;
+
+        this.setLayout(new BorderLayout());
+
+        ChartPanel cp = controller.getChartPanel();
+
+        cp.setDisplayToolTips(false);
+        cp.setDoubleBuffered(true);
+        cp.setMouseZoomable(false);
+        cp.setPopupMenu(null);
+
+        add(cp);
+        add(getChartControls(), BorderLayout.SOUTH);
+    }
+
+    private Component getChartControls() {
+        JPanel container = new JPanel();
+
+        final JTextField durationSelector = new JTextField(5);
+        final JComboBox unitSelector = new JComboBox(controller.getTimeUnits());
+
+        int defaultValue = controller.getTimeValue();
+        TimeUnit defaultUnit = controller.getTimeUnit();
+
+        TimeUnitChangeListener timeUnitChangeListener = new TimeUnitChangeListener(controller, defaultValue, defaultUnit);
+
+        durationSelector.getDocument().addDocumentListener(timeUnitChangeListener);
+        unitSelector.addActionListener(timeUnitChangeListener);
+
+        durationSelector.setText(String.valueOf(defaultValue));
+        unitSelector.setSelectedItem(defaultUnit);
+
+        container.add(new JLabel("Display the most recent"));
+        container.add(durationSelector);
+        container.add(unitSelector);
+
+        return container;
+    }
+
+    private static class TimeUnitChangeListener implements DocumentListener, ActionListener {
+
+        private final RecentTimeSeriesChartController controller;
+        private int value;
+        private TimeUnit unit;
+
+        public TimeUnitChangeListener(RecentTimeSeriesChartController controller, int defaultValue, TimeUnit defaultUnit) {
+            this.controller = controller;
+            this.value = defaultValue;
+            this.unit = defaultUnit;
+        }
+
+        @Override
+        public void removeUpdate(DocumentEvent event) {
+            changed(event.getDocument());
+        }
+
+        @Override
+        public void insertUpdate(DocumentEvent event) {
+            changed(event.getDocument());
+        }
+
+        @Override
+        public void changedUpdate(DocumentEvent event) {
+            changed(event.getDocument());
+        }
+
+        private void changed(Document doc) {
+            try {
+                this.value = Integer.valueOf(doc.getText(0, doc.getLength()));
+            } catch (NumberFormatException nfe) {
+                // ignore
+            } catch (BadLocationException ble) {
+                // ignore
+            }
+            updateChartParameters();
+        }
+
+        private void updateChartParameters() {
+            controller.setTime(value, unit);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            JComboBox comboBox = (JComboBox) e.getSource();
+            TimeUnit time = (TimeUnit) comboBox.getSelectedItem();
+            this.unit = time;
+            updateChartParameters();
+        }
+    }
+}
--- a/src/com/redhat/thermostat/client/ui/VmPanel.java	Wed Feb 01 15:17:25 2012 -0500
+++ b/src/com/redhat/thermostat/client/ui/VmPanel.java	Wed Feb 01 17:00:45 2012 -0500
@@ -53,17 +53,11 @@
 import org.jfree.chart.JFreeChart;
 import org.jfree.chart.plot.PlotOrientation;
 import org.jfree.data.category.DefaultCategoryDataset;
-import org.jfree.data.time.FixedMillisecond;
-import org.jfree.data.time.TimeSeries;
 import org.jfree.data.time.TimeSeriesCollection;
 
-import com.redhat.thermostat.client.DiscreteTimeData;
 import com.redhat.thermostat.client.VmPanelFacade;
 import com.redhat.thermostat.client.ui.SimpleTable.Section;
 import com.redhat.thermostat.client.ui.SimpleTable.TableEntry;
-import com.redhat.thermostat.common.VmMemoryStat;
-import com.redhat.thermostat.common.VmMemoryStat.Generation;
-import com.redhat.thermostat.common.VmMemoryStat.Space;
 
 public class VmPanel extends JPanel {
 
@@ -147,18 +141,7 @@
     }
 
     private Component createCurrentMemoryDisplay() {
-        DefaultCategoryDataset data = new DefaultCategoryDataset();
-
-        VmMemoryStat info = facade.getLatestMemoryInfo();
-        List<Generation> generations = info.getGenerations();
-        for (Generation generation : generations) {
-            List<Space> spaces = generation.spaces;
-            for (Space space : spaces) {
-                data.addValue(space.used, _("VM_CURRENT_MEMORY_CHART_USED"), space.name);
-                data.addValue(space.capacity - space.used, _("VM_CURRENT_MEMORY_CHART_CAPACITY"), space.name);
-                data.addValue(space.maxCapacity - space.capacity, _("VM_CURRENT_MEMORY_CHART_MAX_CAPACITY"), space.name);
-            }
-        }
+        DefaultCategoryDataset data = facade.getCurrentMemory();
 
         JFreeChart chart = ChartFactory.createStackedBarChart(
                 null,
@@ -215,13 +198,7 @@
 
         detailsPanel.add(Components.header(_("VM_GC_COLLECTOR_OVER_GENERATION", collectorName, facade.getCollectorGeneration(collectorName))), BorderLayout.NORTH);
 
-        DiscreteTimeData<Long>[] cpuData = facade.getCollectorRunTime(collectorName);
-        TimeSeries series = new TimeSeries("gc-runs");
-        for (DiscreteTimeData<Long> data : cpuData) {
-            series.add(new FixedMillisecond(data.getTimeInMillis()), data.getData());
-        }
-        TimeSeriesCollection dataset = new TimeSeriesCollection();
-        dataset.addSeries(series);
+        TimeSeriesCollection dataset = facade.getCollectorDataSet(collectorName);
         JFreeChart chart = ChartFactory.createTimeSeriesChart(
                 null,
                 _("VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL"),
@@ -229,12 +206,7 @@
                 dataset,
                 false, false, false);
 
-        ChartPanel chartPanel = new ChartPanel(chart);
-        // make this chart non-interactive
-        chartPanel.setDisplayToolTips(true);
-        chartPanel.setDoubleBuffered(true);
-        chartPanel.setMouseZoomable(false);
-        chartPanel.setPopupMenu(null);
+        JPanel chartPanel = new RecentTimeSeriesChartPanel(new RecentTimeSeriesChartController(chart));
 
         detailsPanel.add(chartPanel, BorderLayout.CENTER);