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