Mercurial > hg > release > thermostat-1.2
changeset 1526:1c6b65679463
The vm-cpu tab should fetch less data
Reviewed-by: jerboaa, jkang
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-October/011142.html
PR2007
line wrap: on
line diff
--- a/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuView.java Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuView.java Mon Oct 20 14:16:54 2014 -0400 @@ -37,13 +37,46 @@ package com.redhat.thermostat.vm.cpu.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.common.ActionListener; +import com.redhat.thermostat.common.model.Range; import com.redhat.thermostat.storage.model.DiscreteTimeData; +import com.redhat.thermostat.vm.cpu.client.core.VmCpuView.Duration; 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, + } + + 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> availableInterval); + public abstract void addData(List<DiscreteTimeData<? extends Number>> data); public abstract void clearData();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/TimeRangeComputer.java Mon Oct 20 14:16:54 2014 -0400 @@ -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.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 Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuController.java Mon Oct 20 14:16:54 2014 -0400 @@ -37,6 +37,7 @@ 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; @@ -49,17 +50,21 @@ 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.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; import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO; import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat; public class VmCpuController implements InformationServiceController<VmRef> { + private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer(); private final VmRef ref; @@ -68,7 +73,10 @@ private final Timer timer; - private long lastSeenTimeStamp = Long.MIN_VALUE; + 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; public VmCpuController(ApplicationService appSvc, VmCpuStatDAO vmCpuStatDao, VmRef ref, VmCpuViewProvider provider) { this.ref = ref; @@ -78,7 +86,7 @@ timer.setAction(new Runnable() { @Override public void run() { - doUpdateVmCpuCharts(); + updateData(); } }); timer.setTimeUnit(TimeUnit.SECONDS); @@ -103,20 +111,76 @@ } } }); + + view.addUserActionListener(new ActionListener<VmCpuView.UserAction>() { + + @Override + public void actionPerformed(ActionEvent<UserAction> actionEvent) { + switch (actionEvent.getActionId()) { + case USER_CHANGED_TIME_RANGE: + Duration duration = (Duration) view.getUserDesiredDuration(); + userDesiredDuration = duration; + view.setVisibleDataRange(duration.value, duration.unit); + break; + default: + throw new AssertionError("Unhandled action type"); + } + } + }); + + userDesiredDuration = view.getUserDesiredDuration(); } private void start() { timer.start(); } - private void doUpdateVmCpuCharts() { - List<VmCpuStat> stats = dao.getLatestVmCpuStats(ref, lastSeenTimeStamp); + 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); + + 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())); + + } + + 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); - lastSeenTimeStamp = Math.max(lastSeenTimeStamp, stat.getTimeStamp()); } view.addData(toDisplay); @@ -126,6 +190,7 @@ timer.stop(); } + @Override public UIComponent getView() { return (UIComponent) view; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/TimeRangeComputerTest.java Mon Oct 20 14:16:54 2014 -0400 @@ -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.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 Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/client-core/src/test/java/com/redhat/thermostat/vm/cpu/client/core/internal/VmCpuControllerTest.java Mon Oct 20 14:16:54 2014 -0400 @@ -37,7 +37,6 @@ package com.redhat.thermostat.vm.cpu.client.core.internal; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -45,6 +44,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -56,26 +56,26 @@ 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.client.core.internal.VmCpuController; import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO; import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat; - public class VmCpuControllerTest { @SuppressWarnings({ "unchecked", "rawtypes" }) // any(List.class) @Test public void testChartUpdate() { + VmRef ref = mock(VmRef.class); VmCpuStat stat1 = new VmCpuStat("foo-agent", 123, "vmId", 50.5); List<VmCpuStat> stats = new ArrayList<VmCpuStat>(); stats.add(stat1); VmCpuStatDAO vmCpuStatDAO = mock(VmCpuStatDAO.class); - when(vmCpuStatDAO.getLatestVmCpuStats(any(VmRef.class), eq(Long.MIN_VALUE))).thenReturn(stats).thenReturn(new ArrayList<VmCpuStat>()); - - VmRef ref = mock(VmRef.class); + when(vmCpuStatDAO.getLatestVmCpuStats(any(VmRef.class), any(Long.class))).thenThrow(new AssertionError("Unbounded queries are bad!")); + when(vmCpuStatDAO.getOldest(ref)).thenReturn(stat1); + when(vmCpuStatDAO.getLatest(ref)).thenReturn(stat1); Timer timer = mock(Timer.class); ArgumentCaptor<Runnable> timerActionCaptor = ArgumentCaptor.forClass(Runnable.class); @@ -89,6 +89,8 @@ final VmCpuView view = mock(VmCpuView.class); ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class); doNothing().when(view).addActionListener(viewArgumentCaptor.capture()); + + when(view.getUserDesiredDuration()).thenReturn(new Duration(1, TimeUnit.MINUTES)); VmCpuViewProvider viewProvider = mock(VmCpuViewProvider.class); when(viewProvider.createView()).thenReturn(view);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuChartPanel.java Mon Oct 20 14:16:54 2014 -0400 @@ -0,0 +1,243 @@ +/* + * 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 Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/client-swing/src/main/java/com/redhat/thermostat/vm/cpu/client/swing/internal/VmCpuPanel.java Mon Oct 20 14:16:54 2014 -0400 @@ -37,10 +37,12 @@ package com.redhat.thermostat.vm.cpu.client.swing.internal; 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.JPanel; import javax.swing.SwingUtilities; import org.jfree.chart.ChartFactory; @@ -53,9 +55,9 @@ import com.redhat.thermostat.client.swing.ComponentVisibleListener; 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.ui.RecentTimeSeriesChartController; import com.redhat.thermostat.common.ActionListener; +import com.redhat.thermostat.common.ActionNotifier; +import com.redhat.thermostat.common.model.Range; import com.redhat.thermostat.shared.locale.Translate; import com.redhat.thermostat.storage.model.DiscreteTimeData; import com.redhat.thermostat.vm.cpu.client.core.VmCpuView; @@ -65,15 +67,26 @@ private static final Translate<LocaleResources> translator = 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 data = new TimeSeriesCollection(); private final TimeSeries cpuTimeSeries = new TimeSeries("cpu-stats"); + private VmCpuChartPanel chartPanel; + + private ActionNotifier<UserAction> userActionNotifier = new ActionNotifier<VmCpuView.UserAction>(this); + public VmCpuPanel() { super(); data.addSeries(cpuTimeSeries); + duration = new Duration(DEFAULT_VALUE, DEFAULT_UNIT); + initializePanel(); visiblePanel.addHierarchyListener(new ComponentVisibleListener() { @@ -90,16 +103,6 @@ } @Override - public void addActionListener(ActionListener<Action> listener) { - notifier.addActionListener(listener); - } - - @Override - public void removeActionListener(ActionListener<Action> listener) { - notifier.removeActionListener(listener); - } - - @Override public Component getUiComponent() { return visiblePanel; } @@ -117,9 +120,42 @@ chart.getXYPlot().getRangeAxis().setLowerBound(0.0); - JPanel chartPanel = new RecentTimeSeriesChartPanel(new RecentTimeSeriesChartController(chart)); + chartPanel = new VmCpuChartPanel(chart, duration); visiblePanel.setContent(chartPanel); + + chartPanel.addPropertyChangeListener(VmCpuChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + duration = (Duration) evt.getNewValue(); + userActionNotifier.fireAction(UserAction.USER_CHANGED_TIME_RANGE); + } + }); + } + + @Override + public void addUserActionListener(ActionListener<UserAction> listener) { + userActionNotifier.addActionListener(listener); + } + + @Override + public void removeUserActionListener(ActionListener<UserAction> listener) { + userActionNotifier.removeActionListener(listener); + } + + @Override + public void setAvailableDataRange(Range<Long> availableInterval) { + // FIXME indicate the total data range to the user somehow + } + + @Override + public void setVisibleDataRange(int time, TimeUnit unit) { + chartPanel.setTimeRangeToShow(time, unit); + } + + @Override + public Duration getUserDesiredDuration() { + return duration; } @Override
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/VmCpuStatDAO.java Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/VmCpuStatDAO.java Mon Oct 20 14:16:54 2014 -0400 @@ -53,8 +53,14 @@ static final Category<VmCpuStat> vmCpuStatCategory = new Category<>("vm-cpu-stats", VmCpuStat.class, Arrays.<Key<?>>asList(Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, vmCpuLoadKey), Arrays.<Key<?>>asList(Key.TIMESTAMP)); + public abstract VmCpuStat getOldest(VmRef ref); + + public abstract VmCpuStat getLatest(VmRef ref); + public abstract List<VmCpuStat> getLatestVmCpuStats(VmRef ref, long since); + public abstract List<VmCpuStat> getVmCpuStats(VmRef ref, long since, long to); + public abstract void putVmCpuStat(VmCpuStat stat); }
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java Mon Oct 20 14:16:54 2014 -0400 @@ -36,11 +36,13 @@ 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; 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 +51,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.cpu.common.VmCpuStatDAO; import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat; @@ -64,15 +67,36 @@ "'" + Key.VM_ID.getName() + "' = ?s , " + "'" + Key.TIMESTAMP.getName() + "' = ?l , " + "'" + vmCpuLoadKey.getName() + "' = ?d"; - - + + // QUERY vm-cpu-stats WHERE 'agentId' = ?s AND \ + // 'vmId' = ?s \ + // SORT 'timeStamp' ASC \ + // LIMIT 1 + static final String DESC_OLDEST_VM_CPU_STAT = "QUERY " + vmCpuStatCategory.getName() + + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s " + + " AND '" + Key.VM_ID.getName() + "' = ?s " + + " SORT '" + Key.TIMESTAMP.getName() + "' ASC " + + " LIMIT 1"; + + // QUERY vm-cpu-stats WHERE 'agentId' = ?s AND \ + // 'vmId' = ?s \ + // SORT 'timeStamp' DSC \ + // LIMIT 1 + static final String DESC_LATEST_VM_CPU_STAT = "QUERY " + vmCpuStatCategory.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<VmCpuStat> getter; + private final VmTimeIntervalPojoListGetter<VmCpuStat> otherGetter; VmCpuStatDAOImpl(Storage storage) { this.storage = storage; storage.registerCategory(vmCpuStatCategory); this.getter = new VmLatestPojoListGetter<>(storage, vmCpuStatCategory); + this.otherGetter = new VmTimeIntervalPojoListGetter<>(storage, vmCpuStatCategory); } @Override @@ -81,6 +105,40 @@ } @Override + public List<VmCpuStat> getVmCpuStats(VmRef ref, long since, long to) { + return otherGetter.getLatest(ref, since, to); + } + + @Override + public VmCpuStat getLatest(VmRef ref) { + return runAgentAndVmIdQuery(ref, DESC_LATEST_VM_CPU_STAT); + } + + @Override + public VmCpuStat getOldest(VmRef ref) { + return runAgentAndVmIdQuery(ref, DESC_OLDEST_VM_CPU_STAT); + } + + private VmCpuStat runAgentAndVmIdQuery(VmRef ref, String descriptor) { + StatementDescriptor<VmCpuStat> desc = new StatementDescriptor<>(vmCpuStatCategory, descriptor); + PreparedStatement<VmCpuStat> prepared; + try { + prepared = storage.prepareStatement(desc); + prepared.setString(0, ref.getHostRef().getAgentId()); + prepared.setString(1, ref.getVmId()); + Cursor<VmCpuStat> 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; + } + + @Override public void putVmCpuStat(VmCpuStat stat) { StatementDescriptor<VmCpuStat> desc = new StatementDescriptor<>(vmCpuStatCategory, DESC_ADD_VM_CPU_STAT); PreparedStatement<VmCpuStat> prepared;
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java Mon Oct 20 14:16:54 2014 -0400 @@ -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.cpu.common.VmCpuStatDAO; @@ -53,21 +54,29 @@ public class VmCpuStatDAOImplStatementDescriptorRegistration implements StatementDescriptorRegistration { - static final String descriptor = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT, + static final String latestDescriptor = String.format(VmLatestPojoListGetter.VM_LATEST_QUERY_FORMAT, + VmCpuStatDAO.vmCpuStatCategory.getName()); + static final String rangeDescriptor = String.format(VmTimeIntervalPojoListGetter.VM_INTERVAL_QUERY_FORMAT, VmCpuStatDAO.vmCpuStatCategory.getName()); @Override public Set<String> getStatementDescriptors() { Set<String> descs = new HashSet<>(); descs.add(VmCpuStatDAOImpl.DESC_ADD_VM_CPU_STAT); - descs.add(descriptor); + descs.add(VmCpuStatDAOImpl.DESC_LATEST_VM_CPU_STAT); + descs.add(VmCpuStatDAOImpl.DESC_OLDEST_VM_CPU_STAT); + descs.add(latestDescriptor); + descs.add(rangeDescriptor); return descs; } @Override public DescriptorMetadata getDescriptorMetadata(String descriptor, PreparedParameter[] params) { - if (descriptor.equals(VmCpuStatDAOImplStatementDescriptorRegistration.descriptor)) { + if (descriptor.equals(VmCpuStatDAOImplStatementDescriptorRegistration.latestDescriptor) + || descriptor.equals(VmCpuStatDAOImplStatementDescriptorRegistration.rangeDescriptor) + || descriptor.equals(VmCpuStatDAOImpl.DESC_LATEST_VM_CPU_STAT) + || descriptor.equals(VmCpuStatDAOImpl.DESC_OLDEST_VM_CPU_STAT)) { String agentId = (String)params[0].getValue(); String vmId = (String)params[1].getValue(); DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
--- a/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java Mon Oct 20 15:50:43 2014 -0400 +++ b/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java Mon Oct 20 14:16:54 2014 -0400 @@ -64,7 +64,7 @@ public void registersAllDescriptors() { VmCpuStatDAOImplStatementDescriptorRegistration reg = new VmCpuStatDAOImplStatementDescriptorRegistration(); Set<String> descriptors = reg.getStatementDescriptors(); - assertEquals(2, descriptors.size()); + assertEquals(5, descriptors.size()); assertFalse("null descriptor not allowed", descriptors.contains(null)); } @@ -92,11 +92,38 @@ // storage-core + this module assertEquals(2, registrations.size()); assertNotNull(vmCpuStatReg); - assertEquals(2, vmCpuStatReg.getStatementDescriptors().size()); + assertEquals(5, vmCpuStatReg.getStatementDescriptors().size()); } @Test public void canGetMetadataForLatestCpuStatQuery() { + String descriptor = VmCpuStatDAOImplStatementDescriptorRegistration.latestDescriptor; + + assertValidDescriptor(descriptor); + } + + @Test + public void canGetMetadataForRangeCpuStatQuery() { + String descriptor = VmCpuStatDAOImplStatementDescriptorRegistration.rangeDescriptor; + + assertValidDescriptor(descriptor); + } + + @Test + public void canGetMetadataForSingleOldestCpuStatQuery() { + String descriptor = VmCpuStatDAOImpl.DESC_OLDEST_VM_CPU_STAT; + + assertValidDescriptor(descriptor); + } + + @Test + public void canGetMetadataForSingleLatestCpuStatQuery() { + String descriptor = VmCpuStatDAOImpl.DESC_LATEST_VM_CPU_STAT; + + assertValidDescriptor(descriptor); + } + + private void assertValidDescriptor(String descriptor) { PreparedParameter agentIdParam = mock(PreparedParameter.class); PreparedParameter vmIdParam = mock(PreparedParameter.class); String agentId = "agentId"; @@ -107,7 +134,7 @@ vmIdParam }; StatementDescriptorMetadataFactory factory = new VmCpuStatDAOImplStatementDescriptorRegistration(); - DescriptorMetadata data = factory.getDescriptorMetadata(VmCpuStatDAOImplStatementDescriptorRegistration.descriptor, params); + DescriptorMetadata data = factory.getDescriptorMetadata(descriptor, params); assertNotNull(data); assertEquals(agentId, data.getAgentId()); assertEquals(vmId, data.getVmId());