blob: e21af0da51cba6d3b91e8292a0907bac6497fd34 [file] [log] [blame]
Paul Greyson2794ca62013-04-10 00:33:30 -07001function startFlowAnimation(flow) {
2 if (flow.select('animate').empty()) {
3 flow.append('svg:animate')
4 .attr('attributeName', 'stroke-dashoffset')
5 .attr('attributeType', 'xml')
6 .attr('from', '500')
7 .attr('to', '-500')
8 .attr('dur', '20s')
9 .attr('repeatCount', 'indefinite');
10 }
11}
12
13function stopFlowAnimation(flow) {
14 flow.select('animate').remove();
15}
16
17
Paul Greyson7a300822013-04-09 12:57:49 -070018function updateSelectedFlowsTopology() {
19 // DRAW THE FLOWS
20 var topologyFlows = [];
21 selectedFlows.forEach(function (flow) {
22 if (flow) {
23 topologyFlows.push(flow);
24 }
25 });
26
Paul Greysonbbd708b2013-04-09 22:37:31 -070027 var flows = flowLayer.selectAll('.flow').data(topologyFlows);
Paul Greyson7a300822013-04-09 12:57:49 -070028
29 flows.enter().append("svg:path").attr('class', 'flow')
30 .attr('stroke-dasharray', '4, 10')
Paul Greyson7a300822013-04-09 12:57:49 -070031
32 flows.exit().remove();
33
34 flows.attr('d', function (d) {
35 if (!d) {
36 return;
37 }
38 var pts = [];
Paul Greyson45aceb22013-04-09 22:17:03 -070039 if (d.createPending) {
Paul Greyson7a300822013-04-09 12:57:49 -070040 // create a temporary vector to indicate the pending flow
41 var s1 = d3.select(document.getElementById(d.srcDpid));
42 var s2 = d3.select(document.getElementById(d.dstDpid));
43
44 var pt1 = document.querySelector('svg').createSVGPoint();
45 pt1.x = s1.attr('x');
46 pt1.y = s1.attr('y');
Paul Greysonbbd708b2013-04-09 22:37:31 -070047 if (drawingRings) {
48 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
49 }
Paul Greyson7a300822013-04-09 12:57:49 -070050 pts.push(pt1);
51
52 var pt2 = document.querySelector('svg').createSVGPoint();
53 pt2.x = s2.attr('x');
54 pt2.y = s2.attr('y');
Paul Greysonbbd708b2013-04-09 22:37:31 -070055 if (drawingRings) {
56 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
57 }
Paul Greyson7a300822013-04-09 12:57:49 -070058 pts.push(pt2);
59
Paul Greyson45aceb22013-04-09 22:17:03 -070060 } else if (d.dataPath && d.dataPath.flowEntries) {
Paul Greyson7a300822013-04-09 12:57:49 -070061 d.dataPath.flowEntries.forEach(function (flowEntry) {
62 var s = d3.select(document.getElementById(flowEntry.dpid.value));
63 // s[0] is null if the flow entry refers to a non-existent switch
64 if (s[0][0]) {
65 var pt = document.querySelector('svg').createSVGPoint();
66 pt.x = s.attr('x');
67 pt.y = s.attr('y');
Paul Greysonbbd708b2013-04-09 22:37:31 -070068 if (drawingRings) {
69 pt = pt.matrixTransform(s[0][0].getCTM());
70 }
Paul Greyson7a300822013-04-09 12:57:49 -070071 pts.push(pt);
72 } else {
73 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
74 }
75 });
76 }
77 if (pts.length) {
78 return line(pts);
79 } else {
80 return "M0,0";
81 }
82 })
83 .attr('id', function (d) {
84 if (d) {
85 return makeFlowKey(d);
86 }
87 })
88 .classed('pending', function (d) {
89 return d && (d.createPending || d.deletePending);
90 });
Paul Greyson7a300822013-04-09 12:57:49 -070091}
92
93function updateSelectedFlowsTable() {
94 function rowEnter(d) {
95 var row = d3.select(this);
96 row.append('div').classed('deleteFlow', true);
97 row.append('div').classed('flowId', true);
98 row.append('div').classed('srcDPID', true);
99 row.append('div').classed('dstDPID', true);
100 row.append('div').classed('iperf', true);
101
102 row.select('.iperf')
103 .append('div')
104 .attr('class', 'iperf-container')
105 .append('svg:svg')
106 .attr('viewBox', '0 0 1000 32')
107 .attr('preserveAspectRatio', 'none')
108 .append('svg:g')
109 .append('svg:path')
110 .attr('class', 'iperfdata');
111
112 row.on('mouseover', function (d) {
113 if (d) {
114 var path = document.getElementById(makeFlowKey(d));
115 d3.select(path).classed('highlight', true);
116 }
117 });
118 row.on('mouseout', function (d) {
119 if (d) {
120 var path = document.getElementById(makeFlowKey(d));
121 d3.select(path).classed('highlight', false);
122 }
123 });
124 }
125
126 function rowUpdate(d) {
127 var row = d3.select(this);
128 row.attr('id', function (d) {
129 if (d) {
130 return makeSelectedFlowKey(d);
131 }
132 });
133
134 if (!d || !hasIPerf(d)) {
135 row.select('.iperfdata')
136 .attr('d', 'M0,0');
137 }
138
139 row.select('.deleteFlow').on('click', function () {
140 deselectFlow(d);
141 });
142 row.on('dblclick', function () {
143 if (d) {
144 var prompt = 'Delete flow ' + d.flowId + '?';
Paul Greyson28cf92b2013-04-10 13:15:06 -0700145 doConfirm(prompt, function (result) {
146 if (result) {
147 deleteFlow(d);
148 d.deletePending = true;
Paul Greyson7a300822013-04-09 12:57:49 -0700149 updateSelectedFlows();
Paul Greyson28cf92b2013-04-10 13:15:06 -0700150
151 setTimeout(function () {
152 d.deletePending = false;
153 updateSelectedFlows();
154 }, pendingTimeout)
155 };
156 });
Paul Greyson7a300822013-04-09 12:57:49 -0700157 }
158 });
159
160 row.select('.flowId')
161 .text(function (d) {
162 if (d) {
163 if (d.flowId) {
164 return d.flowId;
165 } else {
166 return '0x--';
167 }
168 }
169 })
170 .classed('pending', function (d) {
171 return d && (d.createPending || d.deletePending);
172 });
173
174 row.select('.srcDPID')
175 .text(function (d) {
176 if (d) {
177 return d.srcDpid;
178 }
179 });
180
181 row.select('.dstDPID')
182 .text(function (d) {
183 if (d) {
184 return d.dstDpid;
185 }
186 });
187 }
188
189 var flows = d3.select('#selectedFlows')
190 .selectAll('.selectedFlow')
191 .data(selectedFlows);
192
193 flows.enter()
194 .append('div')
195 .classed('selectedFlow', true)
196 .each(rowEnter);
197
198 flows.each(rowUpdate);
199
200 flows.exit().remove();
201}
202
Paul Greyson7a300822013-04-09 12:57:49 -0700203function startIPerfForFlow(flow) {
204 var duration = 10000; // seconds
205 var interval = 100; // ms. this is defined by the server
206 var updateRate = 2000; // ms
207 var pointsToDisplay = 1000;
208
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700209 function makeGraph(iperfData) {
210 var d = 'M0,0';
211
Paul Greysondc0bf822013-04-11 13:45:03 -0700212 var now = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
213
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700214 if (iperfData.samples && iperfData.samples.length) {
215
216 var lastX;
217 var i = iperfData.samples.length - 1;
218 while (i) {
219 var sample = iperfData.samples[i];
220
Paul Greysondc0bf822013-04-11 13:45:03 -0700221 var x = (1000 - (now - sample.time)*10);
Paul Greyson498e0142013-04-11 12:56:39 -0700222 // workaround for discontinuity in iperf data
223 if (x < 0) {
224 i -= 1;
225 continue;
226 }
227
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700228 var y = 28 * sample.value/1000000;
229 if (y > 28) {
230 y = 28;
231 }
232 if (i == iperfData.samples.length - 1) {
233 d = 'M' + x + ',30';
234 }
235
236 // handle gaps
237 // 1.5 for rounding error
238 if (lastX && lastX - x > 1.5) {
239 d += 'L' + lastX + ',30';
240 d += 'M' + x + ',30'
241 }
242 lastX = x;
243
244 d += 'L' + x + ',' + (30-y);
245
246 i -= 1;
Paul Greysonf7625ce2013-04-11 10:39:12 -0700247 }
Paul Greyson498e0142013-04-11 12:56:39 -0700248 d += 'L' + lastX + ',30';
Paul Greyson7a300822013-04-09 12:57:49 -0700249 }
Paul Greysonf7625ce2013-04-11 10:39:12 -0700250 return d;
Paul Greyson7a300822013-04-09 12:57:49 -0700251 }
252
253 if (flow.flowId) {
254 console.log('starting iperf for: ' + flow.flowId);
255 startIPerf(flow, duration, updateRate/interval);
256 flow.iperfDisplayInterval = setInterval(function () {
257 if (flow.iperfData) {
Paul Greyson7a300822013-04-09 12:57:49 -0700258 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700259 iperfPath.attr('d', makeGraph(flow.iperfData));
Paul Greyson7a300822013-04-09 12:57:49 -0700260 }
261
262
263 }, interval);
Paul Greyson2794ca62013-04-10 00:33:30 -0700264
265 var animationTimeout;
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700266 flow.iperfData = {
267 samples: []
268 }
Paul Greyson2794ca62013-04-10 00:33:30 -0700269
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700270 var lastTime;
Paul Greyson7a300822013-04-09 12:57:49 -0700271 flow.iperfFetchInterval = setInterval(function () {
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700272 console.log('Requesting iperf data');
Paul Greyson7a300822013-04-09 12:57:49 -0700273 getIPerfData(flow, function (data) {
274 try {
Paul Greyson7a300822013-04-09 12:57:49 -0700275 var iperfData = JSON.parse(data);
276
277// console.log(iperfData.timestamp);
278
279 // if the data is fresh
280 if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
281
Paul Greyson2794ca62013-04-10 00:33:30 -0700282 var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
283 startFlowAnimation(flowSelection);
284 clearTimeout(animationTimeout);
285 // kill the animation if iperfdata stops flowing
286 animationTimeout = setTimeout(function () {
287 stopFlowAnimation(flowSelection);
288 }, updateRate*1.5);
289
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700290 var endTime = Math.floor(iperfData['end-time']*10)/10;
291
292 var startTime = endTime - (iperfData.samples.length * interval/1000);
293 // set now on the first buffer
Paul Greysondc0bf822013-04-11 13:45:03 -0700294 if (!flow.iperfData.startTime) {
295 flow.iperfData.startTime = startTime;
296 flow.iperfData.localNow = Date.now();
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700297 }
298
299 console.log('iperf buffer start time: ' + startTime);
300 if (lastTime && (startTime - lastTime) > updateRate/1000) {
301 console.log('iperf buffer gap: ' + startTime + ',' + lastTime);
302 }
303 lastTime = startTime;
304
305 // clear out the old data
Paul Greyson7a300822013-04-09 12:57:49 -0700306 while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
307 flow.iperfData.samples.shift();
308 }
309
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700310 // if the client gets too out of sync, resynchronize
Paul Greysondc0bf822013-04-11 13:45:03 -0700311 var clientNow = flow.iperfData.startTime + (Date.now() - flow.iperfData.localNow)/1000;
312 if (Math.abs(clientNow - startTime) > (updateRate/1000) * 2) {
313 console.log('resynchronizing now: ' + clientNow + ' => ' + startTime);
314 flow.iperfData.startTime = startTime;
315 flow.iperfData.localNow = Date.now();
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700316 }
317
318 var time = startTime;
Paul Greyson7a300822013-04-09 12:57:49 -0700319 iperfData.samples.forEach(function (s) {
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700320 var sample = {
321 time: time,
322 value: s
323 };
324 flow.iperfData.samples.push(sample);
325 time += interval/1000;
Paul Greyson7a300822013-04-09 12:57:49 -0700326 });
327 }
328 flow.iperfData.timestamp = iperfData.timestamp;
329 } catch (e) {
330 console.log('bad iperf data: ' + data);
331 }
332// console.log(data);
333 });
Paul Greyson8e3bfd82013-04-11 12:08:26 -0700334 }, updateRate*.5); // over sample to avoid gaps
Paul Greyson7a300822013-04-09 12:57:49 -0700335 }
336}
337
338function updateSelectedFlows() {
339 // make sure that all of the selected flows are either
340 // 1) valid (meaning they are in the latest list of flows)
341 // 2) pending
342 if (model) {
343 var flowMap = {};
344 model.flows.forEach(function (flow) {
345 flowMap[makeFlowKey(flow)] = flow;
346 });
347
348 var newSelectedFlows = [];
349 selectedFlows.forEach(function (flow) {
350 if (flow) {
351 var liveFlow = flowMap[makeFlowKey(flow)];
352 if (liveFlow) {
353 newSelectedFlows.push(liveFlow);
354 liveFlow.deletePending = flow.deletePending;
355 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
356 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
357 } else if (flow.createPending) {
358 newSelectedFlows.push(flow);
359 } else if (hasIPerf(flow)) {
360 clearIPerf(flow);
361 }
362 }
363 });
364 selectedFlows = newSelectedFlows;
365 }
366 selectedFlows.forEach(function (flow) {
367 if (!hasIPerf(flow)) {
368 startIPerfForFlow(flow);
369 }
370 });
371 while (selectedFlows.length < 3) {
372 selectedFlows.push(null);
373 }
374
375 updateSelectedFlowsTable();
Paul Greysonbbd708b2013-04-09 22:37:31 -0700376 // on app init, the table is updated before the svg is constructed
377 if (flowLayer) {
378 updateSelectedFlowsTopology();
379 }
Paul Greyson7a300822013-04-09 12:57:49 -0700380}
381
382function selectFlow(flow) {
383 var flowKey = makeFlowKey(flow);
384 var alreadySelected = false;
385 selectedFlows.forEach(function (f) {
386 if (f && makeFlowKey(f) === flowKey) {
387 alreadySelected = true;
388 }
389 });
390
391 if (!alreadySelected) {
392 selectedFlows.unshift(flow);
393 selectedFlows = selectedFlows.slice(0, 3);
394 updateSelectedFlows();
395 }
396}
397
398function hasIPerf(flow) {
399 return flow && flow.iperfFetchInterval;
400}
401
402function clearIPerf(flow) {
403 console.log('clearing iperf interval for: ' + flow.flowId);
404 clearInterval(flow.iperfFetchInterval);
405 delete flow.iperfFetchInterval;
406 clearInterval(flow.iperfDisplayInterval);
407 delete flow.iperfDisplayInterval;
408 delete flow.iperfData;
409}
410
411function deselectFlow(flow, ifCreatePending) {
Paul Greysonaf750bf2013-04-09 23:26:37 -0700412 if (!flow) {
413 return;
414 }
415
Paul Greyson7a300822013-04-09 12:57:49 -0700416 var flowKey = makeFlowKey(flow);
417 var newSelectedFlows = [];
418 selectedFlows.forEach(function (flow) {
419 if (!flow ||
420 flowKey !== makeFlowKey(flow) ||
421 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
422 newSelectedFlows.push(flow);
423 } else {
424 if (hasIPerf(flow)) {
425 clearIPerf(flow);
426 }
427 }
428 });
429 selectedFlows = newSelectedFlows;
430 while (selectedFlows.length < 3) {
431 selectedFlows.push(null);
432 }
433
434 updateSelectedFlows();
435}
436
437function deselectFlowIfCreatePending(flow) {
438 deselectFlow(flow, true);
439}
440
441function showFlowChooser() {
442 function rowEnter(d) {
443 var row = d3.select(this);
444
445 row.append('div')
446 .classed('black-eye', true).
447 on('click', function () {
448 selectFlow(d);
449 });
450
451 row.append('div')
452 .classed('flowId', true)
453 .text(function (d) {
454 return d.flowId;
455 });
456
457 row.append('div')
458 .classed('srcDPID', true)
459 .text(function (d) {
460 return d.srcDpid;
461 });
462
463
464 row.append('div')
465 .classed('dstDPID', true)
466 .text(function (d) {
467 return d.dstDpid;
468 });
469
470 }
471
Paul Greysonf3070172013-04-09 23:37:00 -0700472 var flowChooser = d3.select(document.getElementById('flowChooser'));
473 flowChooser.html('');
474 flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
475 .style('-webkit-transition');
476
477 var flows = flowChooser
Paul Greyson7a300822013-04-09 12:57:49 -0700478 .append('div')
479 .style('pointer-events', 'auto')
480 .selectAll('.selectedFlow')
481 .data(model.flows)
482 .enter()
483 .append('div')
484 .classed('selectedFlow', true)
485 .each(rowEnter);
486
Paul Greysonf3070172013-04-09 23:37:00 -0700487
Paul Greyson7a300822013-04-09 12:57:49 -0700488 setTimeout(function () {
Paul Greysonf3070172013-04-09 23:37:00 -0700489 flowChooser.style('-webkit-transition', '-webkit-transform .25s');
490 setTimeout(function () {
491 flowChooser.style('-webkit-transform', 'translate3d(0,0,0)');
492 }, 0);
493
494
Paul Greyson7a300822013-04-09 12:57:49 -0700495 d3.select(document.body).on('click', function () {
Paul Greysonf3070172013-04-09 23:37:00 -0700496 flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
Paul Greyson7a300822013-04-09 12:57:49 -0700497 d3.select(document.body).on('click', null);
498 });
499 }, 0);
500}