blob: fa0ccf2c6499cb7604c58a2543014109cb7c35e3 [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 + '?';
145 if (confirm(prompt)) {
146 deleteFlow(d);
147 d.deletePending = true;
148 updateSelectedFlows();
149
150 setTimeout(function () {
151 d.deletePending = false;
152 updateSelectedFlows();
153 }, pendingTimeout)
154 };
155 }
156 });
157
158 row.select('.flowId')
159 .text(function (d) {
160 if (d) {
161 if (d.flowId) {
162 return d.flowId;
163 } else {
164 return '0x--';
165 }
166 }
167 })
168 .classed('pending', function (d) {
169 return d && (d.createPending || d.deletePending);
170 });
171
172 row.select('.srcDPID')
173 .text(function (d) {
174 if (d) {
175 return d.srcDpid;
176 }
177 });
178
179 row.select('.dstDPID')
180 .text(function (d) {
181 if (d) {
182 return d.dstDpid;
183 }
184 });
185 }
186
187 var flows = d3.select('#selectedFlows')
188 .selectAll('.selectedFlow')
189 .data(selectedFlows);
190
191 flows.enter()
192 .append('div')
193 .classed('selectedFlow', true)
194 .each(rowEnter);
195
196 flows.each(rowUpdate);
197
198 flows.exit().remove();
199}
200
Paul Greyson7a300822013-04-09 12:57:49 -0700201function startIPerfForFlow(flow) {
202 var duration = 10000; // seconds
203 var interval = 100; // ms. this is defined by the server
204 var updateRate = 2000; // ms
205 var pointsToDisplay = 1000;
206
207 function makePoints() {
208 var pts = [];
209 var i;
210 for (i=0; i < pointsToDisplay; ++i) {
211 var sample = flow.iperfData.samples[i];
Paul Greysonbbd708b2013-04-09 22:37:31 -0700212 var height = 28 * sample/1000000;
213 if (height > 28)
214 height = 28;
Paul Greyson7a300822013-04-09 12:57:49 -0700215 pts.push({
216 x: i * 1000/(pointsToDisplay-1),
Paul Greysonbbd708b2013-04-09 22:37:31 -0700217 y: 30 - height
Paul Greyson7a300822013-04-09 12:57:49 -0700218 })
219 }
220 return pts;
221 }
222
223 if (flow.flowId) {
224 console.log('starting iperf for: ' + flow.flowId);
225 startIPerf(flow, duration, updateRate/interval);
226 flow.iperfDisplayInterval = setInterval(function () {
227 if (flow.iperfData) {
228 while (flow.iperfData.samples.length < pointsToDisplay) {
229 flow.iperfData.samples.push(0);
230 }
231 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
232 iperfPath.attr('d', line(makePoints()));
233 flow.iperfData.samples.shift();
234 }
235
236
237 }, interval);
Paul Greyson2794ca62013-04-10 00:33:30 -0700238
239 var animationTimeout;
240
Paul Greyson7a300822013-04-09 12:57:49 -0700241 flow.iperfFetchInterval = setInterval(function () {
242 getIPerfData(flow, function (data) {
243 try {
244 if (!flow.iperfData) {
245 flow.iperfData = {
246 samples: []
247 };
248 var i;
249 for (i = 0; i < pointsToDisplay; ++i) {
250 flow.iperfData.samples.push(0);
251 }
252 }
253
254 var iperfData = JSON.parse(data);
255
256// console.log(iperfData.timestamp);
257
258 // if the data is fresh
259 if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
260
Paul Greyson2794ca62013-04-10 00:33:30 -0700261 var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
262 startFlowAnimation(flowSelection);
263 clearTimeout(animationTimeout);
264 // kill the animation if iperfdata stops flowing
265 animationTimeout = setTimeout(function () {
266 stopFlowAnimation(flowSelection);
267 }, updateRate*1.5);
268
Paul Greyson7a300822013-04-09 12:57:49 -0700269 while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
270 flow.iperfData.samples.shift();
271 }
272
273 iperfData.samples.forEach(function (s) {
274 flow.iperfData.samples.push(s);
275 });
276 }
277 flow.iperfData.timestamp = iperfData.timestamp;
278 } catch (e) {
279 console.log('bad iperf data: ' + data);
280 }
281// console.log(data);
282 });
283 }, updateRate/2); // over sample to avoid gaps
284 }
285}
286
287function updateSelectedFlows() {
288 // make sure that all of the selected flows are either
289 // 1) valid (meaning they are in the latest list of flows)
290 // 2) pending
291 if (model) {
292 var flowMap = {};
293 model.flows.forEach(function (flow) {
294 flowMap[makeFlowKey(flow)] = flow;
295 });
296
297 var newSelectedFlows = [];
298 selectedFlows.forEach(function (flow) {
299 if (flow) {
300 var liveFlow = flowMap[makeFlowKey(flow)];
301 if (liveFlow) {
302 newSelectedFlows.push(liveFlow);
303 liveFlow.deletePending = flow.deletePending;
304 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
305 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
306 } else if (flow.createPending) {
307 newSelectedFlows.push(flow);
308 } else if (hasIPerf(flow)) {
309 clearIPerf(flow);
310 }
311 }
312 });
313 selectedFlows = newSelectedFlows;
314 }
315 selectedFlows.forEach(function (flow) {
316 if (!hasIPerf(flow)) {
317 startIPerfForFlow(flow);
318 }
319 });
320 while (selectedFlows.length < 3) {
321 selectedFlows.push(null);
322 }
323
324 updateSelectedFlowsTable();
Paul Greysonbbd708b2013-04-09 22:37:31 -0700325 // on app init, the table is updated before the svg is constructed
326 if (flowLayer) {
327 updateSelectedFlowsTopology();
328 }
Paul Greyson7a300822013-04-09 12:57:49 -0700329}
330
331function selectFlow(flow) {
332 var flowKey = makeFlowKey(flow);
333 var alreadySelected = false;
334 selectedFlows.forEach(function (f) {
335 if (f && makeFlowKey(f) === flowKey) {
336 alreadySelected = true;
337 }
338 });
339
340 if (!alreadySelected) {
341 selectedFlows.unshift(flow);
342 selectedFlows = selectedFlows.slice(0, 3);
343 updateSelectedFlows();
344 }
345}
346
347function hasIPerf(flow) {
348 return flow && flow.iperfFetchInterval;
349}
350
351function clearIPerf(flow) {
352 console.log('clearing iperf interval for: ' + flow.flowId);
353 clearInterval(flow.iperfFetchInterval);
354 delete flow.iperfFetchInterval;
355 clearInterval(flow.iperfDisplayInterval);
356 delete flow.iperfDisplayInterval;
357 delete flow.iperfData;
358}
359
360function deselectFlow(flow, ifCreatePending) {
Paul Greysonaf750bf2013-04-09 23:26:37 -0700361 if (!flow) {
362 return;
363 }
364
Paul Greyson7a300822013-04-09 12:57:49 -0700365 var flowKey = makeFlowKey(flow);
366 var newSelectedFlows = [];
367 selectedFlows.forEach(function (flow) {
368 if (!flow ||
369 flowKey !== makeFlowKey(flow) ||
370 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
371 newSelectedFlows.push(flow);
372 } else {
373 if (hasIPerf(flow)) {
374 clearIPerf(flow);
375 }
376 }
377 });
378 selectedFlows = newSelectedFlows;
379 while (selectedFlows.length < 3) {
380 selectedFlows.push(null);
381 }
382
383 updateSelectedFlows();
384}
385
386function deselectFlowIfCreatePending(flow) {
387 deselectFlow(flow, true);
388}
389
390function showFlowChooser() {
391 function rowEnter(d) {
392 var row = d3.select(this);
393
394 row.append('div')
395 .classed('black-eye', true).
396 on('click', function () {
397 selectFlow(d);
398 });
399
400 row.append('div')
401 .classed('flowId', true)
402 .text(function (d) {
403 return d.flowId;
404 });
405
406 row.append('div')
407 .classed('srcDPID', true)
408 .text(function (d) {
409 return d.srcDpid;
410 });
411
412
413 row.append('div')
414 .classed('dstDPID', true)
415 .text(function (d) {
416 return d.dstDpid;
417 });
418
419 }
420
Paul Greysonf3070172013-04-09 23:37:00 -0700421 var flowChooser = d3.select(document.getElementById('flowChooser'));
422 flowChooser.html('');
423 flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
424 .style('-webkit-transition');
425
426 var flows = flowChooser
Paul Greyson7a300822013-04-09 12:57:49 -0700427 .append('div')
428 .style('pointer-events', 'auto')
429 .selectAll('.selectedFlow')
430 .data(model.flows)
431 .enter()
432 .append('div')
433 .classed('selectedFlow', true)
434 .each(rowEnter);
435
Paul Greysonf3070172013-04-09 23:37:00 -0700436
Paul Greyson7a300822013-04-09 12:57:49 -0700437 setTimeout(function () {
Paul Greysonf3070172013-04-09 23:37:00 -0700438 flowChooser.style('-webkit-transition', '-webkit-transform .25s');
439 setTimeout(function () {
440 flowChooser.style('-webkit-transform', 'translate3d(0,0,0)');
441 }, 0);
442
443
Paul Greyson7a300822013-04-09 12:57:49 -0700444 d3.select(document.body).on('click', function () {
Paul Greysonf3070172013-04-09 23:37:00 -0700445 flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
Paul Greyson7a300822013-04-09 12:57:49 -0700446 d3.select(document.body).on('click', null);
447 });
448 }, 0);
449}