Merge pull request #372 from pgreyson/master

iperf changes. add "K" action to kill a controller without wait state displaying
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index 369e9c4..3616d22 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,3 +1,8 @@
+** 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
+- Add "K" action for killing a controller
+
 ** April 9, 2013 **
 - display number of flows for each core<->core link
 - graphics tweaks
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 9f1ecf1..2b07e33 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -167,7 +167,7 @@
 
 path.flow {
 	fill: none;
-	stroke-width: 5px;
+	stroke-width: 8px;
 	stroke: rgba(255, 255, 255, .35);
 }
 
@@ -181,13 +181,13 @@
 }
 
 path.iperfdata {
-	fill: none;
+	fill: #ccc;
 	stroke-width: 1px;
 	stroke: #ccc;
 }
 
 .iperf {
-	background-color: #222;
+	background-color: black;
 }
 
 #selectedFlowsHeader .iperf {
@@ -293,7 +293,7 @@
 text {
 	stroke: none;
 	fill: white;
-	font-size: 16px;
+	font-size: 22px;
 	pointer-events: none;
 }
 
@@ -469,7 +469,7 @@
 }
 
 .flowCount {
-	font-size: 20px;
+	font-size: 24px;
 	fill: rgba(255, 255, 255, .75);
 }
 
@@ -528,3 +528,21 @@
 	background-color: black;
 }
 
+select {
+	margin-top: 1em;
+	-webkit-appearance: none;
+	border-radius: 0px;
+	font-size: 18px;
+	padding: .5em;
+	background-color: black;
+	color: white;
+	border: 1px solid white;
+}
+
+select:after {
+	content: 'a';
+	position: absolute;
+	right: 0px;
+	top: 0px;
+}
+
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index 1b88b4a..460188f 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -23,10 +23,11 @@
 		<div id='cluster-label'>ONOS Node Cluster</div>
 		<div id='controllers'></div>
 		<div id='actions'>
-			<div id='action-all' class='action'>A</div>
 			<div id='action-local' class='action'>1</div>
-			<div id='action-scale' class='action'>S</div>
 			<div id='action-reset' class='action'>R</div>
+			<div id='action-scale' class='action'>S</div>
+			<div id='action-all' class='action'>A</div>
+			<div id='action-kill' class='action'>K</div>
 		</div>
 	</div>
 
@@ -50,6 +51,7 @@
 	<div id='confirm-background'></div>
 	<div id='confirm-panel'>
 		<div id='confirm-prompt'>A PROMPT</div>
+		<select id='confirm-select'></select>
 		<div id='confirm-buttons'>
 			<div id='confirm-ok' class='confirm-button'>OK</div>
 			<div id='confirm-cancel' class='confirm-button'>CANCEL</div>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index 2689b43..94c41e2 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -36,17 +36,24 @@
 	});
 }
 
+// workaround for another Chrome v25 bug
+// viewbox transform stuff doesn't work in combination with browser zoom
+// also works in Chrome v27
+function zoomWorkaround() {
+	var zoom = window.document.body.clientWidth/window.document.width;
+	// workaround does not seem to be effective for transforming mouse coordinates
+	// map display does not use the transform stuff, so commenting out
+//	d3.select('#svg-container').style('zoom',  zoom);
+}
+
+d3.select(window).on('resize', zoomWorkaround);
+
 appInit(function () {
 	// workaround for Chrome v25 bug
 	// if executed immediately, the view box transform logic doesn't work properly
 	// fixed in Chrome v27
 	setTimeout(function () {
-
-		// workaround for another Chrome v25 bug
-		// viewbox transform stuff doesn't work in combination with browser zoom
-		// also works in Chrome v27
-		d3.select('#svg-container').style('zoom',  window.document.body.clientWidth/window.document.width);
-
+		zoomWorkaround();
 		sync();
 	}, 100);
 });
diff --git a/web/ons-demo/js/constants.js b/web/ons-demo/js/constants.js
index 0ef0882..51b3aa6 100644
--- a/web/ons-demo/js/constants.js
+++ b/web/ons-demo/js/constants.js
@@ -25,6 +25,6 @@
 ***************************************************************************************************/
 var widths = {
 	edge: 6,
-	aggregation: 12,
-	core: 18
+	aggregation: 16,
+	core: 20
 }
\ No newline at end of file
diff --git a/web/ons-demo/js/controller.js b/web/ons-demo/js/controller.js
index 211fa97..f9b4baf 100644
--- a/web/ons-demo/js/controller.js
+++ b/web/ons-demo/js/controller.js
@@ -54,6 +54,14 @@
 	switchControllerCmd: function (cmd) {
 		var url = '/proxy/gui/switchctrl/' + cmd;
 		callURL(url);
+	},
+	resetCmd: function () {
+		var url = '/proxy/gui/reset';
+		callURL(url);
+	},
+	scaleCmd: function () {
+		var url = '/proxy/gui/scale';
+		callURL(url);
 	}
 };
 
