changeset 1576:a614e6dec69a

The vm-class-stats tab should fetch less data Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011470.html PR2020
author Lukasz Dracz <ldracz@redhat.com>
date Fri, 28 Nov 2014 15:47:39 -0500
parents 91e6e6bc3be9
children bd5e7855f9f3
files client/core/pom.xml client/core/src/main/java/com/redhat/thermostat/client/core/experimental/Duration.java client/core/src/main/java/com/redhat/thermostat/client/core/experimental/SingleValueStat.java client/core/src/main/java/com/redhat/thermostat/client/core/experimental/SingleValueSupplier.java client/core/src/main/java/com/redhat/thermostat/client/core/experimental/TimeRangeComputer.java client/core/src/main/java/com/redhat/thermostat/client/core/experimental/TimeRangeController.java client/core/src/test/java/com/redhat/thermostat/client/core/experimental/TimeRangeComputerTest.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/RecentTimeSeriesChartPanel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/SingleValueChartPanel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimeUnitChangeListener.java distribution/config/commands/vm-stat.properties vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatView.java vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatController.java vm-classstat/client-core/src/test/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatControllerTest.java vm-classstat/client-swing/src/main/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanel.java vm-classstat/common/pom.xml vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuView.java vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/TimeRangeComputer.java vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuController.java vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/TimeRangeComputerTest.java vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuControllerTest.java vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuChartPanel.java vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuPanel.java vm-cpu/common/pom.xml vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java vm-cpu/distribution/thermostat-plugin.xml
diffstat 30 files changed, 1025 insertions(+), 592 deletions(-) [+]
line wrap: on
line diff
--- a/client/core/pom.xml	Fri Nov 28 13:48:29 2014 -0500
+++ b/client/core/pom.xml	Fri Nov 28 15:47:39 2014 -0500
@@ -121,6 +121,7 @@
             <Export-Package>
               com.redhat.thermostat.client.ui,
               com.redhat.thermostat.client.locale,
+              com.redhat.thermostat.client.core.experimental,
               <!-- Interfaces for views, controllers, etc. -->
               com.redhat.thermostat.client.core,
               com.redhat.thermostat.client.core.progress,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/Duration.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2014 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.core.experimental;
