Repository: qpid-dispatch
Updated Branches:
  refs/heads/master e9f8a852a -> 61df4890b


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/61df4890/console/stand-alone/plugin/js/topology/traffic.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/traffic.js 
b/console/stand-alone/plugin/js/topology/traffic.js
new file mode 100644
index 0000000..883cb2f
--- /dev/null
+++ b/console/stand-alone/plugin/js/topology/traffic.js
@@ -0,0 +1,278 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+'use strict';
+
+/* global d3 ChordData MIN_CHORD_THRESHOLD */
+
+/* Create animated dots moving along the links between routers
+   to show that there is message flow between routers.
+   */
+const transitionDuration = 1000;
+const CHORDFILTERKEY =  'chordFilter';
+
+function Traffic ($scope, $timeout, QDRService, converter, radius, topology, 
nextHop, excluded) {
+  this.QDRService = QDRService;
+  this.radius = radius;       // the radius of a router circle
+  this.topology = topology;   // contains the list of router nodes
+  this.nextHop = nextHop;     // fn that returns the route through the network 
between two routers
+  this.$scope = $scope;
+  this.$timeout = $timeout;
+  $scope.addressColors = {};
+  this.excludedAddresses = JSON.parse(localStorage[CHORDFILTERKEY]) || [];
+
+  // internal variables
+  this.interval = null;       // setInterval handle
+  this.lastFlows = {};        // the number of dots animated between routers
+  this.chordData = new ChordData(this.QDRService, true, converter); // gets 
ingressHistogram data
+  this.chordData.setFilter(this.excludedAddresses);
+  this.$scope.addresses = {};
+  this.chordData.getMatrix().then( function () {
+    $timeout( function () {
+      this.$scope.addresses = this.chordData.getAddresses();
+      for (let address in this.$scope.addresses) {
+        this.fillColor(address);
+      }
+    }.bind(this));
+  }.bind(this));
+}
+
+/* Public methods on Traffic object */
+
+// stop updating the traffic data
+Traffic.prototype.stop = function () {
+  if (this.interval) {
+    clearInterval(this.interval);
+    this.interval = null;
+  }
+};
+// start updating the traffic data
+Traffic.prototype.start = function () {
+  this.doUpdate(this);
+  this.interval = setInterval(this.doUpdate.bind(this), transitionDuration);
+};
+// remove any animationions that are in progress
+Traffic.prototype.remove = function () {
+  for (let id in this.lastFlows) {
+    d3.select('#SVG_ID').selectAll('circle.flow' + id).remove();
+  }
+  this.lastFlows = {};
+};
+// called when one of the address checkboxes is toggled
+Traffic.prototype.updateAddresses = function () {
+  this.excludedAddresses = [];
+  for (let address in this.$scope.addresses) {
+    if (!this.$scope.addresses[address])
+      this.excludedAddresses.push(address);
+  }
+  localStorage[CHORDFILTERKEY] = JSON.stringify(this.excludedAddresses);
+  if (this.chordData) 
+    this.chordData.setFilter(this.excludedAddresses);
+  // don't wait for the next polling cycle. update now
+  this.stop();
+  this.start();
+};
+Traffic.prototype.toggleAddress = function (address) {
+  this.$scope.addresses[address] = !this.$scope.addresses[address];
+  this.updateAddresses();
+};
+Traffic.prototype.fadeOtherAddresses = function (address) {
+  d3.selectAll('circle.flow').classed('fade', function(d) {
+    return d.address !== address;
+  });
+};
+Traffic.prototype.unFadeAll = function () {
+  d3.selectAll('circle.flow').classed('fade', false);
+};
+
+/* The following don't need to be public, but they are for simplicity sake */
+
+// called periodically to refresh the traffic flow
+Traffic.prototype.doUpdate = function () {
+  let self = this;
+  // we need the nextHop data to show traffic between routers that are 
connected by intermediaries
+  this.QDRService.management.topology.ensureAllEntities([{entity: 
'router.node', attrs: ['id','nextHop']}], 
+    function () {
+      // get the ingressHistogram data for all routers
+      self.chordData.getMatrix().then(self.render.bind(self), function (e) {
+        console.log('Could not get message histogram' + e);
+      });
+    });
+};
+
+// calculate the translation for each dot along the path
+let translateDots = function (radius, path, count, back) {
+  let pnode = path.node();
+  // will be called for each element in the flow selection (for each dot)
+  return function(d) {
+    // will be called with t going from 0 to 1 for each dot
+    return function(t) {
+      // start the points at different positions depending on their value (d)
+      let tt = t * 1000;
+      let f = ((tt + (d.i*1000/count)) % 1000)/1000;
+      if (back)
+        f = 1 - f;
+      // l needs to be calculated each tick because the path's length might be 
changed during the animation
+      let l = pnode.getTotalLength();
+      let p = pnode.getPointAtLength(f * l);
+      return 'translate(' + p.x + ',' + p.y + ')';
+    };
+  };
+};
+// animate the d3 selection (flow) along the given path
+Traffic.prototype.animateFlow = function (flow, path, count, back, rate) {
+  let self = this;
+  let l = path.node().getTotalLength();
+  flow.transition()
+    .ease('easeLinear')
+    .duration(l*10/rate)
+    .attrTween('transform', translateDots(self.radius, path, count, back))
+    .each('end', function () {self.animateFlow(flow, path, count, back, 
rate);});
+};
+
+Traffic.prototype.render = function (matrix) {
+  this.$timeout(
+    function () {
+      this.$scope.addresses = this.chordData.getAddresses();
+    }.bind(this)
+  );
+  // get the rate of message flow between routers
+  let hops = {};  // every hop between routers that is involved in message flow
+  let matrixMessages = matrix.matrixMessages();
+  // the fastest traffic rate gets 3 times as many dots as the slowest
+  let minmax = matrix.getMinMax();
+  let flowScale = d3.scale.linear().domain(minmax).range([1,1.75]);
+
+  // row is ingress router, col is egress router. Value at [row][col] is the 
rate
+  matrixMessages.forEach( function (row, r) {
+    row.forEach(function (val, c) {
+      if (val > MIN_CHORD_THRESHOLD) {
+        // translate between matrix row/col and node index
+        let f = nodeIndexFor(this.topology.nodes, matrix.rows[r].egress);
+        let t = nodeIndexFor(this.topology.nodes, matrix.rows[r].ingress);
+        let address = matrix.getAddress(r, c);
+
+        if (r !== c) {
+          // move the dots along the links between the routers
+          this.nextHop(this.topology.nodes[f], this.topology.nodes[t], 
function (link, fnode, tnode) {
+            let key = '-' + link.uid;
+            let back = fnode.index < tnode.index;
+            if (!hops[key])
+              hops[key] = [];
+            hops[key].push({val: val, back: back, address: address});
+          });
+        }
+        // Find the senders connected to nodes[f] and the receivers connected 
to nodes[t]
+        // and add their links to the animation
+        addClients(hops, this.topology.nodes, f, val, true, address);
+        addClients(hops, this.topology.nodes, t, val, false, address);
+      }
+    }.bind(this));
+  }.bind(this));
+  // for each link between routers that has traffic, start an animation
+  let keep = {};
+  for (let id in hops) {
+    let hop = hops[id];
+    for (let h=0; h<hop.length; h++) {
+      let ahop = hop[h];
+      let flowId = id + '-' + addressIndex(this, ahop.address) + (ahop.back ? 
'b' : '');
+      let path = d3.select('#path' + id);
+      // start the animation. If the animation is already running, this will 
have no effect
+      this.startDots(path, flowId, ahop, flowScale(ahop.val));
+      keep[flowId] = true;
+    }
+  }
+  // remove any existing animations that we don't have data for anymore
+  for (let id in this.lastFlows) {
+    if (this.lastFlows[id] && !keep[id]) {
+      this.lastFlows[id] = 0;
+      d3.select('#SVG_ID').selectAll('circle.flow' + id).remove();
+    }
+  }
+};
+
+// create dots along the path between routers
+Traffic.prototype.startDots = function (path, id, hop, rate) {
+  let back = hop.back, address = hop.address;
+  if (!path.node())
+    return;
+  // the density of dots is determined by the rate of this traffic relative to 
the other traffic
+  let len = Math.max(Math.floor(path.node().getTotalLength() / 50), 1);
+  let dots = [];
+  for (let i=0, offset=addressIndex(this, address); i<len; ++i) {
+    dots[i] = {i: i + 10 * offset, address: address};
+  }
+  // keep track of the number of dots for each link. If the length of the link 
is changed,
+  // re-create the animation
+  if (!this.lastFlows[id])
+    this.lastFlows[id] = len;
+  else {
+    if (this.lastFlows[id] !== len) {
+      this.lastFlows[id] = len;
+      d3.select('#SVG_ID').selectAll('circle.flow' + id).remove();
+    }
+  }
+  let flow = d3.select('#SVG_ID').selectAll('circle.flow' + id)
+    .data(dots, function (d) { return d.i + d.address; });
+
+  let circles = flow
+    .enter().append('circle')
+    .attr('class', 'flow flow' + id)
+    .attr('fill', this.fillColor(address))
+    .attr('r', 5);
+
+  this.animateFlow(circles, path, dots.length, back, rate);
+
+  flow.exit()
+    .remove();
+};
+
+// colors
+let colorGen = d3.scale.category10();
+Traffic.prototype.fillColor = function (n) {
+  if (!(n in this.$scope.addressColors)) {
+    let ci = Object.keys(this.$scope.addressColors).length;
+    this.$scope.addressColors[n] = colorGen(ci);
+  }
+  return this.$scope.addressColors[n];
+};
+// return the node index for a router name
+let nodeIndexFor = function (nodes, name) {
+  for (let i=0; i<nodes.length; i++) {
+    let node = nodes[i];
+    if (node.routerId === name)
+      return i;
+  }
+  return -1;
+};
+let addClients = function (hops, nodes, f, val, sender, address) {
+  let cdir = sender ? 'out' : 'in';
+  for (let n=0; n<nodes.length; n++) {
+    let node = nodes[n];
+    if (node.normals && node.key === nodes[f].key && node.cdir === cdir) {
+      let key = ['',f,n].join('-');
+      if (!hops[key])
+        hops[key] = [];
+      hops[key].push({val: val, back: node.cdir === 'in', address: address});
+      return;
+    }
+  }
+};
+let addressIndex = function (traffic, address) {
+  return Object.keys(traffic.$scope.addresses).indexOf(address);
+};
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org
For additional commands, e-mail: commits-h...@qpid.apache.org

Reply via email to