Merge pull request #377 from pgreyson/master
Bug fixes for iperf display
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index 3616d22..19d5e58 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,3 +1,7 @@
+** April 12, 2013 **
+- fixed bug in iperf fetch
+- improved iperf logging
+
** April 11, 2013 **
- Use timestamps for iperf display
This elimates spurious gaps when server responds slowly. However, gaps still appear if the server drops buffers entirely
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index 460188f..ed31bee 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -76,6 +76,7 @@
<script src="js/topologyactions.js"></script>
<script src="js/topology.js"></script>
+<script src="js/iperf.js"></script>
<script src="js/flows.js"></script>
<script src="js/init.js"></script>
<script src="js/app.js"></script>
diff --git a/web/ons-demo/js/flows.js b/web/ons-demo/js/flows.js
index e21af0d..9f72fe2 100644
--- a/web/ons-demo/js/flows.js
+++ b/web/ons-demo/js/flows.js
@@ -1,6 +1,9 @@
function startFlowAnimation(flow) {
- if (flow.select('animate').empty()) {
- flow.append('svg:animate')
+// console.log('starting animation for flow: ' + flow.flowId);
+
+ var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
+ if (flowSelection.select('animate').empty()) {
+ flowSelection.append('svg:animate')
.attr('attributeName', 'stroke-dashoffset')
.attr('attributeType', 'xml')
.attr('from', '500')
@@ -11,7 +14,8 @@
}
function stopFlowAnimation(flow) {
- flow.select('animate').remove();
+ var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
+ flowSelection.select('animate').remove();
}
@@ -24,7 +28,9 @@
}
});
- var flows = flowLayer.selectAll('.flow').data(topologyFlows);
+ var flows = flowLayer.selectAll('.flow').data(topologyFlows, function (d) {
+ return d.flowId;
+ });
flows.enter().append("svg:path").attr('class', 'flow')
.attr('stroke-dasharray', '4, 10')
@@ -200,141 +206,6 @@
flows.exit().remove();
}
-function startIPerfForFlow(flow) {
- var duration = 10000; // seconds
- var interval = 100; // ms. this is defined by the server
- var updateRate = 2000; // ms
- var pointsToDisplay = 1000;
-
- function makeGraph(iperfData) {
- var d = 'M0,0';
-
- var now = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
-
- if (iperfData.samples && iperfData.samples.length) {
-
- var lastX;
- var i = iperfData.samples.length - 1;
- while (i) {
- var sample = iperfData.samples[i];
-
- var x = (1000 - (now - sample.time)*10);
- // workaround for discontinuity in iperf data
- if (x < 0) {
- i -= 1;
- continue;
- }
-
- var y = 28 * sample.value/1000000;
- if (y > 28) {
- y = 28;
- }
- if (i == iperfData.samples.length - 1) {
- d = 'M' + x + ',30';
- }
-
- // handle gaps
- // 1.5 for rounding error
- if (lastX && lastX - x > 1.5) {
- d += 'L' + lastX + ',30';
- d += 'M' + x + ',30'
- }
- lastX = x;
-
- d += 'L' + x + ',' + (30-y);
-
- i -= 1;
- }
- d += 'L' + lastX + ',30';
- }
- return d;
- }
-
- if (flow.flowId) {
- console.log('starting iperf for: ' + flow.flowId);
- startIPerf(flow, duration, updateRate/interval);
- flow.iperfDisplayInterval = setInterval(function () {
- if (flow.iperfData) {
- var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
- iperfPath.attr('d', makeGraph(flow.iperfData));
- }
-
-
- }, interval);
-
- var animationTimeout;
- flow.iperfData = {
- samples: []
- }
-
- var lastTime;
- flow.iperfFetchInterval = setInterval(function () {
- console.log('Requesting iperf data');
- getIPerfData(flow, function (data) {
- try {
- var iperfData = JSON.parse(data);
-
-// console.log(iperfData.timestamp);
-
- // if the data is fresh
- if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
-
- var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
- startFlowAnimation(flowSelection);
- clearTimeout(animationTimeout);
- // kill the animation if iperfdata stops flowing
- animationTimeout = setTimeout(function () {
- stopFlowAnimation(flowSelection);
- }, updateRate*1.5);
-
- var endTime = Math.floor(iperfData['end-time']*10)/10;
-
- var startTime = endTime - (iperfData.samples.length * interval/1000);
- // set now on the first buffer
- if (!flow.iperfData.startTime) {
- flow.iperfData.startTime = startTime;
- flow.iperfData.localNow = Date.now();
- }
-
- console.log('iperf buffer start time: ' + startTime);
- if (lastTime && (startTime - lastTime) > updateRate/1000) {
- console.log('iperf buffer gap: ' + startTime + ',' + lastTime);
- }
- lastTime = startTime;
-
- // clear out the old data
- while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
- flow.iperfData.samples.shift();
- }
-
- // if the client gets too out of sync, resynchronize
- var clientNow = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
- if (Math.abs(clientNow - startTime) > (updateRate/1000) * 2) {
- console.log('resynchronizing now: ' + clientNow + ' => ' + startTime);
- flow.iperfData.startTime = startTime;
- flow.iperfData.localNow = Date.now();
- }
-
- var time = startTime;
- iperfData.samples.forEach(function (s) {
- var sample = {
- time: time,
- value: s
- };
- flow.iperfData.samples.push(sample);
- time += interval/1000;
- });
- }
- flow.iperfData.timestamp = iperfData.timestamp;
- } catch (e) {
- console.log('bad iperf data: ' + data);
- }
-// console.log(data);
- });
- }, updateRate*.5); // over sample to avoid gaps
- }
-}
-
function updateSelectedFlows() {
// make sure that all of the selected flows are either
// 1) valid (meaning they are in the latest list of flows)
@@ -350,10 +221,13 @@
if (flow) {
var liveFlow = flowMap[makeFlowKey(flow)];
if (liveFlow) {
- newSelectedFlows.push(liveFlow);
- liveFlow.deletePending = flow.deletePending;
- liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
- liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
+ flow.flowId = liveFlow.flowId;
+ if (flow.createPending) {
+ startIPerfForFlow(flow);
+ flow.createPending = false;
+ }
+ flow.dataPath = liveFlow.dataPath;
+ newSelectedFlows.push(flow);
} else if (flow.createPending) {
newSelectedFlows.push(flow);
} else if (hasIPerf(flow)) {
@@ -395,19 +269,6 @@
}
}
-function hasIPerf(flow) {
- return flow && flow.iperfFetchInterval;
-}
-
-function clearIPerf(flow) {
- console.log('clearing iperf interval for: ' + flow.flowId);
- clearInterval(flow.iperfFetchInterval);
- delete flow.iperfFetchInterval;
- clearInterval(flow.iperfDisplayInterval);
- delete flow.iperfDisplayInterval;
- delete flow.iperfData;
-}
-
function deselectFlow(flow, ifCreatePending) {
if (!flow) {
return;
diff --git a/web/ons-demo/js/iperf.js b/web/ons-demo/js/iperf.js
new file mode 100644
index 0000000..b1f964c
--- /dev/null
+++ b/web/ons-demo/js/iperf.js
@@ -0,0 +1,190 @@
+var enableIPerfLog = false;
+
+function iperfLog(message, flow) {
+ if (enableIPerfLog) {
+ console.log('flow: ' + flow.flowId + '===>' + message);
+ }
+}
+
+function hasIPerf(flow) {
+ return flow && flow.iperfFetchTimeout;
+}
+
+function clearIPerf(flow) {
+ iperfLog('clearing iperf interval for: ' + flow.flowId, flow);
+ clearTimeout(flow.iperfFetchTimeout);
+ delete flow.iperfFetchTimeout;
+ clearInterval(flow.iperfDisplayInterval);
+ delete flow.iperfDisplayInterval;
+ clearTimeout(flow.animationTimeout);
+ delete flow.animationTimeout;
+ stopFlowAnimation(flow);
+ delete flow.iperfData.timestamp;
+
+ delete flow.iperfData;
+}
+
+function startIPerfForFlow(flow) {
+ var duration = 10000; // seconds
+ var interval = 100; // ms. this is defined by the server
+ var updateRate = 3000; // ms
+ var pointsToDisplay = 1000;
+
+ function makeGraph(iperfData) {
+ var d = 'M0,0';
+
+ var now = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
+
+ if (iperfData.samples && iperfData.samples.length) {
+
+ var lastX;
+ var i = iperfData.samples.length - 1;
+ while (i) {
+ var sample = iperfData.samples[i];
+
+ var x = (1000 - (now - sample.time)*10);
+ // workaround for discontinuity in iperf data
+ if (x < 0) {
+ i -= 1;
+ continue;
+ }
+
+ var y = 28 * sample.value/1000000;
+ if (y > 28) {
+ y = 28;
+ }
+ if (i == iperfData.samples.length - 1) {
+ d = 'M' + x + ',30';
+ }
+
+ // handle gaps
+ // 1.5 for rounding error
+ if (lastX && lastX - x > 1.5) {
+ d += 'L' + lastX + ',30';
+ d += 'M' + x + ',30'
+ }
+ lastX = x;
+
+ d += 'L' + x + ',' + (30-y);
+
+ i -= 1;
+ }
+ d += 'L' + lastX + ',30';
+ }
+ return d;
+ }
+
+ if (flow.flowId) {
+ iperfLog('starting iperf', flow);
+ startIPerf(flow, duration, updateRate/interval);
+
+ flow.iperfDisplayInterval = setInterval(function () {
+ if (flow.iperfData) {
+ var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
+ flow.iperfData.samples.sort(function (a, b) {
+ return a.time - b.time;
+ });
+ iperfPath.attr('d', makeGraph(flow.iperfData));
+ }
+
+
+ }, interval);
+
+ var animationTimeout;
+ flow.iperfData = {
+ samples: []
+ }
+
+ function resetFlowAnimationTimeout() {
+ clearTimeout(flow.animationTimeout);
+ // kill the animation if iperfdata stops flowing
+ flow.animationTimeout = setTimeout(function () {
+ stopFlowAnimation(flow);
+ }, updateRate*1.5);
+ }
+
+ var lastTime;
+ function fetchData() {
+ iperfLog('Requesting iperf data', flow);
+ var fetchTime = Date.now();
+ getIPerfData(flow, function (data) {
+ var requestTime = Date.now() - fetchTime;
+ var requestTimeMessage = 'iperf request completed in: ' + requestTime + 'ms';
+ if (requestTime > 1000) {
+ requestTimeMessage = requestTimeMessage.toUpperCase();
+ }
+ iperfLog(requestTimeMessage, flow);
+
+ if (!flow.iperfData) {
+ iperfLog('iperf session closed', flow);
+ return;
+ }
+
+ try {
+ var iperfData = JSON.parse(data);
+
+// iperfLog(iperfData.timestamp, flow);
+
+ // if the data is fresh
+ if (!(flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp)) {
+ if (!flow.iperfData.timestamp) {
+ iperfLog('received first iperf buffer', flow);
+ } else {
+ iperfLog('received duplicate iperf buffer with timestamp: ' + iperfData.timestamp, flow);
+ }
+ } else {
+ iperfLog('received new iperf buffer with timstamp: ' + iperfData.timestamp, flow);
+ startFlowAnimation(flow);
+ resetFlowAnimationTimeout();
+
+ var endTime = Math.floor(iperfData['end-time']*10)/10;
+
+ var startTime = endTime - (iperfData.samples.length * interval/1000);
+ // set now on the first buffer
+ if (!flow.iperfData.startTime) {
+ flow.iperfData.startTime = startTime;
+ flow.iperfData.localNow = Date.now();
+ }
+
+ iperfLog('iperf buffer start time: ' + startTime, flow);
+ if (lastTime && (startTime - lastTime) > updateRate/1000) {
+ iperfLog('iperf buffer gap: ' + (startTime - lastTime), flow);
+ }
+ lastTime = startTime;
+
+ // clear out the old data
+ while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
+ flow.iperfData.samples.shift();
+ }
+
+ // if the client gets too out of sync, resynchronize
+ var clientNow = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
+ if (Math.abs(clientNow - startTime) > (updateRate/1000) * 2) {
+ iperfLog('resynchronizing now: ' + clientNow + ' => ' + startTime, flow);
+ flow.iperfData.startTime = startTime;
+ flow.iperfData.localNow = Date.now();
+ }
+
+ var time = startTime;
+ iperfData.samples.forEach(function (s) {
+ var sample = {
+ time: time,
+ value: s
+ };
+ flow.iperfData.samples.push(sample);
+ time += interval/1000;
+ });
+ }
+ flow.iperfData.timestamp = iperfData.timestamp;
+ } catch (e) {
+ iperfLog('bad iperf data: ' + data, flow);
+ }
+ flow.iperfFetchTimeout = setTimeout(fetchData, updateRate*.25); // over sample to avoid gaps
+// iperfLog(data, flow);
+ });
+ }
+
+ // wait a buffer to make sure the old iperf session gets cleared out
+ flow.iperfFetchTimeout = setTimeout(fetchData, updateRate);
+ }
+}
\ No newline at end of file
diff --git a/web/ons-demo/js/map.js b/web/ons-demo/js/map.js
index ad590cb..256162d 100644
--- a/web/ons-demo/js/map.js
+++ b/web/ons-demo/js/map.js
@@ -344,10 +344,7 @@
}
function switchesEnter(switches) {
- switchLayer.selectAll('g').data(switches, function (d) {
- return d.dpid;
- })
- .enter()
+ switches.enter()
.append('svg:g')
.attr("id", function (d) {
return d.dpid;
@@ -363,9 +360,7 @@
function switchesUpdate(switches) {
- switchLayer.selectAll('g').data(switches, function (d) {
- return d.dpid;
- }).each(function (d) {
+ switches.each(function (d) {
// if there's a pending state changed and then the state changes, clear the pending class
var circle = d3.select(this);
if (d.state === 'ACTIVE' && circle.classed('inactive') ||
@@ -392,8 +387,10 @@
var aggregationSwitches = makeSwitchesModel(model.aggregationSwitches, 'aggregation');
var edgeSwitches = makeSwitchesModel(model.edgeSwitches, 'edge');
- var switches = coreSwitches.concat(aggregationSwitches).concat(edgeSwitches);
-
+ var switches = switchLayer.selectAll('g')
+ .data(coreSwitches.concat(aggregationSwitches).concat(edgeSwitches), function (d) {
+ return d.dpid;
+ });
switchesEnter(switches)
switchesUpdate(switches);