changeset 1909:323e9c6e2192

Complete vm-numa plugin. This is a backport of the vm-numa plugin from the hg/thermostat repository revision fd3632b6449c. This changeset is built on top of revision cb6390cf824b and completes the backport for the vm-numa plugin. The separation is to make it easier to see relevant changes for the vm-numa plugin. PR3007 Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019313.html
author Jie Kang <jkang@redhat.com>
date Thu, 09 Jun 2016 09:32:01 -0400
parents cb6390cf824b
children 67f0eb234d5d
files client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/ThermostatChartPanel.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/ThermostatChartPanelTest.java common/test/src/main/java/com/redhat/thermostat/testutils/DataObjectTest.java common/test/src/main/java/com/redhat/thermostat/testutils/ServiceLoaderTest.java vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/VmNumaServiceImpl.java vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/VmNumaView.java vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/internal/VmNumaController.java vm-numa/client-core/src/test/java/com/redhat/thermostat/vm/numa/client/core/internal/VmNumaControllerTest.java vm-numa/client-swing/src/main/java/com/redhat/thermostat/vm/numa/client/swing/internal/VmNumaPanel.java vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/VmNumaDAO.java vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/internal/VmNumaDAOImpl.java vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/internal/VmNumaDAOImplStatementDescriptorRegistration.java
diffstat 12 files changed, 683 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/ThermostatChartPanel.java	Thu Jun 09 09:32:01 2016 -0400
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2012-2016 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 java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.text.JTextComponent;
+
+import org.jfree.chart.ChartMouseEvent;
+import org.jfree.chart.ChartMouseListener;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.panel.CrosshairOverlay;
+import org.jfree.chart.plot.Crosshair;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.ui.RectangleEdge;
+
+import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.client.swing.components.ValueField;
+import com.redhat.thermostat.client.ui.SampledDataset;
+
+
+public class ThermostatChartPanel extends JPanel {
+
+    public static final String PROPERTY_VISIBLE_TIME_RANGE = "visibleTimeRange";
+
+    private static final Color WHITE = new Color(255,255,255,0);
+    private static final Color BLACK = new Color(0,0,0,0);
+    private static final float TRANSPARENT = 0.0f;
+
+    private static final int MINIMUM_DRAW_SIZE = 100;
+
+    private JPanel chartsPanel;
+    private List<ChartPanel> charts;
+
+    private RecentTimeControlPanel recentTimeControlPanel;
+    private JTextComponent label;
+
+    private Duration initialDuration;
+
+    private ChartPanel chartPanel;
+    private Crosshair xCrosshair;
+
+    public ThermostatChartPanel(JFreeChart chart, Duration initialDuration) {
+        this(initialDuration);
+
+        addChart(chart);
+    }
+
+    public ThermostatChartPanel(Duration initialDuration) {
+        this.initialDuration = initialDuration;
+        this.chartsPanel = new JPanel();
+        this.charts = new ArrayList<>();
+
+        chartsPanel.setLayout(new BoxLayout(chartsPanel, BoxLayout.Y_AXIS));
+
+        this.setLayout(new BorderLayout());
+
+        recentTimeControlPanel = new RecentTimeControlPanel(initialDuration);
+        recentTimeControlPanel.addPropertyChangeListener(RecentTimeControlPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
+            @Override
+            public void propertyChange(final PropertyChangeEvent evt) {
+                Duration d = (Duration) evt.getNewValue();
+                ThermostatChartPanel.this.firePropertyChange(RecentTimeControlPanel.PROPERTY_VISIBLE_TIME_RANGE, null, d);
+            }
+        });
+
+        add(chartsPanel, BorderLayout.CENTER);
+        add(recentTimeControlPanel, BorderLayout.SOUTH);
+
+        revalidate();
+    }
+
+    public void addChart(JFreeChart chart) {
+        // jfreechart still generates tooltips when disabled, prevent generation as well
+        if (chart.getPlot() instanceof XYPlot) {
+            chart.getXYPlot().getRenderer().setBaseToolTipGenerator(null);
+        }
+
+        chart.getXYPlot().getRangeAxis().setAutoRange(true);
+
+        chart.getXYPlot().getDomainAxis().setAutoRange(true);
+        chart.getXYPlot().getDomainAxis().setFixedAutoRange(initialDuration.unit.toMillis(initialDuration.value));
+
+        chart.getPlot().setBackgroundPaint(WHITE);
+        chart.getPlot().setBackgroundImageAlpha(TRANSPARENT);
+        chart.getPlot().setOutlinePaint(BLACK);
+
+        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);
+
+        chartsPanel.add(chartPanel);
+        chartsPanel.revalidate();
+        charts.add(chartPanel);
+
+    }
+
+    public void setTimeRangeToShow(Duration duration) {
+        for (ChartPanel cp : charts) {
+            XYPlot plot = cp.getChart().getXYPlot();
+
+            // Don't drop old data; just dont' show it.
+            plot.getDomainAxis().setAutoRange(true);
+            plot.getDomainAxis().setFixedAutoRange(initialDuration.unit.toMillis(initialDuration.value));
+        }
+    }
+
+    public void setDataInformationLabel(final String text) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                if (label == null) {
+                    label = new ValueField(text);
+                    recentTimeControlPanel.addTextComponent(label);
+                }
+
+                label.setName("crossHair");
+                label.setText(text);
+            }
+        });
+    }
+    
+    public void enableDynamicCrosshairs() {
+        CrosshairOverlay crosshairOverlay = new CrosshairOverlay();
+        xCrosshair = new Crosshair(Double.NaN, Color.GRAY, new BasicStroke(0f));
+        crosshairOverlay.addDomainCrosshair(xCrosshair);
+        chartPanel.addOverlay(crosshairOverlay);
+
+        chartPanel.addChartMouseListener(new ChartMouseListener() {
+            @Override
+            public void chartMouseClicked(ChartMouseEvent chartMouseEvent) {
+                //do nothing
+            }
+
+            @Override
+            public void chartMouseMoved(ChartMouseEvent event) {
+                XYPlot plot = (XYPlot) event.getChart().getPlot();
+                ValueAxis xAxis = plot.getDomainAxis();
+                SampledDataset dataset = (SampledDataset) plot.getDataset();
+                double xVal = xAxis.java2DToValue(event.getTrigger().getX(), chartPanel.getScreenDataArea(), RectangleEdge.BOTTOM);
+                int item = findNearestItem(dataset, xVal);
+                int series = dataset.getSeriesCount() - 1;
+                if (item >= 0) {
+                    double x = ((dataset.getStartXValue(series, item) + dataset.getEndXValue(series, item)) / 2) ;
+                    double y = dataset.getYValue(series, item);
+                    xCrosshair.setValue(x);
+                    setDataInformationLabel(String.valueOf(y));
+                } else {
+                    xCrosshair.setValue(Double.NaN);
+                    setDataInformationLabel("");
+                }
+            }
+        });
+    }
+    
+    private int findNearestItem(SampledDataset dataset, double xValue) {
+        int series = dataset.getSeriesCount() - 1;
+        int item = -1;
+        double currDiff = Math.abs(((dataset.getStartXValue(series, 0) + dataset.getEndXValue(series, 0)) / 2) - xValue);
+
+        for (int i = 0; i < dataset.getItemCount(series); i++) {
+            double newXVal = (dataset.getStartXValue(series, i) + dataset.getEndXValue(series, i)) / 2;
+            double newDiff = Math.abs(newXVal - xValue);
+            double yVal = dataset.getYValue(series, i);
+            if (newDiff <= currDiff && yVal > 0) {
+                item = i;
+                currDiff = newDiff;
+            }
+        }
+
+        return item;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/ThermostatChartPanelTest.java	Thu Jun 09 09:32:01 2016 -0400
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012-2016 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 static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.concurrent.TimeUnit;
+import javax.swing.JFrame;
+
+import org.fest.swing.annotation.GUITest;
+import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.fixture.FrameFixture;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import com.redhat.thermostat.annotations.internal.CacioTest;
+import com.redhat.thermostat.client.core.experimental.Duration;
+import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+
+@Category(CacioTest.class)
+@RunWith(CacioFESTRunner.class)
+public class ThermostatChartPanelTest {
+
+    private ThermostatChartPanel thermostatChartPanel;
+    private final TimeSeriesCollection dataset = new TimeSeriesCollection();
+    private Duration duration;
+    private JFreeChart chart;
+    private static final int DEFAULT_VALUE = 10;
+    private static final TimeUnit DEFAULT_UNIT = TimeUnit.MINUTES;
+    private final int ESCAPE = 27;
+
+    private JFrame frame;
+    private FrameFixture frameFixture;
+
+    @BeforeClass
+    public static void setupOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+    @Before
+    public void setUp() {
+        chart = ChartFactory.createTimeSeriesChart(
+                null,
+                "Time Label",
+                "Value Label",
+                dataset,
+                false, false, false);
+        duration = new Duration(DEFAULT_VALUE, DEFAULT_UNIT);
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                thermostatChartPanel = new ThermostatChartPanel(chart, duration);
+                frame = new JFrame();
+                frame.add(thermostatChartPanel);
+            }
+        });
+        frameFixture = new FrameFixture(frame);
+        frameFixture.show();
+    }
+
+    @After
+    public void tearDown() {
+        thermostatChartPanel = null;
+        duration = null;
+        frameFixture.cleanUp();
+        frameFixture = null;
+    }
+
+    @GUITest
+    @Test
+    public void testDurationChangeFiresPropertyChange() {
+
+        final boolean[] b = new boolean[] {false};
+
+        thermostatChartPanel.addPropertyChangeListener(ThermostatChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
+            @Override
+            public void propertyChange(final PropertyChangeEvent evt) {
+                Duration d = (Duration) evt.getNewValue();
+                Duration expected = new Duration(5, TimeUnit.MINUTES);
+                if (d.equals(expected)) {
+                    b[0] = true;
+                }
+            }
+        });
+
+        frameFixture.textBox("durationSelector").selectAll();
+        frameFixture.textBox("durationSelector").enterText("5");
+        frameFixture.textBox("durationSelector").pressKey(ESCAPE);
+        assertTrue(b[0]);
+    }
+
+    @GUITest
+    @Test
+    public void testTimeUnitChangeFiresPropertyChange() {
+
+        final boolean[] b = new boolean[] {false};
+
+        thermostatChartPanel.addPropertyChangeListener(ThermostatChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
+            @Override
+            public void propertyChange(final PropertyChangeEvent evt) {
+                Duration d = (Duration) evt.getNewValue();
+                Duration expected = new Duration(10, TimeUnit.HOURS);
+                if (d.equals(expected)) {
+                    b[0] = true;
+                }
+            }
+        });
+
+        frameFixture.comboBox("unitSelector").selectItem(1);
+
+        assertTrue(b[0]);
+    }
+
+    @GUITest
+    @Test
+    public void testTimeRangeToShow() {
+        Duration time = new Duration(20, TimeUnit.MINUTES);
+        thermostatChartPanel.setTimeRangeToShow(time);
+        assertEquals(time.unit.toMillis(time.value), (long) chart.getXYPlot().getDomainAxis().getRange().getLength());
+    }
+
+    @GUITest
+    @Test
+    public void testMultipleChartTimeRangeToShow() {
+        final JFreeChart chart2 = ChartFactory.createTimeSeriesChart(
+                null,
+                "Time Label",
+                "Value Label",
+                dataset,
+                false, false, false);
+        final JFreeChart chart3 = ChartFactory.createTimeSeriesChart(
+                null,
+                "Time Label",
+                "Value Label",
+                dataset,
+                false, false, false);
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                thermostatChartPanel.addChart(chart2);
+                thermostatChartPanel.addChart(chart3);
+            }
+        });
+
+        Duration time = new Duration(20, TimeUnit.MINUTES);
+        thermostatChartPanel.setTimeRangeToShow(time);
+
+        assertEquals(time.unit.toMillis(time.value), (long) chart.getXYPlot().getDomainAxis().getRange().getLength());
+        assertEquals(time.unit.toMillis(time.value), (long) chart2.getXYPlot().getDomainAxis().getRange().getLength());
+        assertEquals(time.unit.toMillis(time.value), (long) chart3.getXYPlot().getDomainAxis().getRange().getLength());
+    }
+
+    @GUITest
+    @Test
+    public void testInitialTimeValue() {
+        Duration time = new Duration(10, TimeUnit.MINUTES);
+        assertEquals(time.unit.toMillis(time.value), (long) chart.getXYPlot().getDomainAxis().getRange().getLength());
+    }
+
+    @GUITest
+    @Test
+    public void testSetDataInformationLabel() {
+        thermostatChartPanel.setDataInformationLabel("15");
+        assertEquals("15", frameFixture.textBox("crossHair").text());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/DataObjectTest.java	Thu Jun 09 09:32:01 2016 -0400
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2016 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.testutils;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+public abstract class DataObjectTest {
+
+    @Test
+    public void testBasicInstantiation() {
+        ArrayList<Class<?>> failureClasses = new ArrayList<>();
+        for (Class<?> clazz : getDataClasses()) {
+            try {
+                // pojo converters use this
+                clazz.newInstance();
+
+                // pojo converters fail at runtime if the constructor is not public
+                if (!Modifier.isPublic(clazz.getConstructor().getModifiers())) {
+                    throw new IllegalAccessError("constructor is not public");
+                }
+            } catch (ReflectiveOperationException e) {
+                failureClasses.add(clazz);
+            }
+        }
+        String msg = "Should be able to instantiate class using no-arg constructor: "
+                + failureClasses;
+        assertEquals(msg, 0, failureClasses.size());
+    }
+
+    /**
+     * Returns a list of classes representing data objects. These classes are
+     * checked to make sure they comply with the requirements for use as
+     * data-storage objects.
+     */
+    public abstract Class<?>[] getDataClasses();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/ServiceLoaderTest.java	Thu Jun 09 09:32:01 2016 -0400
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2012-2016 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.testutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.junit.Test;
+
+public abstract class ServiceLoaderTest<T> {
+
+    public static final int NO_EXTRA_SERVICES = 0;
+    public static final int STORAGE_SERVICES = 1;
+
+    private final int extraServices;
+    private final Class<T> interfaceClass;
+    private final List<Class<? extends T>> implementations;
+
+    /**
+     * @param type
+     *            the type of service, such as CategoryRegistration
+     * @param extraServices
+     *            the number of extra services. See the various *_SERVICES
+     *            constants.
+     * @param implementations
+     *            the implementations that are expected to be present. This is
+     *            not exclusive; additional services may be present.
+     */
+    @SafeVarargs
+    public ServiceLoaderTest(Class<T> type, int extraServices, Class<? extends T>... implementations) {
+        if (implementations == null || implementations.length == 0) {
+            throw new IllegalArgumentException("implementations is empty");
+        }
+        this.extraServices = extraServices;
+        this.interfaceClass = type;
+        this.implementations = Arrays.asList(implementations);
+    }
+
+    /*
+     * The web storage end-point uses service loader in order to determine the
+     * list of trusted/known categories/statements. This test is to ensure
+     * service loading works for this module's regs. E.g. renaming of the impl
+     * class without changing
+     * META-INF/com.redhat.thermostat.storage.core.auth.CategoryRegistration
+     */
+    @Test
+    public void serviceLoaderCanLoadService() {
+        Map<String, Boolean> foundServiceNames = new HashMap<>();
+        Class<? extends T> firstClass = implementations.get(0);
+        Collection<String> expectedClassNames = new HashSet<>();
+
+        for (Class<? extends T> klass : implementations) {
+            expectedClassNames.add(klass.getName());
+        }
+
+        ServiceLoader<T> loader = ServiceLoader.load(this.interfaceClass, firstClass.getClassLoader());
+
+        List<T> registrations = new ArrayList<>(1);
+        for (T r: loader) {
+            foundServiceNames.put(r.getClass().getName(), true);
+            registrations.add(r);
+        }
+
+        for (String className : expectedClassNames) {
+            assertTrue(foundServiceNames.containsKey(className));
+        }
+
+        int expectedServicesCount = this.extraServices + implementations.size();
+        assertEquals(registrations.size(), expectedServicesCount);
+    }
+
+}
--- a/vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/VmNumaServiceImpl.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/VmNumaServiceImpl.java	Thu Jun 09 09:32:01 2016 -0400
@@ -41,8 +41,6 @@
 import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Filter;
 import com.redhat.thermostat.numa.common.NumaDAO;
-import com.redhat.thermostat.storage.core.AgentId;
-import com.redhat.thermostat.storage.core.VmId;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.vm.numa.client.core.internal.VmNumaController;
 import com.redhat.thermostat.vm.numa.common.VmNumaDAO;
@@ -71,9 +69,7 @@
 
     @Override
     public InformationServiceController<VmRef> getInformationServiceController(VmRef ref) {
-        VmId vmId = new VmId(ref.getVmId());
-        AgentId agentId = new AgentId(ref.getHostRef().getAgentId());
-        return new VmNumaController(appSvc, numaDAO, vmNumaDAO, vmId, agentId, vmNumaViewProvider);
+        return new VmNumaController(appSvc, numaDAO, vmNumaDAO, ref, vmNumaViewProvider);
     }
 
     @Override
--- a/vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/VmNumaView.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/VmNumaView.java	Thu Jun 09 09:32:01 2016 -0400
@@ -36,12 +36,10 @@
 
 package com.redhat.thermostat.vm.numa.client.core;
 
-import java.util.concurrent.TimeUnit;
-
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 
 public abstract class VmNumaView extends BasicView implements UIComponent {
--- a/vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/internal/VmNumaController.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/client-core/src/main/java/com/redhat/thermostat/vm/numa/client/core/internal/VmNumaController.java	Thu Jun 09 09:32:01 2016 -0400
@@ -40,19 +40,16 @@
 import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.numa.common.NumaDAO;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
-import com.redhat.thermostat.storage.core.AgentId;
-import com.redhat.thermostat.storage.core.HostRef;
-import com.redhat.thermostat.storage.core.VmId;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 import com.redhat.thermostat.vm.numa.client.core.VmNumaView;
@@ -69,19 +66,17 @@
 
     private VmNumaView view;
 
-    private VmId vmId;
-    private AgentId agentId;
+    private VmRef vmRef;
     private VmNumaDAO vmNumaDAO;
 
     private long lastSeenTimestamp = Long.MIN_VALUE;
 
-    public VmNumaController(ApplicationService appSvc, NumaDAO numaDAO, VmNumaDAO vmNumaDAO, VmId vmId, AgentId agentId, VmNumaViewProvider vmNumaViewProvider) {
-        this.vmId = vmId;
-        this.agentId = agentId;
+    public VmNumaController(ApplicationService appSvc, NumaDAO numaDAO, VmNumaDAO vmNumaDAO, VmRef vmRef, VmNumaViewProvider vmNumaViewProvider) {
+        this.vmRef = vmRef;
 
         this.vmNumaDAO = vmNumaDAO;
 
-        int numNumaNodes = numaDAO.getNumberOfNumaNodes(new HostRef(agentId.get(), ""));
+        int numNumaNodes = numaDAO.getNumberOfNumaNodes(vmRef.getHostRef());
 
         this.view = vmNumaViewProvider.createView();
 
@@ -98,7 +93,7 @@
                     switch (actionEvent.getActionId()) {
                         case USER_CHANGED_TIME_RANGE:
                             Duration duration = view.getUserDesiredDuration();
-                            lastSeenTimestamp = System.currentTimeMillis() - duration.asMilliseconds();
+                            lastSeenTimestamp = System.currentTimeMillis() - duration.unit.toMillis(duration.value);
                             view.setVisibleDataRange(duration);
                             break;
                         default:
@@ -142,7 +137,7 @@
     }
 
     private void updateData() {
-        List<VmNumaStat> stats = vmNumaDAO.getNumaStats(agentId, vmId, lastSeenTimestamp, System.currentTimeMillis());
+        List<VmNumaStat> stats = vmNumaDAO.getNumaStats(vmRef, lastSeenTimestamp, System.currentTimeMillis());
 
         if (stats.size() > 0) {
             lastSeenTimestamp = stats.get(stats.size() - 1).getTimeStamp();
--- a/vm-numa/client-core/src/test/java/com/redhat/thermostat/vm/numa/client/core/internal/VmNumaControllerTest.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/client-core/src/test/java/com/redhat/thermostat/vm/numa/client/core/internal/VmNumaControllerTest.java	Thu Jun 09 09:32:01 2016 -0400
@@ -38,7 +38,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
@@ -64,9 +63,8 @@
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.numa.common.NumaDAO;
-import com.redhat.thermostat.storage.core.AgentId;
 import com.redhat.thermostat.storage.core.HostRef;
-import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 import com.redhat.thermostat.vm.numa.client.core.VmNumaView;
 import com.redhat.thermostat.vm.numa.client.core.VmNumaViewProvider;
@@ -98,13 +96,13 @@
         when(timerFactory.createTimer()).thenReturn(timer);
         when(appSvc.getTimerFactory()).thenReturn(timerFactory);
 
-        AgentId agentId = new AgentId("agent");
-        VmId vmId = new VmId("vm");
+        HostRef hostRef = new HostRef("host", "host");
+        VmRef vmRef = new VmRef(hostRef, "vm", 0, "vm");
 
         NumaDAO numaDAO = mock(NumaDAO.class);
         when(numaDAO.getNumberOfNumaNodes(any(HostRef.class))).thenReturn(NUM_NODES);
         VmNumaDAO vmNumaDAO = mock(VmNumaDAO.class);
-        when(vmNumaDAO.getNumaStats(eq(agentId), eq(vmId), anyLong(), anyLong())).thenReturn(createStats());
+        when(vmNumaDAO.getNumaStats(any(VmRef.class), anyLong(), anyLong())).thenReturn(createStats());
 
         VmNumaViewProvider viewProvider = mock(VmNumaViewProvider.class);
         view = mock(VmNumaView.class);
@@ -113,7 +111,7 @@
         actionListener = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(view).addActionListener(actionListener.capture());
 
-        vmNumaController = new VmNumaController(appSvc, numaDAO, vmNumaDAO, vmId, agentId, viewProvider);
+        vmNumaController = new VmNumaController(appSvc, numaDAO, vmNumaDAO, vmRef, viewProvider);
     }
 
     private List<VmNumaStat> createStats() {
--- a/vm-numa/client-swing/src/main/java/com/redhat/thermostat/vm/numa/client/swing/internal/VmNumaPanel.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/client-swing/src/main/java/com/redhat/thermostat/vm/numa/client/swing/internal/VmNumaPanel.java	Thu Jun 09 09:32:01 2016 -0400
@@ -59,13 +59,13 @@
 import org.jfree.data.time.TimeSeries;
 import org.jfree.data.time.TimeSeriesCollection;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
 import com.redhat.thermostat.client.swing.components.experimental.ThermostatChartPanel;
 import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 import com.redhat.thermostat.vm.numa.client.core.VmNumaView;
--- a/vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/VmNumaDAO.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/VmNumaDAO.java	Thu Jun 09 09:32:01 2016 -0400
@@ -40,26 +40,20 @@
 import java.util.List;
 
 import com.redhat.thermostat.annotations.Service;
-import com.redhat.thermostat.storage.core.AgentId;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.storage.core.VmRef;
 
 @Service
 public interface VmNumaDAO {
 
-    static final Key<VmNumaNodeStat[]> vmNodeStats = new Key<>("vmNodeStats");
+    Key<VmNumaNodeStat[]> vmNodeStats = new Key<>("vmNodeStats");
 
-    static final Category<VmNumaStat> vmNumaStatCategory = new Category<>("vm-numa-stats", VmNumaStat.class,
+    Category<VmNumaStat> vmNumaStatCategory = new Category<>("vm-numa-stats", VmNumaStat.class,
             Arrays.<Key<?>>asList(Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, vmNodeStats),
             Arrays.<Key<?>>asList(Key.TIMESTAMP));
 
     void putVmNumaStat(VmNumaStat stat);
 
-    List<VmNumaStat> getNumaStats(AgentId agentId, VmId vmId, long since, long to);
-
-    VmNumaStat getNewest(AgentId agentId, VmId vmId);
-
-    VmNumaStat getOldest(AgentId agentId, VmId id);
-
+    List<VmNumaStat> getNumaStats(VmRef vmRef, long since, long to);
 }
--- a/vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/internal/VmNumaDAOImpl.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/internal/VmNumaDAOImpl.java	Thu Jun 09 09:32:01 2016 -0400
@@ -37,24 +37,23 @@
 package com.redhat.thermostat.vm.numa.common.internal;
 
 import java.util.List;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.storage.core.AgentId;
-import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.VmBoundaryPojoGetter;
-import com.redhat.thermostat.storage.core.VmId;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.core.VmTimeIntervalPojoListGetter;
-import com.redhat.thermostat.storage.dao.AbstractDao;
-import com.redhat.thermostat.storage.dao.AbstractDaoStatement;
 import com.redhat.thermostat.vm.numa.common.VmNumaDAO;
 import com.redhat.thermostat.vm.numa.common.VmNumaStat;
 
-public class VmNumaDAOImpl extends AbstractDao implements VmNumaDAO {
+public class VmNumaDAOImpl implements VmNumaDAO {
 
     private static final Logger logger = LoggingUtils.getLogger(VmNumaDAOImpl.class);
 
@@ -83,35 +82,24 @@
 
     @Override
     public void putVmNumaStat(final VmNumaStat stat) {
-        executeStatement(new AbstractDaoStatement<VmNumaStat>(storage, vmNumaStatCategory, DESC_ADD_VM_NUMA_STAT) {
-            @Override
-            public PreparedStatement<VmNumaStat> customize(PreparedStatement<VmNumaStat> preparedStatement) {
-                preparedStatement.setString(0, stat.getAgentId());
-                preparedStatement.setString(1, stat.getVmId());
-                preparedStatement.setLong(2, stat.getTimeStamp());
-                preparedStatement.setPojoList(3, stat.getVmNodeStats());
-                return preparedStatement;
-            }
-        });
+        StatementDescriptor<VmNumaStat> desc = new StatementDescriptor<>(vmNumaStatCategory, DESC_ADD_VM_NUMA_STAT);
+        PreparedStatement<VmNumaStat> prepared;
+        try {
+            prepared = storage.prepareStatement(desc);
+            prepared.setString(0, stat.getAgentId());
+            prepared.setString(1, stat.getVmId());
+            prepared.setLong(2, stat.getTimeStamp());
+            prepared.setPojoList(3, stat.getVmNodeStats());
+            prepared.execute();
+        } catch (DescriptorParsingException e) {
+            logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e);
+        }
     }
 
     @Override
-    public List<VmNumaStat> getNumaStats(AgentId agentId, VmId vmId, long since, long to) {
-        return intervalGetter.getLatest(new VmRef(new HostRef(agentId.get(), ""), vmId.get(), -1, ""), since, to);
-    }
-
-    @Override
-    public VmNumaStat getNewest(AgentId agentId, VmId vmId) {
-        return boundaryGetter.getOldestStat(new VmRef(new HostRef(agentId.get(), ""), "", -1, ""));
-    }
-
-    @Override
-    public VmNumaStat getOldest(AgentId agentId, VmId id) {
-        return boundaryGetter.getOldestStat(new VmRef(new HostRef(agentId.get(), ""), "", -1, ""));
-    }
-
-    @Override
-    protected Logger getLogger() {
-        return logger;
+    public List<VmNumaStat> getNumaStats(VmRef vmRef, long since, long to) {
+        return intervalGetter.getLatest(vmRef, since, to);
     }
 }
--- a/vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/internal/VmNumaDAOImplStatementDescriptorRegistration.java	Thu Jun 09 08:58:57 2016 -0400
+++ b/vm-numa/common/src/main/java/com/redhat/thermostat/vm/numa/common/internal/VmNumaDAOImplStatementDescriptorRegistration.java	Thu Jun 09 09:32:01 2016 -0400
@@ -39,9 +39,11 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.VmBoundaryPojoGetter;
 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.numa.common.VmNumaDAO;
 
@@ -66,4 +68,9 @@
         descs.add(rangeDescriptor);
         return descs;
     }
+
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor, PreparedParameter[] params) {
+        throw new AssertionError("Should not be used");
+    }
 }