diff --git a/examples/sky-shell/src/se/sics/contiki/collect/CollectServer.java b/examples/sky-shell/src/se/sics/contiki/collect/CollectServer.java index 049ce7284..b790dcc6d 100644 --- a/examples/sky-shell/src/se/sics/contiki/collect/CollectServer.java +++ b/examples/sky-shell/src/se/sics/contiki/collect/CollectServer.java @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: CollectServer.java,v 1.18 2010/09/15 16:15:10 nifi Exp $ + * $Id: CollectServer.java,v 1.19 2010/09/21 20:24:18 nifi Exp $ * * ----------------------------------------------------------------- * @@ -34,8 +34,8 @@ * * Authors : Joakim Eriksson, Niclas Finne * Created : 3 jul 2008 - * Updated : $Date: 2010/09/15 16:15:10 $ - * $Revision: 1.18 $ + * Updated : $Date: 2010/09/21 20:24:18 $ + * $Revision: 1.19 $ */ package se.sics.contiki.collect; @@ -59,6 +59,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; import java.util.HashMap; import java.util.Hashtable; import java.util.Properties; @@ -81,10 +82,10 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; +import se.sics.contiki.collect.gui.AggregatedTimeChartPanel; import se.sics.contiki.collect.gui.BarChartPanel; import se.sics.contiki.collect.gui.MapPanel; import se.sics.contiki.collect.gui.NodeInfoPanel; -import se.sics.contiki.collect.gui.PacketChartPanel; import se.sics.contiki.collect.gui.SerialConsole; import se.sics.contiki.collect.gui.TimeChartPanel; @@ -329,6 +330,34 @@ public class CollectServer { dataset.addValue(data.getNode().getSensorDataAggregator().getAverageValue(SensorData.HOPS), categories[1], data.getNode().getName()); } }, + new AggregatedTimeChartPanel(this, NETWORK, + "Next Hop (Over Time)", "Time", "Next Hop Changes") { + { + ValueAxis axis = chart.getXYPlot().getRangeAxis(); + ((NumberAxis)axis).setAutoRangeIncludesZero(true); + axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + } + @Override + protected int[] createState(Node node) { + return new int[1]; + } + @Override + protected void clearState(Map map) { + for(int[] value : map.values()) { + value[0] = 0; + } + } + @Override + protected int getSensorDataValue(SensorData sd, int[] nodeState) { + boolean hasBest = nodeState[0] != 0; + int bestNeighbor = sd.getValue(SensorData.BEST_NEIGHBOR); + if (bestNeighbor != 0 && bestNeighbor != nodeState[0]) { + nodeState[0] = bestNeighbor; + return hasBest ? 1 : 0; + } + return 0; + } + }, new TimeChartPanel(this, NETWORK, "Latency", "Latency", "Time", "Seconds") { { setMaxItemCount(defaultMaxItemCount); @@ -337,7 +366,67 @@ public class CollectServer { return data.getLatency(); } }, - new PacketChartPanel(this, NETWORK, "Received (Over Time)", "Time", "Received Packets"), + new AggregatedTimeChartPanel(this, NETWORK, + "Received (Over Time)", "Time", "Received Packets") { + { + ValueAxis axis = chart.getXYPlot().getRangeAxis(); + ((NumberAxis)axis).setAutoRangeIncludesZero(true); + axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + } + @Override + protected String getTitle(int nodeCount, int dataCount, int duplicateCount) { + return "Received " + dataCount + " packets from " + nodeCount + " node" + + (nodeCount > 1 ? "s" : "") + + (duplicateCount > 0 ? (" (" + duplicateCount + " duplicates)") : ""); + } + @Override + protected Node createState(Node node) { + return node; + } + @Override + protected int getSensorDataValue(SensorData sd, Node node) { + return 1; + } + }, + new AggregatedTimeChartPanel(this, NETWORK, + "Lost (Over Time)", "Time", "Estimated Lost Packets") { + private int totalLost; + { + ValueAxis axis = chart.getXYPlot().getRangeAxis(); + ((NumberAxis)axis).setAutoRangeIncludesZero(true); + axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + } + @Override + protected String getTitle(int nodeCount, int dataCount, int duplicateCount) { + return "Received " + dataCount + " packets from " + nodeCount + + " node" + (nodeCount > 1 ? "s" : "") + ". Estimated " + + totalLost + " lost packet" + (totalLost == 1 ? "" : "s") + + '.'; + } + @Override + protected int[] createState(Node node) { + return new int[1]; + } + @Override + protected void clearState(Map map) { + totalLost = 0; + for(int[] v : map.values()) { + v[0] = 0; + } + } + @Override + protected int getSensorDataValue(SensorData sd, int[] nodeState) { + int lastSeqno = nodeState[0]; + int seqno = sd.getSeqno(); + nodeState[0] = seqno; + if (seqno > lastSeqno + 1 && lastSeqno != 0) { + int estimatedLost = seqno - lastSeqno - 1; + totalLost += estimatedLost; + return estimatedLost; + } + return 0; + } + }, new BarChartPanel(this, NETWORK, "Received (Per Node)", "Received Packets Per Node", "Nodes", "Packets", new String[] { "Packets", "Duplicates" }) { { @@ -861,6 +950,10 @@ public class CollectServer { } } + public Node[] getSelectedNodes() { + return selectedNodes; + } + // ------------------------------------------------------------------- // Node location handling diff --git a/examples/sky-shell/src/se/sics/contiki/collect/SensorDataAggregator.java b/examples/sky-shell/src/se/sics/contiki/collect/SensorDataAggregator.java index f228ce09b..09a1e888f 100644 --- a/examples/sky-shell/src/se/sics/contiki/collect/SensorDataAggregator.java +++ b/examples/sky-shell/src/se/sics/contiki/collect/SensorDataAggregator.java @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: SensorDataAggregator.java,v 1.7 2010/09/15 16:15:10 nifi Exp $ + * $Id: SensorDataAggregator.java,v 1.8 2010/09/21 20:24:18 nifi Exp $ * * ----------------------------------------------------------------- * @@ -34,8 +34,8 @@ * * Authors : Joakim Eriksson, Niclas Finne * Created : 20 aug 2008 - * Updated : $Date: 2010/09/15 16:15:10 $ - * $Revision: 1.7 $ + * Updated : $Date: 2010/09/21 20:24:18 $ + * $Revision: 1.8 $ */ package se.sics.contiki.collect; @@ -53,8 +53,9 @@ public class SensorDataAggregator implements SensorInfo { private int dataCount; private int duplicates = 0; private int lost = 0; + private int nodeRestartCount = 0; private int nextHopChangeCount = 0; - private String lastNextHop = null; + private int lastNextHop = 0; private long shortestPeriod = Long.MAX_VALUE; private long longestPeriod = 0; @@ -91,8 +92,8 @@ public class SensorDataAggregator implements SensorInfo { int seqn = data.getValue(SEQNO); int s = seqn + seqnoDelta; - String bestNeighbor = data.getBestNeighborID(); - if (lastNextHop != null && !lastNextHop.equals(bestNeighbor)) { + int bestNeighbor = data.getValue(BEST_NEIGHBOR); + if (lastNextHop != bestNeighbor && lastNextHop != 0) { nextHopChangeCount++; } lastNextHop = bestNeighbor; @@ -150,14 +151,25 @@ public class SensorDataAggregator implements SensorInfo { shortestPeriod = timeDiff; } } - // Handle wrapping sequence numbers if (dataCount == 0) { // First packet from node. } else if (maxSeqno - s > 2) { - s += maxSeqno - seqnoDelta; - seqnoDelta = maxSeqno; + // Handle sequence number overflow. + seqnoDelta = maxSeqno + 1; + s = seqnoDelta + seqn; + if (seqn > 127) { + // Sequence number restarted at 128 (to separate node restarts + // from sequence number overflow). + seqn -= 128; + seqnoDelta -= 128; + s -= 128; + } else { + // Sequence number restarted at 0. This is usually an indication that + // the node restarted. + nodeRestartCount++; + } if (seqn > 0) { - lost += seqn - 1; + lost += seqn; } } else if (s > maxSeqno + 1){ lost += s - (maxSeqno + 1); @@ -176,8 +188,9 @@ public class SensorDataAggregator implements SensorInfo { dataCount = 0; duplicates = 0; lost = 0; + nodeRestartCount = 0; nextHopChangeCount = 0; - lastNextHop = null; + lastNextHop = 0; minSeqno = Integer.MAX_VALUE; maxSeqno = Integer.MIN_VALUE; seqnoDelta = 0; @@ -264,6 +277,10 @@ public class SensorDataAggregator implements SensorInfo { return nextHopChangeCount; } + public int getEstimatedRestarts() { + return nodeRestartCount; + } + public int getEstimatedLostCount() { return lost; } diff --git a/examples/sky-shell/src/se/sics/contiki/collect/gui/AggregatedTimeChartPanel.java b/examples/sky-shell/src/se/sics/contiki/collect/gui/AggregatedTimeChartPanel.java new file mode 100644 index 000000000..688eec594 --- /dev/null +++ b/examples/sky-shell/src/se/sics/contiki/collect/gui/AggregatedTimeChartPanel.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: AggregatedTimeChartPanel.java,v 1.1 2010/09/21 20:24:19 nifi Exp $ + * + * ----------------------------------------------------------------- + * + * PacketChartPanel + * + * Authors : Joakim Eriksson, Niclas Finne + * Created : 6 sep 2010 + * Updated : $Date: 2010/09/21 20:24:19 $ + * $Revision: 1.1 $ + */ + +package se.sics.contiki.collect.gui; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JPanel; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.time.Minute; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; + +import se.sics.contiki.collect.CollectServer; +import se.sics.contiki.collect.Node; +import se.sics.contiki.collect.SensorData; +import se.sics.contiki.collect.Visualizer; + +/** + * + */ +public abstract class AggregatedTimeChartPanel extends JPanel implements Visualizer { + + private static final long serialVersionUID = 2100788758213434540L; + + protected final CollectServer server; + protected final String category; + protected final String title; + protected final TimeSeries series; + + protected final JFreeChart chart; + protected final ChartPanel chartPanel; + + private Node[] selectedNodes; + private HashMap selectedMap = new HashMap(); + + public AggregatedTimeChartPanel(CollectServer server, String category, String title, + String timeAxisLabel, String valueAxisLabel) { + super(new BorderLayout()); + this.server = server; + this.category = category; + this.title = title; + this.series = new TimeSeries(title, Minute.class); + TimeSeriesCollection timeSeries = new TimeSeriesCollection(series); + this.chart = ChartFactory.createTimeSeriesChart( + title, timeAxisLabel, valueAxisLabel, timeSeries, + false, true, false + ); + this.chartPanel = new ChartPanel(chart); + this.chartPanel.setPreferredSize(new Dimension(500, 270)); + setBaseShapeVisible(false); + add(chartPanel, BorderLayout.CENTER); + } + + @Override + public String getCategory() { + return category; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public Component getPanel() { + return this; + } + + @Override + public void nodeAdded(Node node) { + // Ignore + } + + @Override + public void nodesSelected(Node[] nodes) { + if (isVisible()) { + updateSelected(nodes); + } + } + + private void updateSelected(Node[] nodes) { + if (this.selectedNodes != nodes) { + this.selectedNodes = nodes; + this.selectedMap.clear(); + if (nodes != null) { + for(Node node : nodes) { + this.selectedMap.put(node, createState(node)); + } + } + updateCharts(); + } + } + + @Override + public void nodeDataReceived(SensorData data) { + if (isVisible() && selectedMap.get(data.getNode()) != null) { + updateCharts(); + } + } + + @Override + public void clearNodeData() { + if (isVisible()) { + updateCharts(); + } + } + + private void updateCharts() { + int duplicates = 0; + int total = 0; + series.clear(); + if (this.selectedNodes != null && server.getSensorDataCount() > 0) { + long minute = server.getSensorData(0).getNodeTime() / 60000; + long lastMinute = minute; + int count = 0; + clearState(selectedMap); + for(int i = 0; i < server.getSensorDataCount(); i++) { + SensorData sd = server.getSensorData(i); + T nodeState = selectedMap.get(sd.getNode()); + if (nodeState != null) { + if (sd.isDuplicate()) { + duplicates++; + } else { + long min = sd.getNodeTime() / 60000; + if (min != minute) { + if (lastMinute < minute) { + series.add(new Minute(new Date(lastMinute * 60000L)), 0); + if (lastMinute < minute - 1) { + series.add(new Minute(new Date((minute - 1) * 60000L)), 0); + } + } + series.add(new Minute(new Date(minute * 60000L)), count); + count = 0; + lastMinute = minute + 1; + minute = min; + } + count += getSensorDataValue(sd, nodeState); + } + total++; + } + } + } + chart.setTitle(getTitle(selectedMap.size(), total, duplicates)); + } + + protected String getTitle(int nodeCount, int dataCount, int duplicateCount) { + return title; + } + + protected abstract T createState(Node node); + + protected void clearState(Map map) { + } + + protected abstract int getSensorDataValue(SensorData sd, T nodeState); + + public boolean getBaseShapeVisible() { + return ((XYLineAndShapeRenderer)this.chart.getXYPlot().getRenderer()).getBaseShapesVisible(); + } + + public void setBaseShapeVisible(boolean visible) { + ((XYLineAndShapeRenderer)this.chart.getXYPlot().getRenderer()).setBaseShapesVisible(visible); + } + + public double getRangeMinimumSize() { + return chart.getXYPlot().getRangeAxis().getAutoRangeMinimumSize(); + } + + public void setRangeMinimumSize(double size) { + chart.getXYPlot().getRangeAxis().setAutoRangeMinimumSize(size); + } + + public void setVisible(boolean visible) { + updateSelected(visible ? server.getSelectedNodes() : null); + super.setVisible(visible); + } + +}