+
+import java.util.concurrent.TimeUnit;
+
+public class Duration {
+    public final int value;
+    public final TimeUnit unit;
+
+    public Duration(int value, TimeUnit unit) {
+        this.value = value;
+        this.unit = unit;
+    }
+
+    @Override
+    public String toString() {
+        return value + " " + unit;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/SingleValueStat.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012-2014 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.core.experimental;
+
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+
+public interface SingleValueStat<T> extends TimeStampedPojo {
+
+    public T getValue();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/SingleValueSupplier.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012-2014 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.core.experimental;
+
+import com.redhat.thermostat.storage.core.VmRef;
+
+import java.util.List;
+
+public interface SingleValueSupplier<T> {
+
+    public abstract List<T> getStats(VmRef ref, long since, long to);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/TimeRangeComputer.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012-2014 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.core.experimental;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.redhat.thermostat.common.model.Range;
+
+/**
+ * Compute additional data ranges to fetch when trying to update a currently
+ * displayed time-data with additional time-data.
+ */
+public class TimeRangeComputer {
+
+    public static List<Range<Long>> computeAdditionalRangesToFetch(Range<Long> availableRange, Range<Long> desiredRange, Range<Long> displayedRange) {
+
+        // TODO find out how to show 'sampled' values across a
+        // very, very large desiredRange
+
+        // Do we need to show additional data?
+        if (displayedRange.equals(desiredRange)) {
+            return Collections.emptyList();
+        }
+
+        // Do we already have all the data?
+        if (contains(displayedRange, desiredRange)) {
+            // TODO 'narrow' view to the desired range
+            return Collections.emptyList();
+        }
+
+        // What's the best that we can do with the desired range?
+        Range<Long> closestInterval = closestInterval(availableRange, desiredRange);
+
+        List<Range<Long>> results = new ArrayList<>();
+        if (closestInterval.getMin() < displayedRange.getMin()) {
+            if (closestInterval.getMax() < displayedRange.getMin()) {
+                results.add(closestInterval);
+            } else if (closestInterval.getMax() < displayedRange.getMax()) {
+                results.add(new Range<>(closestInterval.getMin(), displayedRange.getMin()));
+            } else {
+                results.add(new Range<>(closestInterval.getMin(), displayedRange.getMin()));
+                results.add(new Range<>(displayedRange.getMax(), closestInterval.getMax()));
+            }
+        } else if (closestInterval.getMin() < displayedRange.getMax()) {
+            if (closestInterval.getMax() < displayedRange.getMax()) {
+                // nothing to do here
+            } else if (closestInterval.getMax() > displayedRange.getMax()) {
+                results.add(new Range<>(displayedRange.getMax(), closestInterval.getMax()));
+            }
+        } else {
+            results.add(closestInterval);
+        }
+        return results;
+    }
+
+    private static boolean contains(Range<Long> larger, Range<Long> smaller) {
+        return (larger.getMin() <= smaller.getMin()) && (larger.getMax() >= smaller.getMax());
+    }
+
+    private static Range<Long> closestInterval(Range<Long> avilable, Range<Long> desired) {
+        long min = Math.max(avilable.getMin(), desired.getMin());
+        long max = Math.min(avilable.getMax(), desired.getMax());
+        return new Range<>(min, max);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/TimeRangeController.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2014 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.core.experimental;
+
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.model.DiscreteTimeData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TimeRangeController {
+
+    private Range<Long> availableRange = new Range<>(Long.MAX_VALUE, Long.MIN_VALUE);
+    private Range<Long> displayedRange = new Range<>(Long.MAX_VALUE, Long.MIN_VALUE);
+    private List<DiscreteTimeData<Number>> allToDisplay;
+
+    public void update(Duration userDesiredDuration,Range<Long> newAvailableRange, SingleValueSupplier dao, VmRef ref) {
+        long now = System.currentTimeMillis();
+        long userVisibleTimeDelta = (userDesiredDuration.unit.toMillis(userDesiredDuration.value));
+        Range<Long> desiredRange = new Range<>(now - userVisibleTimeDelta, now);
+
+        if (availableRange.equals(newAvailableRange)) {
+            return;
+        }
+
+        availableRange = newAvailableRange;
+
+        List<Range<Long>> additionalIntervals = TimeRangeComputer.computeAdditionalRangesToFetch(availableRange, desiredRange, displayedRange);
+
+        long displayedMin = Math.min(displayedRange.getMin(), Long.MAX_VALUE);
+        long displayedMax = Math.max(displayedRange.getMax(), Long.MIN_VALUE);
+
+        allToDisplay = new ArrayList<>();
+        for (Range<Long> interval : additionalIntervals) {
+            List<SingleValueStat> stats = dao.getStats(ref, interval.getMin(), interval.getMax());
+            allToDisplay.addAll(getDiscreteTimeData(stats));
+
+            displayedMin = Math.min(displayedMin, interval.getMin());
+            displayedMax = Math.max(displayedMax, interval.getMax());
+        }
+
+        // the view does not show data older than the delta
+        displayedMin = Math.max(displayedMin, displayedMax - userVisibleTimeDelta);
+
+        displayedRange = new Range<>(displayedMin, displayedMax);
+    }
+
+    public List<DiscreteTimeData<Number>> getDataToDisplay() {
+        return new ArrayList<>(allToDisplay);
+    }
+
+    private List<DiscreteTimeData<Number>> getDiscreteTimeData(List<SingleValueStat> stats) {
+        List<DiscreteTimeData<Number>> toDisplay = new ArrayList<>(stats.size());
+
+        for (SingleValueStat stat : stats) {
+            DiscreteTimeData<Number> data =
+                    new DiscreteTimeData<>(stat.getTimeStamp(),(Number) stat.getValue());
+            toDisplay.add(data);
+        }
+        return toDisplay;
+    }
+
+    public Range<Long> getAvailableRange() {
+        return availableRange;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/test/java/com/redhat/thermostat/client/core/experimental/TimeRangeComputerTest.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012-2014 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.core.experimental;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.model.Range;
+
+public class TimeRangeComputerTest {
+
+    @Test
+    public void verifyComputesEmptyResultIfAllDataIsAvaialable() {
+        Range<Long> available = new Range<>(1l, 10l);
+        Range<Long> desired = new Range<>(2l, 3l);
+        Range<Long> displayed = new Range<>(1l, 10l);
+
+        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void verifyComputesRangeBeforeCorrectly() {
+        Range<Long> available = new Range<>(1l, 10l);
+        Range<Long> desired = new Range<>(1l, 7l);
+        Range<Long> displayed = new Range<>(5l, 10l);
+
+        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
+
+        assertEquals(1, result.size());
+        assertEquals(new Range<>(1l,5l), result.get(0));
+    }
+
+    @Test
+    public void verifyComputesRangeAfterCorrectly() {
+        Range<Long> available = new Range<>(1l, 10l);
+        Range<Long> desired = new Range<>(2l, 6l);
+        Range<Long> displayed = new Range<>(1l, 5l);
+
+        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
+
+        assertEquals(1, result.size());
+        assertEquals(new Range<>(5l,6l), result.get(0));
+    }
+
+    @Test
+    public void verifyComputesOverlappingRangeCorrectly() {
+        Range<Long> available = new Range<>(1l, 10l);
+        Range<Long> desired = new Range<>(2l, 8l);
+        Range<Long> displayed = new Range<>(4l, 6l);
+
+        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
+
+        assertEquals(2, result.size());
+        assertEquals(new Range<>(2l,4l), result.get(0));
+        assertEquals(new Range<>(6l, 8l), result.get(1));
+    }
+
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/RecentTimeSeriesChartPanel.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/RecentTimeSeriesChartPanel.java	Fri Nov 28 15:47:39 2014 -0500
@@ -40,8 +40,6 @@
 import java.awt.Component;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.util.concurrent.TimeUnit;
 
 import javax.swing.DefaultComboBoxModel;
@@ -50,12 +48,10 @@
 import javax.swing.JPanel;
 import javax.swing.JTextField;
 import javax.swing.SwingUtilities;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.swing.components.experimental.TimeUnitChangeListener;
 import org.jfree.chart.ChartPanel;
 
 import com.redhat.thermostat.client.locale.LocaleResources;
@@ -125,7 +121,13 @@
         int defaultValue = controller.getTimeValue();
         TimeUnit defaultUnit = controller.getTimeUnit();
 
-        TimeUnitChangeListener timeUnitChangeListener = new TimeUnitChangeListener(controller, defaultValue, defaultUnit);
+        TimeUnitChangeListener timeUnitChangeListener = new TimeUnitChangeListener(new com.redhat.thermostat.common.ActionListener() {
+            @Override
+            public void actionPerformed(final com.redhat.thermostat.common.ActionEvent actionEvent) {
+                Duration d = (Duration) actionEvent.getPayload();
+                controller.setTime(d.value, d.unit);
+            }
+        }, defaultValue, defaultUnit);
 
         durationSelector.getDocument().addDocumentListener(timeUnitChangeListener);
         unitSelector.addActionListener(timeUnitChangeListener);
@@ -166,56 +168,5 @@
         });
     }
 
-    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) {
-            @SuppressWarnings("unchecked") // We are a TimeUnitChangeListener, specifically.
-            JComboBox<TimeUnit> comboBox = (JComboBox<TimeUnit>) e.getSource();
-            TimeUnit time = (TimeUnit) comboBox.getSelectedItem();
-            this.unit = time;
-            updateChartParameters();
-        }
-    }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/SingleValueChartPanel.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2012-2014 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.swing.components.experimental;
+
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.swing.components.ValueField;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.shared.locale.Translate;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.XYPlot;
+
+import javax.swing.text.JTextComponent;
+import java.util.concurrent.TimeUnit;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+
+
+public class SingleValueChartPanel extends JPanel {
+
+    static final TimeUnit[] DEFAULT_TIMEUNITS = new TimeUnit[] { TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES };
+
+    public static final String PROPERTY_VISIBLE_TIME_RANGE = "visibleTimeRange";
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private static final int MINIMUM_DRAW_SIZE = 100;
+
+    private ChartPanel chartPanel;
+
+    private JPanel labelContainer;
+    private JTextComponent label;
+
+    public SingleValueChartPanel(JFreeChart chart, Duration duration) {
+
+        this.setLayout(new BorderLayout());
+
+        // instead of just disabling display of tooltips, disable their generation too
+        if (chart.getPlot() instanceof XYPlot) {
+            chart.getXYPlot().getRenderer().setBaseToolTipGenerator(null);
+        }
+
+        chart.getXYPlot().getRangeAxis().setAutoRange(true);
+
+        chart.getXYPlot().getDomainAxis().setAutoRange(true);
+        chart.getXYPlot().getDomainAxis().setFixedAutoRange(duration.unit.toMillis(duration.value));
+
+        chartPanel = new ChartPanel(chart);
+
+        chartPanel.setDisplayToolTips(false);
+        chartPanel.setDoubleBuffered(true);
+        chartPanel.setMouseZoomable(false);
+        chartPanel.setPopupMenu(null);
+
+        /*
+         * By default, ChartPanel scales itself instead of redrawing things when
+         * it's resized. To have it resize automatically, we need to set minimum
+         * and maximum sizes. Lets constrain the minimum, but not the maximum
+         * size.
+         */
+        chartPanel.setMinimumDrawHeight(MINIMUM_DRAW_SIZE);
+        chartPanel.setMaximumDrawHeight(Integer.MAX_VALUE);
+        chartPanel.setMinimumDrawWidth(MINIMUM_DRAW_SIZE);
+        chartPanel.setMaximumDrawWidth(Integer.MAX_VALUE);
+
+        add(chartPanel, BorderLayout.CENTER);
+        add(getControlsAndAdditionalDisplay(duration), BorderLayout.SOUTH);
+
+    }
+
+    private Component getControlsAndAdditionalDisplay(Duration duration) {
+        JPanel container = new JPanel();
+        container.setOpaque(false);
+
+        container.setLayout(new BorderLayout());
+
+        container.add(getChartControls(duration), BorderLayout.LINE_START);
+        container.add(getAdditionalDataDisplay(), BorderLayout.LINE_END);
+
+        return container;
+    }
+
+    private Component getChartControls(Duration duration) {
+        JPanel container = new JPanel();
+        container.setOpaque(false);
+
+        final JTextField durationSelector = new JTextField(5);
+        final JComboBox<TimeUnit> unitSelector = new JComboBox<>();
+        unitSelector.setModel(new DefaultComboBoxModel<>(DEFAULT_TIMEUNITS));
+
+        TimeUnitChangeListener timeUnitChangeListener = new TimeUnitChangeListener(new ActionListener() {
+            @Override
+            public void actionPerformed(final com.redhat.thermostat.common.ActionEvent actionEvent) {
+                Duration d = (Duration) actionEvent.getPayload();
+                SingleValueChartPanel.this.firePropertyChange(PROPERTY_VISIBLE_TIME_RANGE, null, d);
+            }
+        }, duration.value, duration.unit);
+
+        durationSelector.getDocument().addDocumentListener(timeUnitChangeListener);
+        unitSelector.addActionListener(timeUnitChangeListener);
+
+        durationSelector.setText(String.valueOf(duration.value));
+        unitSelector.setSelectedItem(duration.unit);
+
+        container.add(new JLabel(translator.localize(LocaleResources.CHART_DURATION_SELECTOR_LABEL).getContents()));
+        container.add(durationSelector);
+        container.add(unitSelector);
+
+        return container;
+    }
+
+    private Component getAdditionalDataDisplay() {
+        JPanel panel = new JPanel(new GridBagLayout());
+        panel.setOpaque(false);
+        labelContainer = new JPanel();
+        labelContainer.setOpaque(false);
+        GridBagConstraints constraints = new GridBagConstraints();
+        constraints.fill = GridBagConstraints.BOTH;
+        constraints.anchor = GridBagConstraints.CENTER;
+        panel.add(labelContainer, constraints);
+        return panel;
+    }
+
+    public void setTimeRangeToShow(int timeValue, TimeUnit timeUnit) {
+        XYPlot plot = chartPanel.getChart().getXYPlot();
+
+        // Don't drop old data; just dont' show it.
+        plot.getDomainAxis().setAutoRange(true);
+        plot.getDomainAxis().setFixedAutoRange(timeUnit.toMillis(timeValue));
+    }
+
+    public void setDataInformationLabel(final String text) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                if (label == null) {
+                    label = new ValueField(text);
+                    labelContainer.add(label);
+                }
+
+                label.setText(text);
+            }
+        });
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimeUnitChangeListener.java	Fri Nov 28 15:47:39 2014 -0500
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012-2014 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.swing.components.experimental;
+
+import javax.swing.JComboBox;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+
+public class TimeUnitChangeListener implements DocumentListener, java.awt.event.ActionListener {
+
+    public enum TimeChangeEvent {
+        TIME_CHANGE_EVENT;
+    }
+
+
+    private final ActionListener listener;
+    private int value;
+    private TimeUnit unit;
+
+    public TimeUnitChangeListener(ActionListener listener, int defaultValue, TimeUnit defaultUnit) {
+        this.listener = listener;
+        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
+        }
+        fireTimeChanged();
+    }
+
+    private void fireTimeChanged() {
+        ActionEvent e = new ActionEvent(this, TimeChangeEvent.TIME_CHANGE_EVENT);
+        e.setPayload(new Duration(this.value, this.unit));
+        listener.actionPerformed(e);
+    }
+
+    @Override
+    public void actionPerformed(final java.awt.event.ActionEvent e) {
+        @SuppressWarnings("unchecked") // We are a TimeUnitChangeListener, specifically.
+                JComboBox<TimeUnit> comboBox = (JComboBox<TimeUnit>) e.getSource();
+        TimeUnit time = (TimeUnit) comboBox.getSelectedItem();
+        this.unit = time;
+        fireTimeChanged();
+    }
+
+}
\ No newline at end of file
--- a/distribution/config/commands/vm-stat.properties	Fri Nov 28 13:48:29 2014 -0500
+++ b/distribution/config/commands/vm-stat.properties	Fri Nov 28 15:47:39 2014 -0500
@@ -2,6 +2,7 @@
           com.redhat.thermostat.storage.mongodb=${project.version}, \
           com.redhat.thermostat.web.common=${project.version}, \
           com.redhat.thermostat.web.client=${project.version}, \
+          com.redhat.thermostat.client.core=${project.version}, \
           org.apache.httpcomponents.httpcore=${httpcomponents.core.version}, \
           org.apache.httpcomponents.httpclient=${httpcomponents.client.version}, \
           ${osgi.compendium.bundle.symbolic-name}=${osgi.compendium.osgi-version}, \
--- a/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatView.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatView.java	Fri Nov 28 15:47:39 2014 -0500
@@ -37,13 +37,31 @@
 package com.redhat.thermostat.vm.classstat.client.core;
 
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 
 public abstract class VmClassStatView extends BasicView implements UIComponent {
 
+    public enum UserAction {
+        USER_CHANGED_TIME_RANGE,
+    }
+
+    public abstract void addUserActionListener(ActionListener<UserAction> listener);
+
+    public abstract void removeUserActionListener(ActionListener<UserAction> listener);
+
+    public abstract Duration getUserDesiredDuration();
+
+    public abstract void setVisibleDataRange(int time, TimeUnit unit);
+
+    public abstract void setAvailableDataRange(Range<Long> availableDataRange);
+
     public abstract void clearClassCount();
 
     public abstract void addClassCount(List<DiscreteTimeData<Long>> data);
--- a/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatController.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatController.java	Fri Nov 28 15:47:39 2014 -0500
@@ -43,16 +43,20 @@
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.client.core.views.BasicView.Action;
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.core.experimental.TimeRangeController;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.NotImplementedException;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.storage.model.DiscreteTimeData;
+import com.redhat.thermostat.client.core.experimental.SingleValueSupplier;
+import com.redhat.thermostat.client.core.experimental.SingleValueStat;
 import com.redhat.thermostat.vm.classstat.client.core.VmClassStatView;
 import com.redhat.thermostat.vm.classstat.client.core.VmClassStatViewProvider;
 import com.redhat.thermostat.vm.classstat.client.locale.LocaleResources;
@@ -63,19 +67,46 @@
 
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
+    private TimeRangeController timeRangeController;
+
     private class UpdateChartData implements Runnable {
         @Override
         public void run() {
-            long timeStamp = lastSeenTimeStamp;
-            List<VmClassStat> latestClassStats = dao.getLatestClassStats(ref, timeStamp);
-            List<DiscreteTimeData<Long>> timeData = new ArrayList<>();
-            for (VmClassStat stat : latestClassStats) {
-                timeData.add(new DiscreteTimeData<Long>(stat.getTimeStamp(), stat.getLoadedClasses()));
-                timeStamp = Math.max(timeStamp, stat.getTimeStamp());
-            }
-            classesView.addClassCount(timeData);
-            lastSeenTimeStamp = timeStamp;
+
+            VmClassStat oldest = dao.getOldest(ref);
+            VmClassStat latest = dao.getLatest(ref);
+
+            Range<Long> newAvailableRange = new Range<>(oldest.getTimeStamp(), latest.getTimeStamp());
+
+            SingleValueSupplier singleValueSupplier = new SingleValueSupplier() {
+                @Override
+                public List getStats(final VmRef ref, final long since, final long to) {
+                    List<VmClassStat> stats = dao.getClassStats(ref, since, to);
+                    List<SingleValueStat<Long>> singleValueStats = new ArrayList<>();
+                    for (final VmClassStat stat : stats ) {
+                        singleValueStats.add(new SingleValueStat<Long>() {
+                            @Override
+                            public Long getValue() {
+                                return stat.getLoadedClasses();
+                            }
+
+                            @Override
+                            public long getTimeStamp() {
+                                return stat.getTimeStamp();
+                            }
+                        });
+                    }
+                    return singleValueStats;
+                }
+            };
+
+            timeRangeController.update(userDesiredDuration, newAvailableRange, singleValueSupplier, ref);
+            classesView.setAvailableDataRange(timeRangeController.getAvailableRange());
+
+            List classCount = timeRangeController.getDataToDisplay();
+            classesView.addClassCount(classCount);
         }
+
     }
 
     private final VmClassStatView classesView;
@@ -83,7 +114,7 @@
     private final VmClassStatDAO dao;
     private final Timer timer;
 
-    private volatile long lastSeenTimeStamp = Long.MIN_VALUE;
+    private Duration userDesiredDuration;
 
     public VmClassStatController(ApplicationService appSvc, VmClassStatDAO vmClassStatDao, VmRef ref, VmClassStatViewProvider viewProvider) {
         this.ref = ref;
@@ -113,6 +144,27 @@
                 }
             }
         });
