blob: 72bfd2fd509990ceeb2456ef49b70fcb4bc201ca [file] [log] [blame]
Paul Greyson7a300822013-04-09 12:57:49 -07001function updateSelectedFlowsTopology() {
2 // DRAW THE FLOWS
3 var topologyFlows = [];
4 selectedFlows.forEach(function (flow) {
5 if (flow) {
6 topologyFlows.push(flow);
7 }
8 });
9
Paul Greysonbbd708b2013-04-09 22:37:31 -070010 var flows = flowLayer.selectAll('.flow').data(topologyFlows);
Paul Greyson7a300822013-04-09 12:57:49 -070011
12 flows.enter().append("svg:path").attr('class', 'flow')
13 .attr('stroke-dasharray', '4, 10')
14 .append('svg:animate')
15 .attr('attributeName', 'stroke-dashoffset')
16 .attr('attributeType', 'xml')
17 .attr('from', '500')
18 .attr('to', '-500')
19 .attr('dur', '20s')
20 .attr('repeatCount', 'indefinite');
21
22 flows.exit().remove();
23
24 flows.attr('d', function (d) {
25 if (!d) {
26 return;
27 }
28 var pts = [];
Paul Greyson45aceb22013-04-09 22:17:03 -070029 if (d.createPending) {
Paul Greyson7a300822013-04-09 12:57:49 -070030 // create a temporary vector to indicate the pending flow
31 var s1 = d3.select(document.getElementById(d.srcDpid));
32 var s2 = d3.select(document.getElementById(d.dstDpid));
33
34 var pt1 = document.querySelector('svg').createSVGPoint();
35 pt1.x = s1.attr('x');
36 pt1.y = s1.attr('y');
Paul Greysonbbd708b2013-04-09 22:37:31 -070037 if (drawingRings) {
38 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
39 }
Paul Greyson7a300822013-04-09 12:57:49 -070040 pts.push(pt1);
41
42 var pt2 = document.querySelector('svg').createSVGPoint();
43 pt2.x = s2.attr('x');
44 pt2.y = s2.attr('y');
Paul Greysonbbd708b2013-04-09 22:37:31 -070045 if (drawingRings) {
46 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
47 }
Paul Greyson7a300822013-04-09 12:57:49 -070048 pts.push(pt2);
49
Paul Greyson45aceb22013-04-09 22:17:03 -070050 } else if (d.dataPath && d.dataPath.flowEntries) {
Paul Greyson7a300822013-04-09 12:57:49 -070051 d.dataPath.flowEntries.forEach(function (flowEntry) {
52 var s = d3.select(document.getElementById(flowEntry.dpid.value));
53 // s[0] is null if the flow entry refers to a non-existent switch
54 if (s[0][0]) {
55 var pt = document.querySelector('svg').createSVGPoint();
56 pt.x = s.attr('x');
57 pt.y = s.attr('y');
Paul Greysonbbd708b2013-04-09 22:37:31 -070058 if (drawingRings) {
59 pt = pt.matrixTransform(s[0][0].getCTM());
60 }
Paul Greyson7a300822013-04-09 12:57:49 -070061 pts.push(pt);
62 } else {
63 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
64 }
65 });
66 }
67 if (pts.length) {
68 return line(pts);
69 } else {
70 return "M0,0";
71 }
72 })
73 .attr('id', function (d) {
74 if (d) {
75 return makeFlowKey(d);
76 }
77 })
78 .classed('pending', function (d) {
79 return d && (d.createPending || d.deletePending);
80 });
81
82 // "marching ants"
83 flows.select('animate').attr('from', 500);
84
85}
86
87function updateSelectedFlowsTable() {
88 function rowEnter(d) {
89 var row = d3.select(this);
90 row.append('div').classed('deleteFlow', true);
91 row.append('div').classed('flowId', true);
92 row.append('div').classed('srcDPID', true);
93 row.append('div').classed('dstDPID', true);
94 row.append('div').classed('iperf', true);
95
96 row.select('.iperf')
97 .append('div')
98 .attr('class', 'iperf-container')
99 .append('svg:svg')
100 .attr('viewBox', '0 0 1000 32')
101 .attr('preserveAspectRatio', 'none')
102 .append('svg:g')
103 .append('svg:path')
104 .attr('class', 'iperfdata');
105
106 row.on('mouseover', function (d) {
107 if (d) {
108 var path = document.getElementById(makeFlowKey(d));
109 d3.select(path).classed('highlight', true);
110 }
111 });
112 row.on('mouseout', function (d) {
113 if (d) {
114 var path = document.getElementById(makeFlowKey(d));
115 d3.select(path).classed('highlight', false);
116 }
117 });
118 }
119
120 function rowUpdate(d) {
121 var row = d3.select(this);
122 row.attr('id', function (d) {
123 if (d) {
124 return makeSelectedFlowKey(d);
125 }
126 });
127
128 if (!d || !hasIPerf(d)) {
129 row.select('.iperfdata')
130 .attr('d', 'M0,0');
131 }
132
133 row.select('.deleteFlow').on('click', function () {
134 deselectFlow(d);
135 });
136 row.on('dblclick', function () {
137 if (d) {
138 var prompt = 'Delete flow ' + d.flowId + '?';
139 if (confirm(prompt)) {
140 deleteFlow(d);
141 d.deletePending = true;
142 updateSelectedFlows();
143
144 setTimeout(function () {
145 d.deletePending = false;
146 updateSelectedFlows();
147 }, pendingTimeout)
148 };
149 }
150 });
151
152 row.select('.flowId')
153 .text(function (d) {
154 if (d) {
155 if (d.flowId) {
156 return d.flowId;
157 } else {
158 return '0x--';
159 }
160 }
161 })
162 .classed('pending', function (d) {
163 return d && (d.createPending || d.deletePending);
164 });
165
166 row.select('.srcDPID')
167 .text(function (d) {
168 if (d) {
169 return d.srcDpid;
170 }
171 });
172
173 row.select('.dstDPID')
174 .text(function (d) {
175 if (d) {
176 return d.dstDpid;
177 }
178 });
179 }
180
181 var flows = d3.select('#selectedFlows')
182 .selectAll('.selectedFlow')
183 .data(selectedFlows);
184
185 flows.enter()
186 .append('div')
187 .classed('selectedFlow', true)
188 .each(rowEnter);
189
190 flows.each(rowUpdate);
191
192 flows.exit().remove();
193}
194
Paul Greyson7a300822013-04-09 12:57:49 -0700195function startIPerfForFlow(flow) {
196 var duration = 10000; // seconds
197 var interval = 100; // ms. this is defined by the server
198 var updateRate = 2000; // ms
199 var pointsToDisplay = 1000;
200
201 function makePoints() {
202 var pts = [];
203 var i;
204 for (i=0; i < pointsToDisplay; ++i) {
205 var sample = flow.iperfData.samples[i];
Paul Greysonbbd708b2013-04-09 22:37:31 -0700206 var height = 28 * sample/1000000;
207 if (height > 28)
208 height = 28;
Paul Greyson7a300822013-04-09 12:57:49 -0700209 pts.push({
210 x: i * 1000/(pointsToDisplay-1),
Paul Greysonbbd708b2013-04-09 22:37:31 -0700211 y: 30 - height
Paul Greyson7a300822013-04-09 12:57:49 -0700212 })
213 }
214 return pts;
215 }
216
217 if (flow.flowId) {
218 console.log('starting iperf for: ' + flow.flowId);
219 startIPerf(flow, duration, updateRate/interval);
220 flow.iperfDisplayInterval = setInterval(function () {
221 if (flow.iperfData) {
222 while (flow.iperfData.samples.length < pointsToDisplay) {
223 flow.iperfData.samples.push(0);
224 }
225 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
226 iperfPath.attr('d', line(makePoints()));
227 flow.iperfData.samples.shift();
228 }
229
230
231 }, interval);
232 flow.iperfFetchInterval = setInterval(function () {
233 getIPerfData(flow, function (data) {
234 try {
235 if (!flow.iperfData) {
236 flow.iperfData = {
237 samples: []
238 };
239 var i;
240 for (i = 0; i < pointsToDisplay; ++i) {
241 flow.iperfData.samples.push(0);
242 }
243 }
244
245 var iperfData = JSON.parse(data);
246
247// console.log(iperfData.timestamp);
248
249 // if the data is fresh
250 if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
251
252 while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
253 flow.iperfData.samples.shift();
254 }
255
256 iperfData.samples.forEach(function (s) {
257 flow.iperfData.samples.push(s);
258 });
259 }
260 flow.iperfData.timestamp = iperfData.timestamp;
261 } catch (e) {
262 console.log('bad iperf data: ' + data);
263 }
264// console.log(data);
265 });
266 }, updateRate/2); // over sample to avoid gaps
267 }
268}
269
270function updateSelectedFlows() {
271 // make sure that all of the selected flows are either
272 // 1) valid (meaning they are in the latest list of flows)
273 // 2) pending
274 if (model) {
275 var flowMap = {};
276 model.flows.forEach(function (flow) {
277 flowMap[makeFlowKey(flow)] = flow;
278 });
279
280 var newSelectedFlows = [];
281 selectedFlows.forEach(function (flow) {
282 if (flow) {
283 var liveFlow = flowMap[makeFlowKey(flow)];
284 if (liveFlow) {
285 newSelectedFlows.push(liveFlow);
286 liveFlow.deletePending = flow.deletePending;
287 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
288 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
289 } else if (flow.createPending) {
290 newSelectedFlows.push(flow);
291 } else if (hasIPerf(flow)) {
292 clearIPerf(flow);
293 }
294 }
295 });
296 selectedFlows = newSelectedFlows;
297 }
298 selectedFlows.forEach(function (flow) {
299 if (!hasIPerf(flow)) {
300 startIPerfForFlow(flow);
301 }
302 });
303 while (selectedFlows.length < 3) {
304 selectedFlows.push(null);
305 }
306
307 updateSelectedFlowsTable();
Paul Greysonbbd708b2013-04-09 22:37:31 -0700308 // on app init, the table is updated before the svg is constructed
309 if (flowLayer) {
310 updateSelectedFlowsTopology();
311 }
Paul Greyson7a300822013-04-09 12:57:49 -0700312}
313
314function selectFlow(flow) {
315 var flowKey = makeFlowKey(flow);
316 var alreadySelected = false;
317 selectedFlows.forEach(function (f) {
318 if (f && makeFlowKey(f) === flowKey) {
319 alreadySelected = true;
320 }
321 });
322
323 if (!alreadySelected) {
324 selectedFlows.unshift(flow);
325 selectedFlows = selectedFlows.slice(0, 3);
326 updateSelectedFlows();
327 }
328}
329
330function hasIPerf(flow) {
331 return flow && flow.iperfFetchInterval;
332}
333
334function clearIPerf(flow) {
335 console.log('clearing iperf interval for: ' + flow.flowId);
336 clearInterval(flow.iperfFetchInterval);
337 delete flow.iperfFetchInterval;
338 clearInterval(flow.iperfDisplayInterval);
339 delete flow.iperfDisplayInterval;
340 delete flow.iperfData;
341}
342
343function deselectFlow(flow, ifCreatePending) {
Paul Greysonaf750bf2013-04-09 23:26:37 -0700344 if (!flow) {
345 return;
346 }
347
Paul Greyson7a300822013-04-09 12:57:49 -0700348 var flowKey = makeFlowKey(flow);
349 var newSelectedFlows = [];
350 selectedFlows.forEach(function (flow) {
351 if (!flow ||
352 flowKey !== makeFlowKey(flow) ||
353 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
354 newSelectedFlows.push(flow);
355 } else {
356 if (hasIPerf(flow)) {
357 clearIPerf(flow);
358 }
359 }
360 });
361 selectedFlows = newSelectedFlows;
362 while (selectedFlows.length < 3) {
363 selectedFlows.push(null);
364 }
365
366 updateSelectedFlows();
367}
368
369function deselectFlowIfCreatePending(flow) {
370 deselectFlow(flow, true);
371}
372
373function showFlowChooser() {
374 function rowEnter(d) {
375 var row = d3.select(this);
376
377 row.append('div')
378 .classed('black-eye', true).
379 on('click', function () {
380 selectFlow(d);
381 });
382
383 row.append('div')
384 .classed('flowId', true)
385 .text(function (d) {
386 return d.flowId;
387 });
388
389 row.append('div')
390 .classed('srcDPID', true)
391 .text(function (d) {
392 return d.srcDpid;
393 });
394
395
396 row.append('div')
397 .classed('dstDPID', true)
398 .text(function (d) {
399 return d.dstDpid;
400 });
401
402 }
403
404 var flows = d3.select('#flowChooser')
405 .append('div')
406 .style('pointer-events', 'auto')
407 .selectAll('.selectedFlow')
408 .data(model.flows)
409 .enter()
410 .append('div')
411 .classed('selectedFlow', true)
412 .each(rowEnter);
413
414 setTimeout(function () {
415 d3.select(document.body).on('click', function () {
416 d3.select('#flowChooser').html('');
417 d3.select(document.body).on('click', null);
418 });
419 }, 0);
420}