@@ -101,7 +109,11 @@
 	controllerFunctions.switchControllerCmd('local');
 }
 
-function switchAll() {
-	controllerFunctions.switchControllerCmd('all');
+function resetNetwork() {
+	controllerFunctions.resetCmd();
+}
+
+function scaleNetwork() {
+	controllerFunctions.scaleCmd();;
 }
 
diff --git a/web/ons-demo/js/flows.js b/web/ons-demo/js/flows.js
index bc30d11..e21af0d 100644
--- a/web/ons-demo/js/flows.js
+++ b/web/ons-demo/js/flows.js
@@ -206,20 +206,48 @@
 	var updateRate = 2000; // ms
 	var pointsToDisplay = 1000;
 
-	function makePoints() {
-		var pts = [];
-		var i;
-		for (i=0; i < pointsToDisplay; ++i) {
-			var sample = flow.iperfData.samples[i];
-			var height = 28 * sample/1000000;
-			if (height > 28)
-				height = 28;
-			pts.push({
-				x: i * 1000/(pointsToDisplay-1),
-				y: 30 - height
-			})
+	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 pts;
+		return d;
 	}
 
 	if (flow.flowId) {
@@ -227,32 +255,23 @@
 		startIPerf(flow, duration, updateRate/interval);
 		flow.iperfDisplayInterval = setInterval(function () {
 			if (flow.iperfData) {
-				while (flow.iperfData.samples.length < pointsToDisplay) {
-					flow.iperfData.samples.push(0);
-				}
 				var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
-				iperfPath.attr('d', line(makePoints()));
-				flow.iperfData.samples.shift();
+				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 {
-					if (!flow.iperfData) {
-						flow.iperfData = {
-							samples: []
-						};
-						var i;
-						for (i = 0; i < pointsToDisplay; ++i) {
-							flow.iperfData.samples.push(0);
-						}
-					}
-
 					var iperfData = JSON.parse(data);
 
 //				console.log(iperfData.timestamp);
@@ -268,12 +287,42 @@
 							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) {
-							flow.iperfData.samples.push(s);
+							var sample = {
+								time: time,
+								value: s
+							};
+							flow.iperfData.samples.push(sample);
+							time += interval/1000;
 						});
 					}
 					flow.iperfData.timestamp = iperfData.timestamp;
@@ -282,7 +331,7 @@
 				}
 //				console.log(data);
 			});
-		}, updateRate/2); // over sample to avoid gaps
+		}, updateRate*.5); // over sample to avoid gaps
 	}
 }
 
diff --git a/web/ons-demo/js/init.js b/web/ons-demo/js/init.js
index f7bd55b..bf812aa 100644
--- a/web/ons-demo/js/init.js
+++ b/web/ons-demo/js/init.js
@@ -26,11 +26,29 @@
 	});
 
 	d3.select('#action-scale').on('click', function () {
-		alert('scale')
+		var prompt = "Scale network?"
+		doConfirm(prompt, function (result) {
+			if (result) {
+				scaleNetwork();
+			}
+		});
 	});
 
 	d3.select('#action-reset').on('click', function () {
-		alert('reset')
+		var prompt = "Reset network?"
+		doConfirm(prompt, function (result) {
+			if (result) {
+				resetNetwork();
+			}
+		});
+	});
+
+	d3.select('#action-kill').on('click', function () {
+		var prompt = "Kill ONOS node?";
+		var options = model.activeControllers;
+		doConfirm(prompt, function (result) {
+			controllerDown(result);
+		}, options);
 	});
 
 	createTopologyView(cb);
diff --git a/web/ons-demo/js/utils.js b/web/ons-demo/js/utils.js
index 265c661..1b4c38c 100644
--- a/web/ons-demo/js/utils.js
+++ b/web/ons-demo/js/utils.js
@@ -204,10 +204,24 @@
 /***************************************************************************************************
 
 ***************************************************************************************************/
-function doConfirm(prompt, cb) {
+function doConfirm(prompt, cb, options) {
 	var confirm = d3.select('#confirm');
 	confirm.select('#confirm-prompt').text(prompt);
 
+	var select = d3.select(document.getElementById('confirm-select'));
+	if (options) {
+		select.style('display', 'block');
+		select.text('');
+		select.selectAll('option').
+			data(options)
+			.enter()
+				.append('option')
+					.attr('value', function (d) {return d})
+					.text(function (d) {return d});
+	} else {
+		select.style('display', 'none');
+	}
+
 	function show() {
 		confirm.style('display', '-webkit-box');
 		confirm.style('opacity', 0);
@@ -227,7 +241,11 @@
 	confirm.select('#confirm-ok').on('click', function () {
 		d3.select(this).on('click', null);
 		dismiss();
-		cb(true);
+		if (options) {
+			cb(select[0][0].value);
+		} else {
+			cb(true);
+		}
 	});
 
 	confirm.select('#confirm-cancel').on('click', function () {