blob: 32f934748dab55fa844e8d79ec2d64374ca51dc1 [file] [log] [blame]
function startFlowAnimation(flow) {
if (flow.select('animate').empty()) {
flow.append('svg:animate')
.attr('attributeName', 'stroke-dashoffset')
.attr('attributeType', 'xml')
.attr('from', '500')
.attr('to', '-500')
.attr('dur', '20s')
.attr('repeatCount', 'indefinite');
}
}
function stopFlowAnimation(flow) {
flow.select('animate').remove();
}
function updateSelectedFlowsTopology() {
// DRAW THE FLOWS
var topologyFlows = [];
selectedFlows.forEach(function (flow) {
if (flow) {
topologyFlows.push(flow);
}
});
var flows = flowLayer.selectAll('.flow').data(topologyFlows);
flows.enter().append("svg:path").attr('class', 'flow')
.attr('stroke-dasharray', '4, 10')
flows.exit().remove();
flows.attr('d', function (d) {
if (!d) {
return;
}
var pts = [];
if (d.createPending) {
// create a temporary vector to indicate the pending flow
var s1 = d3.select(document.getElementById(d.srcDpid));
var s2 = d3.select(document.getElementById(d.dstDpid));
var pt1 = document.querySelector('svg').createSVGPoint();
pt1.x = s1.attr('x');
pt1.y = s1.attr('y');
if (drawingRings) {
pt1 = pt1.matrixTransform(s1[0][0].getCTM());
}
pts.push(pt1);
var pt2 = document.querySelector('svg').createSVGPoint();
pt2.x = s2.attr('x');
pt2.y = s2.attr('y');
if (drawingRings) {
pt2 = pt2.matrixTransform(s2[0][0].getCTM());
}
pts.push(pt2);
} else if (d.dataPath && d.dataPath.flowEntries) {
d.dataPath.flowEntries.forEach(function (flowEntry) {
var s = d3.select(document.getElementById(flowEntry.dpid.value));
// s[0] is null if the flow entry refers to a non-existent switch
if (s[0][0]) {
var pt = document.querySelector('svg').createSVGPoint();
pt.x = s.attr('x');
pt.y = s.attr('y');
if (drawingRings) {
pt = pt.matrixTransform(s[0][0].getCTM());
}
pts.push(pt);
} else {
console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
}
});
}
if (pts.length) {
return line(pts);
} else {
return "M0,0";
}
})
.attr('id', function (d) {
if (d) {
return makeFlowKey(d);
}
})
.classed('pending', function (d) {
return d && (d.createPending || d.deletePending);
});
}
function updateSelectedFlowsTable() {
function rowEnter(d) {
var row = d3.select(this);
row.append('div').classed('deleteFlow', true);
row.append('div').classed('flowId', true);
row.append('div').classed('srcDPID', true);
row.append('div').classed('dstDPID', true);
row.append('div').classed('iperf', true);
row.select('.iperf')
.append('div')
.attr('class', 'iperf-container')
.append('svg:svg')
.attr('viewBox', '0 0 1000 32')
.attr('preserveAspectRatio', 'none')
.append('svg:g')
.append('svg:path')
.attr('class', 'iperfdata');
row.on('mouseover', function (d) {
if (d) {
var path = document.getElementById(makeFlowKey(d));
d3.select(path).classed('highlight', true);
}
});
row.on('mouseout', function (d) {
if (d) {
var path = document.getElementById(makeFlowKey(d));
d3.select(path).classed('highlight', false);
}
});
}
function rowUpdate(d) {
var row = d3.select(this);
row.attr('id', function (d) {
if (d) {
return makeSelectedFlowKey(d);
}
});
if (!d || !hasIPerf(d)) {
row.select('.iperfdata')
.attr('d', 'M0,0');
}
row.select('.deleteFlow').on('click', function () {
deselectFlow(d);
});
row.on('dblclick', function () {
if (d) {
var prompt = 'Delete flow ' + d.flowId + '?';
doConfirm(prompt, function (result) {
if (result) {
deleteFlow(d);
d.deletePending = true;
updateSelectedFlows();
setTimeout(function () {
d.deletePending = false;
updateSelectedFlows();
}, pendingTimeout)
};
});
}
});
row.select('.flowId')
.text(function (d) {
if (d) {
if (d.flowId) {
return d.flowId;
} else {
return '0x--';
}
}
})
.classed('pending', function (d) {
return d && (d.createPending || d.deletePending);
});
row.select('.srcDPID')
.text(function (d) {
if (d) {
return d.srcDpid;
}
});
row.select('.dstDPID')
.text(function (d) {
if (d) {
return d.dstDpid;
}
});
}
var flows = d3.select('#selectedFlows')
.selectAll('.selectedFlow')
.data(selectedFlows);
flows.enter()
.append('div')
.classed('selectedFlow', true)
.each(rowEnter);
flows.each(rowUpdate);
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';
if (iperfData.samples && iperfData.samples.length) {
var lastX;
var i = iperfData.samples.length - 1;
while (i) {
var sample = iperfData.samples[i];
var x = (1000 - (iperfData.now - sample.time)*10);
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;
if (!i) {
d += 'L' + x + ',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));
flow.iperfData.now += interval/1000;
}
}, 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.now) {
flow.iperfData.now = startTime;
}
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
if (Math.abs(flow.iperfData.now - startTime) > (updateRate/1000) * 1.25) {
flow.iperfData.now = startTime;
}
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)
// 2) pending
if (model) {
var flowMap = {};
model.flows.forEach(function (flow) {
flowMap[makeFlowKey(flow)] = flow;
});
var newSelectedFlows = [];
selectedFlows.forEach(function (flow) {
if (flow) {
var liveFlow = flowMap[makeFlowKey(flow)];
if (liveFlow) {
newSelectedFlows.push(liveFlow);
liveFlow.deletePending = flow.deletePending;
liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
} else if (flow.createPending) {
newSelectedFlows.push(flow);
} else if (hasIPerf(flow)) {
clearIPerf(flow);
}
}
});
selectedFlows = newSelectedFlows;
}
selectedFlows.forEach(function (flow) {
if (!hasIPerf(flow)) {
startIPerfForFlow(flow);
}
});
while (selectedFlows.length < 3) {
selectedFlows.push(null);
}
updateSelectedFlowsTable();
// on app init, the table is updated before the svg is constructed
if (flowLayer) {
updateSelectedFlowsTopology();
}
}
function selectFlow(flow) {
var flowKey = makeFlowKey(flow);
var alreadySelected = false;
selectedFlows.forEach(function (f) {
if (f && makeFlowKey(f) === flowKey) {
alreadySelected = true;
}
});
if (!alreadySelected) {
selectedFlows.unshift(flow);
selectedFlows = selectedFlows.slice(0, 3);
updateSelectedFlows();
}
}
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;
}
var flowKey = makeFlowKey(flow);
var newSelectedFlows = [];
selectedFlows.forEach(function (flow) {
if (!flow ||
flowKey !== makeFlowKey(flow) ||
flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
newSelectedFlows.push(flow);
} else {
if (hasIPerf(flow)) {
clearIPerf(flow);
}
}
});
selectedFlows = newSelectedFlows;
while (selectedFlows.length < 3) {
selectedFlows.push(null);
}
updateSelectedFlows();
}
function deselectFlowIfCreatePending(flow) {
deselectFlow(flow, true);
}
function showFlowChooser() {
function rowEnter(d) {
var row = d3.select(this);
row.append('div')
.classed('black-eye', true).
on('click', function () {
selectFlow(d);
});
row.append('div')
.classed('flowId', true)
.text(function (d) {
return d.flowId;
});
row.append('div')
.classed('srcDPID', true)
.text(function (d) {
return d.srcDpid;
});
row.append('div')
.classed('dstDPID', true)
.text(function (d) {
return d.dstDpid;
});
}
var flowChooser = d3.select(document.getElementById('flowChooser'));
flowChooser.html('');
flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
.style('-webkit-transition');
var flows = flowChooser
.append('div')
.style('pointer-events', 'auto')
.selectAll('.selectedFlow')
.data(model.flows)
.enter()
.append('div')
.classed('selectedFlow', true)
.each(rowEnter);
setTimeout(function () {
flowChooser.style('-webkit-transition', '-webkit-transform .25s');
setTimeout(function () {
flowChooser.style('-webkit-transform', 'translate3d(0,0,0)');
}, 0);
d3.select(document.body).on('click', function () {
flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
d3.select(document.body).on('click', null);
});
}, 0);
}