+
+        classesView.addUserActionListener(new ActionListener<VmClassStatView.UserAction>() {
+
+            @Override
+            public void actionPerformed(final ActionEvent<VmClassStatView.UserAction> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case USER_CHANGED_TIME_RANGE:
+                    Duration duration = classesView.getUserDesiredDuration();
+                    userDesiredDuration = duration;
+                    classesView.setVisibleDataRange(duration.value, duration.unit);
+                    break;
+                default:
+                    throw new AssertionError("Unhandled action type");
+                }
+            }
+        });
+
+        userDesiredDuration = classesView.getUserDesiredDuration();
+
+        timeRangeController = new TimeRangeController();
+
     }
 
     private void start() {
--- a/vm-classstat/client-core/src/test/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatControllerTest.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/client-core/src/test/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatControllerTest.java	Fri Nov 28 15:47:39 2014 -0500
@@ -37,7 +37,6 @@
 package com.redhat.thermostat.vm.classstat.client.core.internal;
 
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -45,7 +44,9 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -71,10 +72,13 @@
         stats.add(stat1);
 
         VmClassStatDAO vmClassStatDAO = mock(VmClassStatDAO.class);
-        when(vmClassStatDAO.getLatestClassStats(any(VmRef.class), anyInt())).thenReturn(stats).thenReturn(new ArrayList<VmClassStat>());
 
         VmRef ref = mock(VmRef.class);
 
+        when(vmClassStatDAO.getLatestClassStats(any(VmRef.class), any(Long.class))).thenThrow(new AssertionError("Unbounded queries are bad!"));
+        when(vmClassStatDAO.getOldest(ref)).thenReturn(stat1);
+        when(vmClassStatDAO.getLatest(ref)).thenReturn(stat1);
+
         Timer timer = mock(Timer.class);
         ArgumentCaptor<Runnable> timerActionCaptor = ArgumentCaptor.forClass(Runnable.class);
         doNothing().when(timer).setAction(timerActionCaptor.capture());
@@ -87,7 +91,9 @@
         VmClassStatView view = mock(VmClassStatView.class);
         ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(view).addActionListener(viewArgumentCaptor.capture());
-        
+
+        when(view.getUserDesiredDuration()).thenReturn(new Duration(1, TimeUnit.MINUTES));
+
         VmClassStatViewProvider viewProvider = mock(VmClassStatViewProvider.class);
         when(viewProvider.createView()).thenReturn(view);
 
--- a/vm-classstat/client-swing/src/main/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanel.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/client-swing/src/main/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanel.java	Fri Nov 28 15:47:39 2014 -0500
@@ -37,12 +37,17 @@
 package com.redhat.thermostat.vm.classstat.client.swing;
 
 import java.awt.Component;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
-import javax.swing.JComponent;
 import javax.swing.SwingUtilities;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.swing.components.experimental.SingleValueChartPanel;
+import com.redhat.thermostat.common.model.Range;
 import org.jfree.chart.ChartFactory;
 import org.jfree.chart.JFreeChart;
 import org.jfree.chart.axis.NumberAxis;
@@ -56,9 +61,7 @@
 
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
-import com.redhat.thermostat.client.swing.components.RecentTimeSeriesChartPanel;
 import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
-import com.redhat.thermostat.client.ui.RecentTimeSeriesChartController;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.shared.locale.Translate;
@@ -70,10 +73,19 @@
 
     private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
 
+    private static final int DEFAULT_VALUE = 10;
+    private static final TimeUnit DEFAULT_UNIT = TimeUnit.MINUTES;
+
+    private Duration duration;
+
     private HeaderPanel visiblePanel;
     
     private final TimeSeriesCollection dataset = new TimeSeriesCollection();
 
+    private SingleValueChartPanel chartPanel;
+
+    private ActionNotifier<UserAction> userActionActionNotifier = new ActionNotifier<VmClassStatView.UserAction>(this);
+
     private final ActionNotifier<Action> notifier = new ActionNotifier<Action>(this);
 
     public VmClassStatPanel() {
@@ -81,6 +93,8 @@
         // any name works
         dataset.addSeries(new TimeSeries("class-stat"));
 
+        duration = new Duration(DEFAULT_VALUE, DEFAULT_UNIT);
+
         visiblePanel.setHeader(t.localize(LocaleResources.VM_LOADED_CLASSES));
 
         JFreeChart chart = ChartFactory.createTimeSeriesChart(
@@ -104,10 +118,18 @@
         axis.setRangeType(RangeType.POSITIVE);
         axis.setAutoRangeMinimumSize(10);
 
-        JComponent chartPanel = new RecentTimeSeriesChartPanel(new RecentTimeSeriesChartController(chart));
+        chartPanel = new SingleValueChartPanel(chart, duration);
 
         visiblePanel.setContent(chartPanel);
 
+        chartPanel.addPropertyChangeListener(SingleValueChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
+            @Override
+            public void propertyChange(final PropertyChangeEvent evt) {
+                duration = (Duration) evt.getNewValue();
+                userActionActionNotifier.fireAction(UserAction.USER_CHANGED_TIME_RANGE);
+            }
+        });
+
         new ComponentVisibilityNotifier().initialize(visiblePanel, notifier);
     }
 
@@ -141,6 +163,31 @@
     }
 
     @Override
