mirror of https://github.com/status-im/consul.git
Merge pull request #2046 from ross/ui-tomography
Implementation of a per-node tomography graph
This commit is contained in:
commit
fa26d5f64e
|
@ -594,6 +594,15 @@
|
|||
{{else}}
|
||||
<p class="light small">No sessions</p>
|
||||
{{/if}}
|
||||
|
||||
<h5>Network Tomography</h5>
|
||||
|
||||
{{ tomographyGraph tomography 336 }}
|
||||
|
||||
<p class="light small">Node: <span id="tomography-node-info"></span></p>
|
||||
<p class="light small">Minimum: {{ tomography.min }}ms</p>
|
||||
<p class="light small">Median: {{ tomography.median }}ms</p>
|
||||
<p class="light small">Maximum: {{ tomography.max }}ms</p>
|
||||
</script>
|
||||
|
||||
<script type="text/x-handlebars" id="acls">
|
||||
|
|
|
@ -91,3 +91,82 @@ function notify(message, ttl) {
|
|||
window.notifications = [];
|
||||
window.notifications.push(notification);
|
||||
}
|
||||
|
||||
// Tomography
|
||||
|
||||
// TODO: not sure how to how do to this more Ember.js-y
|
||||
function tomographyMouseOver(el) {
|
||||
var buf = el.getAttribute('data-node') + ' - ' + el.getAttribute('data-distance') + 'ms';
|
||||
document.getElementById('tomography-node-info').innerHTML = buf;
|
||||
}
|
||||
|
||||
Ember.Handlebars.helper('tomographyGraph', function(tomography, size) {
|
||||
|
||||
// This is ugly, but I'm working around bugs with Handlebars and templating
|
||||
// parts of svgs. Basically things render correctly the first time, but when
|
||||
// stuff is updated for subsequent go arounds the templated parts don't show.
|
||||
// It appears (based on google searches) that the replaced elements aren't
|
||||
// being interpreted as http://www.w3.org/2000/svg. Anyway, this works and
|
||||
// if/when Handlebars fixes the underlying issues all of this can be cleaned
|
||||
// up drastically.
|
||||
|
||||
var max = -999999999;
|
||||
tomography.distances.forEach(function (d, i) {
|
||||
if (d.distance > max) {
|
||||
max = d.distance;
|
||||
}
|
||||
});
|
||||
var insetSize = size / 2 - 8;
|
||||
var buf = '' +
|
||||
' <svg width="' + size + '" height="' + size + '">' +
|
||||
' <g class="tomography" transform="translate(' + (size / 2) + ', ' + (size / 2) + ')">' +
|
||||
' <g>' +
|
||||
' <circle class="background" r="' + insetSize + '"/>' +
|
||||
' <circle class="axis" r="' + (insetSize * 0.25) + '"/>' +
|
||||
' <circle class="axis" r="' + (insetSize * 0.5) + '"/>' +
|
||||
' <circle class="axis" r="' + (insetSize * 0.75) + '"/>' +
|
||||
' <circle class="border" r="' + insetSize + '"/>' +
|
||||
' </g>' +
|
||||
' <g class="lines">';
|
||||
var distances = tomography.distances;
|
||||
var n = distances.length;
|
||||
if (tomography.n > 360) {
|
||||
// We have more nodes than we want to show, take a random sampling to keep
|
||||
// the number around 360.
|
||||
var sampling = 360 / tomography.n;
|
||||
distances = distances.filter(function (_, i) {
|
||||
return i == 0 || i == n - 1 || Math.random() < sampling
|
||||
});
|
||||
// Re-set n to the filtered size
|
||||
n = distances.length;
|
||||
}
|
||||
distances.forEach(function (d, i) {
|
||||
buf += ' <line transform="rotate(' + (i * 360 / n) + ')" y2="' + (-insetSize * (d.distance / max)) + '" ' +
|
||||
'data-node="' + d.node + '" data-distance="' + d.distance + '" onmouseover="tomographyMouseOver(this);"/>';
|
||||
});
|
||||
buf += '' +
|
||||
' </g>' +
|
||||
' <g class="labels">' +
|
||||
' <circle class="point" r="5"/>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -0.25 ) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (max > 0 ? (parseInt(max * 25) / 100) : 0) + 'ms</text>' +
|
||||
' </g>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -0.5 ) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (max > 0 ? (parseInt(max * 50) / 100) : 0)+ 'ms</text>' +
|
||||
' </g>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -0.75 ) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (max > 0 ? (parseInt(max * 75) / 100) : 0) + 'ms</text>' +
|
||||
' </g>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -1) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (max > 0 ? (parseInt(max * 100) / 100) : 0) + 'ms</text>' +
|
||||
' </g>' +
|
||||
' </g>' +
|
||||
' </g>' +
|
||||
' </svg>';
|
||||
|
||||
return new Handlebars.SafeString(buf);
|
||||
});
|
||||
|
|
|
@ -104,6 +104,9 @@ App.DcRoute = App.BaseRoute.extend({
|
|||
});
|
||||
|
||||
return objs;
|
||||
}),
|
||||
coordinates: Ember.$.getJSON(formatUrl(consulHost + '/v1/coordinate/nodes', params.dc, token)).then(function(data) {
|
||||
return data;
|
||||
})
|
||||
});
|
||||
},
|
||||
|
@ -112,6 +115,7 @@ App.DcRoute = App.BaseRoute.extend({
|
|||
controller.set('content', models.dc);
|
||||
controller.set('nodes', models.nodes);
|
||||
controller.set('dcs', models.dcs);
|
||||
controller.set('coordinates', models.coordinates);
|
||||
controller.set('isDropdownVisible', false);
|
||||
},
|
||||
});
|
||||
|
@ -257,19 +261,78 @@ App.ServicesShowRoute = App.BaseRoute.extend({
|
|||
}
|
||||
});
|
||||
|
||||
function distance(a, b) {
|
||||
a = a.Coord;
|
||||
b = b.Coord;
|
||||
var sum = 0;
|
||||
for (var i = 0; i < a.Vec.length; i++) {
|
||||
var diff = a.Vec[i] - b.Vec[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
var rtt = Math.sqrt(sum) + a.Height + b.Height;
|
||||
|
||||
var adjusted = rtt + a.Adjustment + b.Adjustment;
|
||||
if (adjusted > 0.0) {
|
||||
rtt = adjusted;
|
||||
}
|
||||
|
||||
return Math.round(rtt * 100000.0) / 100.0;
|
||||
}
|
||||
|
||||
App.NodesShowRoute = App.BaseRoute.extend({
|
||||
model: function(params) {
|
||||
var dc = this.modelFor('dc').dc;
|
||||
var dc = this.modelFor('dc');
|
||||
var token = App.get('settings.token');
|
||||
|
||||
var min = 999999999;
|
||||
var max = -999999999;
|
||||
var sum = 0;
|
||||
var distances = [];
|
||||
dc.coordinates.forEach(function (node) {
|
||||
if (params.name == node.Node) {
|
||||
dc.coordinates.forEach(function (other) {
|
||||
if (node.Node != other.Node) {
|
||||
var dist = distance(node, other);
|
||||
distances.push({ node: other.Node, distance: dist });
|
||||
sum += dist;
|
||||
if (dist < min) {
|
||||
min = dist;
|
||||
}
|
||||
if (dist > max) {
|
||||
max = dist;
|
||||
}
|
||||
}
|
||||
});
|
||||
distances.sort(function (a, b) {
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
}
|
||||
});
|
||||
var n = distances.length;
|
||||
var halfN = Math.floor(n / 2);
|
||||
var median;
|
||||
if (n % 2) {
|
||||
// odd
|
||||
median = distances[halfN].distance;
|
||||
} else {
|
||||
median = (distances[halfN - 1].distance + distances[halfN].distance) / 2;
|
||||
}
|
||||
|
||||
// Return a promise hash of the node and nodes
|
||||
return Ember.RSVP.hash({
|
||||
dc: dc,
|
||||
dc: dc.dc,
|
||||
token: token,
|
||||
node: Ember.$.getJSON(formatUrl(consulHost + '/v1/internal/ui/node/' + params.name, dc, token)).then(function(data) {
|
||||
tomography: {
|
||||
distances: distances,
|
||||
n: distances.length,
|
||||
min: parseInt(min * 100) / 100,
|
||||
median: parseInt(median * 100) / 100,
|
||||
max: parseInt(max * 100) / 100
|
||||
},
|
||||
node: Ember.$.getJSON(formatUrl(consulHost + '/v1/internal/ui/node/' + params.name, dc.dc, token)).then(function(data) {
|
||||
return App.Node.create(data);
|
||||
}),
|
||||
nodes: Ember.$.getJSON(formatUrl(consulHost + '/v1/internal/ui/node/' + params.name, dc, token)).then(function(data) {
|
||||
nodes: Ember.$.getJSON(formatUrl(consulHost + '/v1/internal/ui/node/' + params.name, dc.dc, token)).then(function(data) {
|
||||
return App.Node.create(data);
|
||||
})
|
||||
});
|
||||
|
@ -286,6 +349,7 @@ App.NodesShowRoute = App.BaseRoute.extend({
|
|||
setupController: function(controller, models) {
|
||||
controller.set('content', models.node);
|
||||
controller.set('sessions', models.sessions);
|
||||
controller.set('tomography', models.tomography);
|
||||
//
|
||||
// Since we have 2 column layout, we need to also display the
|
||||
// list of nodes on the left. Hence setting the attribute
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -148,3 +148,34 @@ a {
|
|||
opacity: 0.6;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.tomography .background {
|
||||
fill: $gray-background;
|
||||
}
|
||||
.tomography .axis {
|
||||
fill: none;
|
||||
stroke: $gray-light;
|
||||
stroke-dasharray: 4 4;
|
||||
}
|
||||
.tomography .border {
|
||||
fill: none;
|
||||
stroke: $gray-darker;
|
||||
}
|
||||
.tomography .point {
|
||||
stroke: $gray-darker;
|
||||
fill: $green-faded;
|
||||
}
|
||||
.tomography .lines line {
|
||||
stroke: $red;
|
||||
}
|
||||
.tomography .lines line:hover {
|
||||
stroke: $gray-darker;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.tomography .tick line {
|
||||
stroke: $gray-light;
|
||||
}
|
||||
.tomography .tick text {
|
||||
font-size: 14px;
|
||||
text-anchor: start;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue