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:
Ross McFarland 2016-05-14 17:44:28 -07:00
parent 359541a67d
commit ba6d402e85
5 changed files with 144 additions and 6 deletions

View File

@ -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">

View File

@ -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);
});

View File

@ -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

View File

@ -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;
}