+    public void addUserActionListener(final ActionListener<UserAction> listener) {
+        userActionActionNotifier.addActionListener(listener);
+    }
+
+    @Override
+    public void removeUserActionListener(final ActionListener<UserAction> listener) {
+        userActionActionNotifier.removeActionListener(listener);
+    }
+
+    @Override
+    public Duration getUserDesiredDuration() {
+        return duration;
+    }
+
+    @Override
+    public void setVisibleDataRange(final int time, final TimeUnit unit) {
+        chartPanel.setTimeRangeToShow(time, unit);
+    }
+
+    @Override
+    public void setAvailableDataRange(final Range<Long> availableDataRange) {
+        // FIXME indicate the total data range to the user somehow
+    }
+
+    @Override
     public void clearClassCount() {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
--- a/vm-classstat/common/pom.xml	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/common/pom.xml	Fri Nov 28 15:47:39 2014 -0500
@@ -124,5 +124,10 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 </project>
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Fri Nov 28 15:47:39 2014 -0500
@@ -56,7 +56,13 @@
 
     public List<VmClassStat> getLatestClassStats(VmRef ref, long since);
 
+    public List<VmClassStat> getClassStats(VmRef ref, long since, long to);
+
     public void putVmClassStat(VmClassStat stat);
 
+    public abstract VmClassStat getOldest(VmRef ref);
+
+    public abstract VmClassStat getLatest(VmRef ref);
+
 }
 
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java	Fri Nov 28 15:47:39 2014 -0500
@@ -41,6 +41,7 @@
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.PreparedStatement;
@@ -49,6 +50,7 @@
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
 import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.core.VmTimeIntervalPojoListGetter;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 import com.redhat.thermostat.vm.classstat.common.model.VmClassStat;
 
