mirror of https://github.com/status-im/consul.git
Implementation of a per-node tomography graph
Adds a new section to the node information, Network Tomography. There's a radar plot of the distances (in ms) between the current node and its peers as well as min, avg, and max.
This commit is contained in:
parent
359541a67d
commit
ba6d402e85
|
@ -594,6 +594,14 @@
|
|||
{{else}}
|
||||
<p class="light small">No sessions</p>
|
||||
{{/if}}
|
||||
|
||||
<h5>Network Tomography</h5>
|
||||
|
||||
{{ tomographyGraph tomography 336 }}
|
||||
|
||||
<p class="light small">Minimum: {{ tomography.min }}ms</p>
|
||||
<p class="light small">Average: {{ tomography.avg }}ms</p>
|
||||
<p class="light small">Maximum: {{ tomography.max }}ms</p>
|
||||
</script>
|
||||
|
||||
<script type="text/x-handlebars" id="acls">
|
||||
|
|
|
@ -91,3 +91,59 @@ function notify(message, ttl) {
|
|||
window.notifications = [];
|
||||
window.notifications.push(notification);
|
||||
}
|
||||
|
||||
// Tomography
|
||||
|
||||
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 n = tomography.n;
|
||||
var max = Math.max.apply(null, tomography.distances);
|
||||
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">';
|
||||
tomography.distances.forEach(function (distance, i) {
|
||||
buf += ' <line transform="rotate(' + (i * 360 / n) + ')" y2="' + (-insetSize * (distance / max)) + '"></line>';
|
||||
});
|
||||
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">' + (parseInt(max * 25) / 100) + 'ms</text>' +
|
||||
' </g>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -0.5 ) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (parseInt(max * 50) / 100) + 'ms</text>' +
|
||||
' </g>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -0.75 ) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (parseInt(max * 75) / 100) + 'ms</text>' +
|
||||
' </g>' +
|
||||
' <g class="tick" transform="translate(0, ' + (insetSize * -1) + ')">' +
|
||||
' <line x2="70"/>' +
|
||||
' <text x="75" y="0" dy=".32em">' + (parseInt(max * 100) / 100) + '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,61 @@ 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 sum = 0;
|
||||
var distances = [];
|
||||
dc.coordinates.forEach(function (node) {
|
||||
if (params.name == node.Node) {
|
||||
dc.coordinates.forEach(function (other) {
|
||||
// TODO: ignore self
|
||||
var dist = distance(node, other);
|
||||
distances.push(dist);
|
||||
sum += dist;
|
||||
});
|
||||
distances.sort();
|
||||
}
|
||||
});
|
||||
var min = Math.min.apply(null, distances);
|
||||
var avg = sum / distances.length;
|
||||
var max = Math.max.apply(null, distances);
|
||||
|
||||
// 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,
|
||||
avg: parseInt(avg * 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 +332,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,30 @@ 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 .tick line {
|
||||
stroke: $gray-light;
|
||||
}
|
||||
.tomography .tick text {
|
||||
font-size: 14px;
|
||||
text-anchor: start;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue