blob: 29fc4faf8a99d2432c4eedbb6fccc32946fa031a [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 = [];
29 if (!d.dataPath.flowEntries) {
30 // 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
46 } else {
47 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
189// TODO: cancel the interval when the flow is desel
190function startIPerfForFlow(flow) {
191 var duration = 10000; // seconds
192 var interval = 100; // ms. this is defined by the server
193 var updateRate = 2000; // ms
194 var pointsToDisplay = 1000;
195
196 function makePoints() {
197 var pts = [];
198 var i;
199 for (i=0; i < pointsToDisplay; ++i) {
200 var sample = flow.iperfData.samples[i];
201 var height = 30 * sample/1000000;
202 if (height > 30)
203 height = 30;
204 pts.push({
205 x: i * 1000/(pointsToDisplay-1),
206 y: 32 - height
207 })
208 }
209 return pts;
210 }
211
212 if (flow.flowId) {
213 console.log('starting iperf for: ' + flow.flowId);
214 startIPerf(flow, duration, updateRate/interval);
215 flow.iperfDisplayInterval = setInterval(function () {
216 if (flow.iperfData) {
217 while (flow.iperfData.samples.length < pointsToDisplay) {
218 flow.iperfData.samples.push(0);
219 }
220 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
221 iperfPath.attr('d', line(makePoints()));
222 flow.iperfData.samples.shift();
223 }
224
225
226 }, interval);
227 flow.iperfFetchInterval = setInterval(function () {
228 getIPerfData(flow, function (data) {
229 try {
230 if (!flow.iperfData) {
231 flow.iperfData = {
232 samples: []
233 };
234 var i;
235 for (i = 0; i < pointsToDisplay; ++i) {
236 flow.iperfData.samples.push(0);
237 }
238 }
239
240 var iperfData = JSON.parse(data);
241
242// console.log(iperfData.timestamp);
243
244 // if the data is fresh
245 if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
246
247 while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
248 flow.iperfData.samples.shift();
249 }
250
251 iperfData.samples.forEach(function (s) {
252 flow.iperfData.samples.push(s);
253 });
254 }
255 flow.iperfData.timestamp = iperfData.timestamp;
256 } catch (e) {
257 console.log('bad iperf data: ' + data);
258 }
259// console.log(data);
260 });
261 }, updateRate/2); // over sample to avoid gaps
262 }
263}
264
265function updateSelectedFlows() {
266 // make sure that all of the selected flows are either
267 // 1) valid (meaning they are in the latest list of flows)
268 // 2) pending
269 if (model) {
270 var flowMap = {};
271 model.flows.forEach(function (flow) {
272 flowMap[makeFlowKey(flow)] = flow;
273 });
274
275 var newSelectedFlows = [];
276 selectedFlows.forEach(function (flow) {
277 if (flow) {
278 var liveFlow = flowMap[makeFlowKey(flow)];
279 if (liveFlow) {
280 newSelectedFlows.push(liveFlow);
281 liveFlow.deletePending = flow.deletePending;
282 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
283 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
284 } else if (flow.createPending) {
285 newSelectedFlows.push(flow);
286 } else if (hasIPerf(flow)) {
287 clearIPerf(flow);
288 }
289 }
290 });
291 selectedFlows = newSelectedFlows;
292 }
293 selectedFlows.forEach(function (flow) {
294 if (!hasIPerf(flow)) {
295 startIPerfForFlow(flow);
296 }
297 });
298 while (selectedFlows.length < 3) {
299 selectedFlows.push(null);
300 }
301
302 updateSelectedFlowsTable();
303 updateSelectedFlowsTopology();
304}
305
306function selectFlow(flow) {
307 var flowKey = makeFlowKey(flow);
308 var alreadySelected = false;
309 selectedFlows.forEach(function (f) {
310 if (f && makeFlowKey(f) === flowKey) {
311 alreadySelected = true;
312 }
313 });
314
315 if (!alreadySelected) {
316 selectedFlows.unshift(flow);
317 selectedFlows = selectedFlows.slice(0, 3);
318 updateSelectedFlows();
319 }
320}
321
322function hasIPerf(flow) {
323 return flow && flow.iperfFetchInterval;
324}
325
326function clearIPerf(flow) {
327 console.log('clearing iperf interval for: ' + flow.flowId);
328 clearInterval(flow.iperfFetchInterval);
329 delete flow.iperfFetchInterval;
330 clearInterval(flow.iperfDisplayInterval);
331 delete flow.iperfDisplayInterval;
332 delete flow.iperfData;
333}
334
335function deselectFlow(flow, ifCreatePending) {
336 var flowKey = makeFlowKey(flow);
337 var newSelectedFlows = [];
338 selectedFlows.forEach(function (flow) {
339 if (!flow ||
340 flowKey !== makeFlowKey(flow) ||
341 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
342 newSelectedFlows.push(flow);
343 } else {
344 if (hasIPerf(flow)) {
345 clearIPerf(flow);
346 }
347 }
348 });
349 selectedFlows = newSelectedFlows;
350 while (selectedFlows.length < 3) {
351 selectedFlows.push(null);
352 }
353
354 updateSelectedFlows();
355}
356
357function deselectFlowIfCreatePending(flow) {
358 deselectFlow(flow, true);
359}
360
361function showFlowChooser() {
362 function rowEnter(d) {
363 var row = d3.select(this);
364
365 row.append('div')
366 .classed('black-eye', true).
367 on('click', function () {
368 selectFlow(d);
369 });
370
371 row.append('div')
372 .classed('flowId', true)
373 .text(function (d) {
374 return d.flowId;
375 });
376
377 row.append('div')
378 .classed('srcDPID', true)
379 .text(function (d) {
380 return d.srcDpid;
381 });
382
383
384 row.append('div')
385 .classed('dstDPID', true)
386 .text(function (d) {
387 return d.dstDpid;
388 });
389
390 }
391
392 var flows = d3.select('#flowChooser')
393 .append('div')
394 .style('pointer-events', 'auto')
395 .selectAll('.selectedFlow')
396 .data(model.flows)
397 .enter()
398 .append('div')
399 .classed('selectedFlow', true)
400 .each(rowEnter);
401
402 setTimeout(function () {
403 d3.select(document.body).on('click', function () {
404 d3.select('#flowChooser').html('');
405 d3.select(document.body).on('click', null);
406 });
407 }, 0);
408}