@@ -65,13 +67,35 @@
                  "'" + Key.TIMESTAMP.getName() + "' = ?l , " +
                  "'" + loadedClassesKey.getName() + "' = ?l";
 
+    // LATEST vm-cpu-stats WHERE 'agentId' = ?s AND \
+    //                        'vmId' = ?s \
+    //                        SORT 'timeStamp' ASC  \
+    //                        LIMIT 1
+    static final String DESC_OLDEST_VM_CLASS_STAT = "QUERY " + vmClassStatsCategory.getName() +
+            " WHERE '" + Key.AGENT_ID.getName() + "' = ?s " +
+            "   AND '" + Key.VM_ID.getName() + "' = ?s " +
+            "  SORT '" + Key.TIMESTAMP.getName() + "' ASC " +
+            "  LIMIT 1";
+
+    // LATEST vm-cpu-stats WHERE 'agentId' = ?s AND \
+    //                        'vmId' = ?s \
+    //                        SORT 'timeStamp' DSC  \
+    //                        LIMIT 1
+    static final String DESC_LATEST_VM_CLASS_STAT = "QUERY " + vmClassStatsCategory.getName() +
+            " WHERE '" + Key.AGENT_ID.getName() + "' = ?s " +
+            "   AND '" + Key.VM_ID.getName() + "' = ?s " +
+            "  SORT '" + Key.TIMESTAMP.getName() + "' DSC " +
+            "  LIMIT 1";
+
     private final Storage storage;
     private final VmLatestPojoListGetter<VmClassStat> getter;
+    private final VmTimeIntervalPojoListGetter<VmClassStat> otherGetter;
 
     VmClassStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(vmClassStatsCategory);
         this.getter = new VmLatestPojoListGetter<>(storage, vmClassStatsCategory);
+        this.otherGetter = new VmTimeIntervalPojoListGetter<>(storage, vmClassStatsCategory);
     }
 
     @Override
@@ -80,6 +104,11 @@
     }
 
     @Override
+    public List<VmClassStat> getClassStats(VmRef ref, long since, long to) {
+        return otherGetter.getLatest(ref, since, to);
+    }
+
+    @Override
     public void putVmClassStat(VmClassStat stat) {
         StatementDescriptor<VmClassStat> desc = new StatementDescriptor<>(vmClassStatsCategory, DESC_ADD_VM_CLASS_STAT);
         PreparedStatement<VmClassStat> prepared;
@@ -96,5 +125,34 @@
             logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e);
         }
     }
+
+    @Override
+    public VmClassStat getOldest(final VmRef ref) {
+        return runAgentAndVmIdQuery(ref, DESC_OLDEST_VM_CLASS_STAT);
+    }
+
+    @Override
+    public VmClassStat getLatest(final VmRef ref) {
+        return runAgentAndVmIdQuery(ref, DESC_LATEST_VM_CLASS_STAT);
+    }
+
+    private VmClassStat runAgentAndVmIdQuery(final VmRef ref, final String descriptor) {
+        StatementDescriptor<VmClassStat> desc = new StatementDescriptor<>(vmClassStatsCategory, descriptor);
+        PreparedStatement<VmClassStat> prepared;
+        try {
+            prepared = storage.prepareStatement(desc);
+            prepared.setString(0, ref.getHostRef().getAgentId());
+            prepared.setString(1, ref.getVmId());
+            Cursor<VmClassStat> cursor = prepared.executeQuery();
+            if (cursor.hasNext()) {
+                return cursor.next();
+            }
+        } catch (DescriptorParsingException e) {
+            logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e);
+        }
+        return null;
+    }
 }
 
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java	Fri Nov 28 15:47:39 2014 -0500
@@ -41,6 +41,7 @@
 
 import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.VmLatestPojoListGetter;
+import com.redhat.thermostat.storage.core.VmTimeIntervalPojoListGetter;
 import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
@@ -53,13 +54,18 @@
 public class VmClassStatDAOImplStatementDescriptorRegistration implements
         StatementDescriptorRegistration {
     
-    static final String QUERY = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
+    static final String LATEST = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT,
+            VmClassStatDAO.vmClassStatsCategory.getName());
+    static final String RANGE = String.format(VmTimeIntervalPojoListGetter.VM_INTERVAL_QUERY_FORMAT,
             VmClassStatDAO.vmClassStatsCategory.getName());
 
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>(1);
-        descs.add(QUERY);
+        descs.add(LATEST);
+        descs.add(RANGE);
+        descs.add(VmClassStatDAOImpl.DESC_LATEST_VM_CLASS_STAT);
+        descs.add(VmClassStatDAOImpl.DESC_OLDEST_VM_CLASS_STAT);
         descs.add(VmClassStatDAOImpl.DESC_ADD_VM_CLASS_STAT);
         return descs;
     }
