blob: bc30d11bf3ada6c607a9ec41a3f9a7fec348b9ee [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
209 function makePoints() {
210 var pts = [];
211 var i;
212 for (i=0; i < pointsToDisplay; ++i) {
213 var sample = flow.iperfData.samples[i];
Paul Greysonbbd708b2013-04-09 22:37:31 -0700214 var height = 28 * sample/1000000;
215 if (height > 28)
216 height = 28;
Paul Greyson7a300822013-04-09 12:57:49 -0700217 pts.push({
218 x: i * 1000/(pointsToDisplay-1),
Paul Greysonbbd708b2013-04-09 22:37:31 -0700219 y: 30 - height
Paul Greyson7a300822013-04-09 12:57:49 -0700220 })
221 }
222 return pts;
223 }
224
225 if (flow.flowId) {
226 console.log('starting iperf for: ' + flow.flowId);
227 startIPerf(flow, duration, updateRate/interval);
228 flow.iperfDisplayInterval = setInterval(function () {
229 if (flow.iperfData) {
230 while (flow.iperfData.samples.length < pointsToDisplay) {
231 flow.iperfData.samples.push(0);
232 }
233 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
234 iperfPath.attr('d', line(makePoints()));
235 flow.iperfData.samples.shift();
236 }
237
238
239 }, interval);
Paul Greyson2794ca62013-04-10 00:33:30 -0700240
241 var animationTimeout;
242
Paul Greyson7a300822013-04-09 12:57:49 -0700243 flow.iperfFetchInterval = setInterval(function () {
244 getIPerfData(flow, function (data) {
245 try {
246 if (!flow.iperfData) {
247 flow.iperfData = {
248 samples: []
249 };
250 var i;
251 for (i = 0; i < pointsToDisplay; ++i) {
252 flow.iperfData.samples.push(0);
253 }
254 }
255
256 var iperfData = JSON.parse(data);
257
258// console.log(iperfData.timestamp);
259
260 // if the data is fresh
261 if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
262
Paul Greyson2794ca62013-04-10 00:33:30 -0700263 var flowSelection = d3.select(document.getElementById(makeFlowKey(flow)));
264 startFlowAnimation(flowSelection);
265 clearTimeout(animationTimeout);
266 // kill the animation if iperfdata stops flowing
267 animationTimeout = setTimeout(function () {
268 stopFlowAnimation(flowSelection);
269 }, updateRate*1.5);
270
Paul Greyson7a300822013-04-09 12:57:49 -0700271 while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
272 flow.iperfData.samples.shift();
273 }
274
275 iperfData.samples.forEach(function (s) {
276 flow.iperfData.samples.push(s);
277 });
278 }
279 flow.iperfData.timestamp = iperfData.timestamp;
280 } catch (e) {
281 console.log('bad iperf data: ' + data);
282 }
283// console.log(data);
284 });
285 }, updateRate/2); // over sample to avoid gaps
286 }
287}
288
289function updateSelectedFlows() {
290 // make sure that all of the selected flows are either
291 // 1) valid (meaning they are in the latest list of flows)
292 // 2) pending
293 if (model) {
294 var flowMap = {};
295 model.flows.forEach(function (flow) {
296 flowMap[makeFlowKey(flow)] = flow;
297 });
298
299 var newSelectedFlows = [];
300 selectedFlows.forEach(function (flow) {
301 if (flow) {
302 var liveFlow = flowMap[makeFlowKey(flow)];
303 if (liveFlow) {
304 newSelectedFlows.push(liveFlow);
305 liveFlow.deletePending = flow.deletePending;
306 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
307 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
308 } else if (flow.createPending) {
309 newSelectedFlows.push(flow);
310 } else if (hasIPerf(flow)) {
311 clearIPerf(flow);
312 }
313 }
314 });
315 selectedFlows = newSelectedFlows;
316 }
317 selectedFlows.forEach(function (flow) {
318 if (!hasIPerf(flow)) {
319 startIPerfForFlow(flow);
320 }
321 });
322 while (selectedFlows.length < 3) {
323 selectedFlows.push(null);
324 }
325
326 updateSelectedFlowsTable();
Paul Greysonbbd708b2013-04-09 22:37:31 -0700327 // on app init, the table is updated before the svg is constructed
328 if (flowLayer) {
329 updateSelectedFlowsTopology();
330 }
Paul Greyson7a300822013-04-09 12:57:49 -0700331}
332
333function selectFlow(flow) {
334 var flowKey = makeFlowKey(flow);
335 var alreadySelected = false;
336 selectedFlows.forEach(function (f) {
337 if (f && makeFlowKey(f) === flowKey) {
338 alreadySelected = true;
339 }
340 });
341
342 if (!alreadySelected) {
343 selectedFlows.unshift(flow);
344 selectedFlows = selectedFlows.slice(0, 3);
345 updateSelectedFlows();
346 }
347}
348
349function hasIPerf(flow) {
350 return flow && flow.iperfFetchInterval;
351}
352
353function clearIPerf(flow) {
354 console.log('clearing iperf interval for: ' + flow.flowId);
355 clearInterval(flow.iperfFetchInterval);
356 delete flow.iperfFetchInterval;
357 clearInterval(flow.iperfDisplayInterval);
358 delete flow.iperfDisplayInterval;
359 delete flow.iperfData;
360}
361
362function deselectFlow(flow, ifCreatePending) {
Paul Greysonaf750bf2013-04-09 23:26:37 -0700363 if (!flow) {
364 return;
365 }
366
Paul Greyson7a300822013-04-09 12:57:49 -0700367 var flowKey = makeFlowKey(flow);
368 var newSelectedFlows = [];
369 selectedFlows.forEach(function (flow) {
370 if (!flow ||
371 flowKey !== makeFlowKey(flow) ||
372 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
373 newSelectedFlows.push(flow);
374 } else {
375 if (hasIPerf(flow)) {
376 clearIPerf(flow);
377 }
378 }
379 });
380 selectedFlows = newSelectedFlows;
381 while (selectedFlows.length < 3) {
382 selectedFlows.push(null);
383 }
384
385 updateSelectedFlows();
386}
387
388function deselectFlowIfCreatePending(flow) {
389 deselectFlow(flow, true);
390}
391
392function showFlowChooser() {
393 function rowEnter(d) {
394 var row = d3.select(this);
395
396 row.append('div')
397 .classed('black-eye', true).
398 on('click', function () {
399 selectFlow(d);
400 });
401
402 row.append('div')
403 .classed('flowId', true)
404 .text(function (d) {
405 return d.flowId;
406 });
407
408 row.append('div')
409 .classed('srcDPID', true)
410 .text(function (d) {
411 return d.srcDpid;
412 });
413
414
415 row.append('div')
416 .classed('dstDPID', true)
417 .text(function (d) {
418 return d.dstDpid;
419 });
420
421 }
422
Paul Greysonf3070172013-04-09 23:37:00 -0700423 var flowChooser = d3.select(document.getElementById('flowChooser'));
424 flowChooser.html('');
425 flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
426 .style('-webkit-transition');
427
428 var flows = flowChooser
Paul Greyson7a300822013-04-09 12:57:49 -0700429 .append('div')
430 .style('pointer-events', 'auto')
431 .selectAll('.selectedFlow')
432 .data(model.flows)
433 .enter()
434 .append('div')
435 .classed('selectedFlow', true)
436 .each(rowEnter);
437
Paul Greysonf3070172013-04-09 23:37:00 -0700438
Paul Greyson7a300822013-04-09 12:57:49 -0700439 setTimeout(function () {
Paul Greysonf3070172013-04-09 23:37:00 -0700440 flowChooser.style('-webkit-transition', '-webkit-transform .25s');
441 setTimeout(function () {
442 flowChooser.style('-webkit-transform', 'translate3d(0,0,0)');
443 }, 0);
444
445
Paul Greyson7a300822013-04-09 12:57:49 -0700446 d3.select(document.body).on('click', function () {
Paul Greysonf3070172013-04-09 23:37:00 -0700447 flowChooser.style('-webkit-transform', 'translate3d(-100%, 0, 0)')
Paul Greyson7a300822013-04-09 12:57:49 -0700448 d3.select(document.body).on('click', null);
449 });
450 }, 0);
451}