--- a/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java	Fri Nov 28 15:47:39 2014 -0500
@@ -58,10 +58,10 @@
     public void registersAllDescriptors() {
         VmClassStatDAOImplStatementDescriptorRegistration reg = new VmClassStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(2, descriptors.size());
+        assertEquals(5, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
-    
+
     /*
      * The web storage end-point uses service loader in order to determine the
      * list of trusted/known registrations. This test is to ensure service loading
@@ -86,8 +86,8 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(vmClassStatReg);
-        assertEquals(2, vmClassStatReg.getStatementDescriptors().size());
+        assertEquals(5, vmClassStatReg.getStatementDescriptors().size());
     }
-    
+
 }
 
--- a/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuView.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuView.java	Fri Nov 28 15:47:39 2014 -0500
@@ -41,27 +41,13 @@
 
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 
 public abstract class VmCpuView extends BasicView implements UIComponent {
 
-    public static class Duration {
-        public final int value;
-        public final TimeUnit unit;
-
-        public Duration(int value, TimeUnit unit) {
-            this.value = value;
-            this.unit = unit;
-        }
-
-        @Override
-        public String toString() {
-            return value + " " + unit;
-        }
-    }
-
     public enum UserAction {
         USER_CHANGED_TIME_RANGE,
     }
--- a/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/TimeRangeComputer.java	Fri Nov 28 13:48:29 2014 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * Copyright 2012-2014 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.cpu.client.core.internal;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.redhat.thermostat.common.model.Range;
-
-/**
- * Compute additional data ranges to fetch when trying to update a currently
- * displayed time-data with additional time-data.
- */
-public class TimeRangeComputer {
-
-    public static List<Range<Long>> computeAdditionalRangesToFetch(Range<Long> availableRange, Range<Long> desiredRange, Range<Long> displayedRange) {
-
-        // TODO find out how to show 'sampled' values across a
-        // very, very large desiredRange
-
-        // Do we need to show additional data?
-        if (displayedRange.equals(desiredRange)) {
-            return Collections.emptyList();
-        }
-
-        // Do we already have all the data?
-        if (contains(displayedRange, desiredRange)) {
-            // TODO 'narrow' view to the desired range
-            return Collections.emptyList();
-        }
-
-        // What's the best that we can do with the desired range?
-        Range<Long> closestInterval = closestInterval(availableRange, desiredRange);
-
-        List<Range<Long>> results = new ArrayList<>();
-        if (closestInterval.getMin() < displayedRange.getMin()) {
-            if (closestInterval.getMax() < displayedRange.getMin()) {
-                results.add(closestInterval);
-            } else if (closestInterval.getMax() < displayedRange.getMax()) {
-                results.add(new Range<>(closestInterval.getMin(), displayedRange.getMin()));
-            } else {
-                results.add(new Range<>(closestInterval.getMin(), displayedRange.getMin()));
-                results.add(new Range<>(displayedRange.getMax(), closestInterval.getMax()));
-            }
-        } else if (closestInterval.getMin() < displayedRange.getMax()) {
-            if (closestInterval.getMax() < displayedRange.getMax()) {
-                // nothing to do here
-            } else if (closestInterval.getMax() > displayedRange.getMax()) {
-                results.add(new Range<>(displayedRange.getMax(), closestInterval.getMax()));
-            }
-        } else {
-            results.add(closestInterval);
-        }
-        return results;
-    }
-
-    private static boolean contains(Range<Long> larger, Range<Long> smaller) {
-        return (larger.getMin() <= smaller.getMin()) && (larger.getMax() >= smaller.getMax());
-    }
-
-    private static Range<Long> closestInterval(Range<Long> avilable, Range<Long> desired) {
-        long min = Math.max(avilable.getMin(), desired.getMin());
-        long max = Math.min(avilable.getMax(), desired.getMax());
-        return new Range<>(min, max);
-    }
-
-}
--- a/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuController.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuController.java	Fri Nov 28 15:47:39 2014 -0500
@@ -37,13 +37,14 @@
 package com.redhat.thermostat.vm.cpu.client.core.internal;
 
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
 import com.redhat.thermostat.client.core.views.BasicView.Action;
 import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.core.experimental.TimeRangeController;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
@@ -54,9 +55,9 @@
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.storage.model.DiscreteTimeData;
+import com.redhat.thermostat.client.core.experimental.SingleValueSupplier;
+import com.redhat.thermostat.client.core.experimental.SingleValueStat;
 import com.redhat.thermostat.vm.cpu.client.core.VmCpuView;
-import com.redhat.thermostat.vm.cpu.client.core.VmCpuView.Duration;
 import com.redhat.thermostat.vm.cpu.client.core.VmCpuViewProvider;
 import com.redhat.thermostat.vm.cpu.client.core.VmCpuView.UserAction;
 import com.redhat.thermostat.vm.cpu.client.locale.LocaleResources;
@@ -73,10 +74,9 @@
 
     private final Timer timer;
 
-    private Range<Long> availableRange = new Range<Long>(Long.MAX_VALUE, Long.MIN_VALUE);
-    private Range<Long> displayedRange = new Range<Long>(Long.MAX_VALUE, Long.MIN_VALUE);
+    private Duration userDesiredDuration;
 
-    private Duration userDesiredDuration;
+    private TimeRangeController timeRangeController;
 
     public VmCpuController(ApplicationService appSvc, VmCpuStatDAO vmCpuStatDao, VmRef ref, VmCpuViewProvider provider) {
         this.ref = ref;
@@ -118,7 +118,7 @@
             public void actionPerformed(ActionEvent<UserAction> actionEvent) {
                 switch (actionEvent.getActionId()) {
                 case USER_CHANGED_TIME_RANGE:
-                    Duration duration = (Duration) view.getUserDesiredDuration();
+                    Duration duration = view.getUserDesiredDuration();
                     userDesiredDuration = duration;
                     view.setVisibleDataRange(duration.value, duration.unit);
                     break;
@@ -129,6 +129,8 @@
         });
 
         userDesiredDuration = view.getUserDesiredDuration();
+
+        timeRangeController = new TimeRangeController();
     }
 
     private void start() {
@@ -136,54 +138,38 @@
     }
 
     private void updateData() {
-        long now = System.currentTimeMillis();
-        long userVisibleTimeDelta = (userDesiredDuration.unit.toMillis(userDesiredDuration.value));
-        Range<Long> desiredRange = new Range<Long>(now - userVisibleTimeDelta, now);
-
         VmCpuStat oldest = dao.getOldest(ref);
         VmCpuStat latest = dao.getLatest(ref);
 
         Range<Long> newAvailableRange = new Range<>(oldest.getTimeStamp(), latest.getTimeStamp());
 
-        if (availableRange.equals(newAvailableRange)) {
-            return;
-        }
-
-        availableRange = newAvailableRange;
-
-        view.setAvailableDataRange(availableRange);
-
-        List<Range<Long>> additionalIntervals = TimeRangeComputer.computeAdditionalRangesToFetch(availableRange, desiredRange, displayedRange);
-
-        long displayedMin = Math.min(displayedRange.getMin(), Long.MAX_VALUE);
-        long displayedMax = Math.max(displayedRange.getMax(), Long.MIN_VALUE);
-
-        for (Range<Long> interval : additionalIntervals) {
-            List<VmCpuStat> stats = dao.getVmCpuStats(ref, interval.getMin(), interval.getMax());
-            appendToVmCpuCharts(stats);
+        SingleValueSupplier singleValueSupplier = new SingleValueSupplier() {
+            @Override
+            public List getStats(final VmRef ref, final long since, final long to) {
+                List<VmCpuStat> stats = dao.getVmCpuStats(ref, since, to);
+                List<SingleValueStat<Double>> singleValueStats = new ArrayList<>();
+                for (final VmCpuStat stat : stats ) {
+                    singleValueStats.add(new SingleValueStat<Double>() {
+                        @Override
+                        public Double getValue() {
+                            return stat.getCpuLoad();
+                        }
 
-            displayedMin = Math.min(displayedMin, interval.getMin());
-            displayedMax = Math.max(displayedMax, interval.getMax());
-        }
-
-        // the view does not show data older than the delta
-        displayedMin = Math.max(displayedMin, displayedMax - userVisibleTimeDelta);
-
-        // System.out.println("Wanted to display " + new Date(desiredRange.getMin()) + " to " + new Date(desiredRange.getMax()));
-        displayedRange = new Range<>(displayedMin, displayedMax);
-        // System.out.println("Displayed from " + new Date(displayedRange.getMin()) + " to " + new Date(displayedRange.getMax()));
+                        @Override
+                        public long getTimeStamp() {
+                            return stat.getTimeStamp();
+                        }
+                    });
+                }
+                return singleValueStats;
+           }
+        };
 
-    }
+        timeRangeController.update(userDesiredDuration, newAvailableRange, singleValueSupplier, ref);
+        view.setAvailableDataRange(timeRangeController.getAvailableRange());
 
-    private void appendToVmCpuCharts(List<VmCpuStat> stats) {
-        List<DiscreteTimeData<? extends Number>> toDisplay = new ArrayList<>(stats.size());
-        for (VmCpuStat stat: stats) {
-            DiscreteTimeData<? extends Number> data =
-                    new DiscreteTimeData<Number>(stat.getTimeStamp(), stat.getCpuLoad());
-            toDisplay.add(data);
-        }
-
-        view.addData(toDisplay);
+        List data = timeRangeController.getDataToDisplay();
+        view.addData(data);
     }
 
     private void stop() {
--- a/vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/TimeRangeComputerTest.java	Fri Nov 28 13:48:29 2014 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/*
- * Copyright 2012-2014 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.cpu.client.core.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.List;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.common.model.Range;
-
-public class TimeRangeComputerTest {
-
-    @Test
-    public void verifyComputesEmptyResultIfAllDataIsAvaialable() {
-        Range<Long> available = new Range<>(1l, 10l);
-        Range<Long> desired = new Range<>(2l, 3l);
-        Range<Long> displayed = new Range<>(1l, 10l);
-
-        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
-
-        assertTrue(result.isEmpty());
-    }
-
-    @Test
-    public void verifyComputesRangeBeforeCorrectly() {
-        Range<Long> available = new Range<>(1l, 10l);
-        Range<Long> desired = new Range<>(1l, 7l);
-        Range<Long> displayed = new Range<>(5l, 10l);
-
-        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
-
-        assertEquals(1, result.size());
-        assertEquals(new Range<>(1l,5l), result.get(0));
-    }
-
-    @Test
-    public void verifyComputesRangeAfterCorrectly() {
-        Range<Long> available = new Range<>(1l, 10l);
-        Range<Long> desired = new Range<>(2l, 6l);
-        Range<Long> displayed = new Range<>(1l, 5l);
-
-        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
-
-        assertEquals(1, result.size());
-        assertEquals(new Range<>(5l,6l), result.get(0));
-    }
-
-    @Test
-    public void verifyComputesOverlappingRangeCorrectly() {
-        Range<Long> available = new Range<>(1l, 10l);
-        Range<Long> desired = new Range<>(2l, 8l);
-        Range<Long> displayed = new Range<>(4l, 6l);
-
-        List<Range<Long>> result = TimeRangeComputer.computeAdditionalRangesToFetch(available, desired, displayed);
-
-        assertEquals(2, result.size());
-        assertEquals(new Range<>(2l,4l), result.get(0));
-        assertEquals(new Range<>(6l, 8l), result.get(1));
-    }
-
-}
--- a/vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuControllerTest.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuControllerTest.java	Fri Nov 28 15:47:39 2014 -0500
@@ -49,6 +49,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
@@ -56,7 +57,6 @@
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.vm.cpu.client.core.VmCpuView;
-import com.redhat.thermostat.vm.cpu.client.core.VmCpuView.Duration;
 import com.redhat.thermostat.vm.cpu.client.core.VmCpuViewProvider;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
 import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat;
--- a/vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuChartPanel.java	Fri Nov 28 13:48:29 2014 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,243 +0,0 @@
-/*
- * Copyright 2012-2014 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.vm.cpu.client.swing.internal;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JTextField;
-import javax.swing.SwingUtilities;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.Document;
-import javax.swing.text.JTextComponent;
-
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.plot.XYPlot;
-
-import com.redhat.thermostat.client.locale.LocaleResources;
-import com.redhat.thermostat.client.swing.components.ValueField;
-import com.redhat.thermostat.shared.locale.Translate;
-import com.redhat.thermostat.vm.cpu.client.core.VmCpuView;
-import com.redhat.thermostat.vm.cpu.client.core.VmCpuView.Duration;
-
-public class VmCpuChartPanel extends JPanel {
-
-    static final TimeUnit[] DEFAULT_TIMEUNITS = new TimeUnit[] { TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES };
-
-    public static final String PROPERTY_VISIBLE_TIME_RANGE = "visibleTimeRange";
-
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private static final long serialVersionUID = -1733906800911900456L;
-    private static final int MINIMUM_DRAW_SIZE = 100;
-
-    private ChartPanel chartPanel;
-
-    private JPanel labelContainer;
-    private JTextComponent label;
-
-    public VmCpuChartPanel(JFreeChart chart, Duration duration) {
-
-        this.setLayout(new BorderLayout());
-
-        // instead of just disabling display of tooltips, disable their generation too
-        if (chart.getPlot() instanceof XYPlot) {
-            chart.getXYPlot().getRenderer().setBaseToolTipGenerator(null);
-        }
-
-        chart.getXYPlot().getRangeAxis().setAutoRange(true);
-
-        chart.getXYPlot().getDomainAxis().setAutoRange(true);
-        chart.getXYPlot().getDomainAxis().setFixedAutoRange(duration.unit.toMillis(duration.value));
-
-        chartPanel = new ChartPanel(chart);
-
-        chartPanel.setDisplayToolTips(false);
-        chartPanel.setDoubleBuffered(true);
-        chartPanel.setMouseZoomable(false);
-        chartPanel.setPopupMenu(null);
-
-        /*
-         * By default, ChartPanel scales itself instead of redrawing things when
-         * it's resized. To have it resize automatically, we need to set minimum
-         * and maximum sizes. Lets constrain the minimum, but not the maximum
-         * size.
-         */
-        chartPanel.setMinimumDrawHeight(MINIMUM_DRAW_SIZE);
-        chartPanel.setMaximumDrawHeight(Integer.MAX_VALUE);
-        chartPanel.setMinimumDrawWidth(MINIMUM_DRAW_SIZE);
-        chartPanel.setMaximumDrawWidth(Integer.MAX_VALUE);
-
-        add(chartPanel, BorderLayout.CENTER);
-        add(getControlsAndAdditionalDisplay(duration), BorderLayout.SOUTH);
-
-    }
-
-    private Component getControlsAndAdditionalDisplay(Duration duration) {
-        JPanel container = new JPanel();
-        container.setOpaque(false);
-
-        container.setLayout(new BorderLayout());
-
-        container.add(getChartControls(duration), BorderLayout.LINE_START);
-        container.add(getAdditionalDataDisplay(), BorderLayout.LINE_END);
-
-        return container;
-    }
-
-    private Component getChartControls(Duration duration) {
-        JPanel container = new JPanel();
-        container.setOpaque(false);
-
-        final JTextField durationSelector = new JTextField(5);
-        final JComboBox<TimeUnit> unitSelector = new JComboBox<>();
-        unitSelector.setModel(new DefaultComboBoxModel<>(DEFAULT_TIMEUNITS));
-
-        TimeUnitChangeListener timeUnitChangeListener = new TimeUnitChangeListener(duration.value, duration.unit);
-
-        durationSelector.getDocument().addDocumentListener(timeUnitChangeListener);
-        unitSelector.addActionListener(timeUnitChangeListener);
-
-        durationSelector.setText(String.valueOf(duration.value));
-        unitSelector.setSelectedItem(duration.unit);
-
-        container.add(new JLabel(translator.localize(LocaleResources.CHART_DURATION_SELECTOR_LABEL).getContents()));
-        container.add(durationSelector);
-        container.add(unitSelector);
-
-        return container;
-    }
-
-    private Component getAdditionalDataDisplay() {
-        JPanel panel = new JPanel(new GridBagLayout());
-        panel.setOpaque(false);
-        labelContainer = new JPanel();
-        labelContainer.setOpaque(false);
-        GridBagConstraints constraints = new GridBagConstraints();
-        constraints.fill = GridBagConstraints.BOTH;
-        constraints.anchor = GridBagConstraints.CENTER;
-        panel.add(labelContainer, constraints);
-        return panel;
-    }
-
-    public void setTimeRangeToShow(int timeValue, TimeUnit timeUnit) {
-        XYPlot plot = chartPanel.getChart().getXYPlot();
-
-        // Don't drop old data; just dont' show it.
-        plot.getDomainAxis().setAutoRange(true);
-        plot.getDomainAxis().setFixedAutoRange(timeUnit.toMillis(timeValue));
-    }
-
-    public void setDataInformationLabel(final String text) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                if (label == null) {
-                    label = new ValueField(text);
-                    labelContainer.add(label);
-                }
-
-                label.setText(text);
-            }
-        });
-    }
-
-    private class TimeUnitChangeListener implements DocumentListener, ActionListener {
-
-        private int value;
-        private TimeUnit unit;
-
-        public TimeUnitChangeListener(int defaultValue, TimeUnit defaultUnit) {
-            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
-            }
-            chartChanged();
-        }
-
-        private void chartChanged() {
-            VmCpuChartPanel.this.firePropertyChange(PROPERTY_VISIBLE_TIME_RANGE, null, new VmCpuView.Duration(value, unit));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            @SuppressWarnings("unchecked") // We are a TimeUnitChangeListener, specifically.
-            JComboBox<TimeUnit> comboBox = (JComboBox<TimeUnit>) e.getSource();
-            TimeUnit time = (TimeUnit) comboBox.getSelectedItem();
-            this.unit = time;
-            chartChanged();
-        }
-    }
-
-
-}
-
--- a/vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuPanel.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuPanel.java	Fri Nov 28 15:47:39 2014 -0500
@@ -45,6 +45,8 @@
 
 import javax.swing.SwingUtilities;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.swing.components.experimental.SingleValueChartPanel;
 import org.jfree.chart.ChartFactory;
 import org.jfree.chart.JFreeChart;
 import org.jfree.data.time.FixedMillisecond;
@@ -77,7 +79,7 @@
     private final TimeSeriesCollection data = new TimeSeriesCollection();
     private final TimeSeries cpuTimeSeries = new TimeSeries("cpu-stats");
 
-    private VmCpuChartPanel chartPanel;
+    private SingleValueChartPanel chartPanel;
 
     private ActionNotifier<UserAction> userActionNotifier = new ActionNotifier<VmCpuView.UserAction>(this);
 
@@ -110,11 +112,11 @@
 
         chart.getXYPlot().getRangeAxis().setLowerBound(0.0);
 
-        chartPanel = new VmCpuChartPanel(chart, duration);
+        chartPanel = new SingleValueChartPanel(chart, duration);
 
         visiblePanel.setContent(chartPanel);
 
-        chartPanel.addPropertyChangeListener(VmCpuChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
+        chartPanel.addPropertyChangeListener(SingleValueChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
             @Override
             public void propertyChange(PropertyChangeEvent evt) {
                 duration = (Duration) evt.getNewValue();
--- a/vm-cpu/common/pom.xml	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/common/pom.xml	Fri Nov 28 15:47:39 2014 -0500
@@ -130,5 +130,10 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 </project>
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java	Fri Nov 28 15:47:39 2014 -0500
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.vm.cpu.common.internal;
 
-import java.util.Date;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
--- a/vm-cpu/distribution/thermostat-plugin.xml	Fri Nov 28 13:48:29 2014 -0500
+++ b/vm-cpu/distribution/thermostat-plugin.xml	Fri Nov 28 15:47:39 2014 -0500
@@ -53,6 +53,7 @@
       <bundles>
         <bundle><symbolic-name>com.redhat.thermostat.vm.cpu.common</symbolic-name><version>${project.version}</version></bundle>
         <bundle><symbolic-name>com.redhat.thermostat.vm.cpu.agent</symbolic-name><version>${project.version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.client.core</symbolic-name><version>${project.version}</version></bundle>
       </bundles>
     </extension>
     <extension>