529 lines
14 KiB
JavaScript
529 lines
14 KiB
JavaScript
/* -*- js-indent-level: 8 -*- */
|
|
/*
|
|
Socket to be intialized on opening the analytics page in Admin console
|
|
containing various graphs to show to the user on specified interval
|
|
*/
|
|
|
|
/* global _ d3 Util AdminSocketBase $ Admin */
|
|
var AdminSocketAnalytics = AdminSocketBase.extend({
|
|
constructor: function(host) {
|
|
this.base(host);
|
|
},
|
|
|
|
_memStatsData: [],
|
|
_cpuStatsData: [],
|
|
_sentStatsData: [],
|
|
_recvStatsData: [],
|
|
|
|
_memStatsSize: 0,
|
|
_memStatsInterval: 0,
|
|
|
|
_cpuStatsSize: 0,
|
|
_cpuStatsInterval: 0,
|
|
|
|
_netStatsSize: 0,
|
|
_netStatsInterval: 0,
|
|
|
|
_initStatsData: function(option, size, interval, reset) {
|
|
var actualData;
|
|
|
|
if (reset) {
|
|
actualData = [];
|
|
}
|
|
|
|
var offset = actualData.length * interval;
|
|
for (var i = 0; i < size; i++) {
|
|
actualData.unshift({time: -(offset), value: 0});
|
|
offset += interval;
|
|
}
|
|
|
|
if (option === 'mem')
|
|
this._memStatsData = actualData;
|
|
else if (option === 'cpu')
|
|
this._cpuStatsData = actualData;
|
|
else if (option === 'sent')
|
|
this._sentStatsData = actualData;
|
|
else if (option === 'recv')
|
|
this._recvStatsData = actualData;
|
|
},
|
|
|
|
onSocketOpen: function() {
|
|
// Base class' onSocketOpen handles authentication
|
|
this.base.call(this);
|
|
|
|
this.socket.send('subscribe mem_stats cpu_stats sent_activity recv_activity settings');
|
|
this.socket.send('settings');
|
|
this.socket.send('sent_activity');
|
|
this.socket.send('recv_activity');
|
|
this.socket.send('mem_stats');
|
|
this.socket.send('cpu_stats');
|
|
},
|
|
|
|
_d3MemXAxis: null,
|
|
_d3MemYAxis: null,
|
|
_d3MemLine: null,
|
|
_xMemScale: null,
|
|
_yMemScale: null,
|
|
|
|
_d3CpuXAxis: null,
|
|
_d3CpuYAxis: null,
|
|
_d3CpuLine: null,
|
|
_xCpuScale: null,
|
|
_yCpuScale: null,
|
|
|
|
_d3NetXAxis: null,
|
|
_d3NetYAxis: null,
|
|
_d3NetSentLine: null,
|
|
_d3NetRecvLine: null,
|
|
_xNetScale: null,
|
|
_yNetScale: null,
|
|
|
|
_graphWidth: 1000,
|
|
_graphHeight: 500,
|
|
_graphMargins: {
|
|
top: 20,
|
|
right: 20,
|
|
bottom: 20,
|
|
left: 100
|
|
},
|
|
|
|
_setUpAxis: function(option) {
|
|
var data, xScale, yScale, d3XAxis, d3Line;
|
|
|
|
if (option === 'mem')
|
|
data = this._memStatsData;
|
|
else if (option === 'cpu')
|
|
data = this._cpuStatsData;
|
|
else if (option === 'net')
|
|
data = this._sentStatsData.concat(this._recvStatsData);
|
|
|
|
xScale = d3.scaleLinear().range([this._graphMargins.left, this._graphWidth - this._graphMargins.right]).domain([d3.min(data, function(d) {
|
|
return d.time;
|
|
}), d3.max(data, function(d) {
|
|
return d.time;
|
|
})]);
|
|
|
|
|
|
yScale = d3.scaleLinear().range([this._graphHeight - this._graphMargins.bottom, this._graphMargins.top]).domain([d3.min(data, function(d) {
|
|
return d.value;
|
|
}), d3.max(data, function(d) {
|
|
return d.value;
|
|
})]);
|
|
|
|
d3XAxis = d3.axisBottom(xScale)
|
|
.tickFormat(function(d) {
|
|
d = Math.abs(d / 1000);
|
|
var sUnit = 0;
|
|
var i = 0;
|
|
var units = ['s', 'min', 'hr'];
|
|
for (i = 0; i < units.length && Math.abs(d) >= 60; i++) {
|
|
sUnit = parseInt(d % 60);
|
|
d = parseInt(d / 60);
|
|
}
|
|
if (i !== 0 && sUnit !== 0) {
|
|
return d + units[i][0] + ' ' + sUnit + units[i-1][0];
|
|
}
|
|
else
|
|
return d + units[i];
|
|
});
|
|
|
|
d3Line = d3.line()
|
|
.x(function(d) {
|
|
return xScale(d.time);
|
|
})
|
|
.y(function(d) {
|
|
return yScale(d.value);
|
|
})
|
|
.curve(d3.curveMonotoneX);
|
|
|
|
if (option === 'mem') {
|
|
this._xMemScale = xScale;
|
|
this._yMemScale = yScale;
|
|
this._d3MemXAxis = d3XAxis;
|
|
this._d3MemYAxis = d3.axisLeft(this._yMemScale)
|
|
.tickFormat(function (d) {
|
|
return Util.humanizeMem(d);
|
|
});
|
|
this._d3MemLine = d3Line;
|
|
}
|
|
else if (option === 'cpu') {
|
|
this._xCpuScale = xScale;
|
|
this._yCpuScale = yScale;
|
|
this._d3CpuXAxis = d3XAxis;
|
|
this._d3CpuYAxis = d3.axisLeft(this._yCpuScale)
|
|
.tickFormat(function (d) {
|
|
return d + '%';
|
|
});
|
|
this._d3CpuLine = d3Line;
|
|
}
|
|
else if (option === 'net') {
|
|
this._xNetScale = xScale;
|
|
this._yNetScale = yScale;
|
|
this._d3NetXAxis = d3XAxis;
|
|
this._d3NetYAxis = d3.axisLeft(this._yNetScale)
|
|
.tickFormat(function (d) {
|
|
return Util.humanizeMem(d/1000) + '/sec';
|
|
});
|
|
this._d3NetSentLine = d3Line;
|
|
this._d3NetRecvLine = d3Line;
|
|
|
|
}
|
|
},
|
|
|
|
_createGraph: function(option) {
|
|
var vis, xAxis, yAxis, line, data;
|
|
|
|
if (option === 'mem') {
|
|
vis = d3.select('#MemVisualisation');
|
|
this._setUpAxis('mem');
|
|
xAxis = this._d3MemXAxis;
|
|
yAxis = this._d3MemYAxis;
|
|
line = this._d3MemLine;
|
|
data = this._memStatsData;
|
|
}
|
|
else if (option === 'cpu') {
|
|
vis = d3.select('#CpuVisualisation');
|
|
this._setUpAxis('cpu');
|
|
xAxis = this._d3CpuXAxis;
|
|
yAxis = this._d3CpuYAxis;
|
|
line = this._d3CpuLine;
|
|
data = this._cpuStatsData;
|
|
}
|
|
else if (option === 'net') {
|
|
vis = d3.select('#NetVisualisation');
|
|
this._setUpAxis('net');
|
|
xAxis = this._d3NetXAxis;
|
|
yAxis = this._d3NetYAxis;
|
|
|
|
var legend = vis.append('g')
|
|
.attr('x', this._graphWidth - 70)
|
|
.attr('y', 50)
|
|
.style('font-size', '17px');
|
|
|
|
var legendData = [
|
|
{
|
|
text: _('Received'),
|
|
color: 'green'
|
|
},
|
|
{
|
|
text: _('Sent'),
|
|
color: 'red'
|
|
}
|
|
];
|
|
var legendSpacing = 20;
|
|
|
|
for (var i = legendData.length - 1; i >= 0; i--) {
|
|
|
|
legend.append('text')
|
|
.attr('x', this._graphWidth - 70)
|
|
.attr('y', 80 + i * legendSpacing)
|
|
.text(legendData[i].text);
|
|
legend.append('rect')
|
|
.attr('x', this._graphWidth - 90)
|
|
.attr('y', 67 + i * legendSpacing)
|
|
.attr('width', 15)
|
|
.attr('height', 15)
|
|
.style('fill', legendData[i].color)
|
|
.style('stroke', 'black');
|
|
}
|
|
}
|
|
|
|
vis.append('svg:g')
|
|
.attr('class', 'x-axis axis')
|
|
.attr('transform', 'translate(0,' + (this._graphHeight - this._graphMargins.bottom) + ')')
|
|
.call(xAxis);
|
|
|
|
vis.append('svg:g')
|
|
.attr('class', 'y-axis axis')
|
|
.attr('transform', 'translate(' + this._graphMargins.left + ',0)')
|
|
.call(yAxis);
|
|
|
|
if (option === 'cpu' || option === 'mem') {
|
|
|
|
vis.append('svg:path')
|
|
.attr('d', line(data))
|
|
.attr('class', 'line')
|
|
.attr('stroke', 'blue')
|
|
.attr('stroke-width', 1)
|
|
.attr('fill', 'none');
|
|
}
|
|
else if (option === 'net') {
|
|
|
|
vis.append('svg:path')
|
|
.attr('d', this._d3NetSentLine(this._sentStatsData))
|
|
.attr('class', 'lineSent')
|
|
.attr('stroke', 'red')
|
|
.attr('stroke-width', 1)
|
|
.attr('fill', 'none');
|
|
|
|
vis.append('svg:path')
|
|
.attr('d', this._d3NetRecvLine(this._recvStatsData))
|
|
.attr('class', 'lineRecv')
|
|
.attr('stroke', 'green')
|
|
.attr('stroke-width', 1)
|
|
.attr('fill', 'none');
|
|
}
|
|
|
|
},
|
|
|
|
_addNewData: function(oldData, newData, option) {
|
|
var size, graphName, line, elemSize;
|
|
elemSize = this._graphWidth - this._graphMargins['left'] - this._graphMargins['right'];
|
|
|
|
if (option === 'mem') {
|
|
size = this._memStatsSize;
|
|
graphName = '#MemVisualisation';
|
|
line = 'line';
|
|
}
|
|
else if (option === 'cpu') {
|
|
size = this._cpuStatsSize;
|
|
graphName = '#CpuVisualisation';
|
|
line = 'line';
|
|
}
|
|
else if (option === 'sent' || option === 'recv')
|
|
size = this._netStatsSize;
|
|
|
|
if (graphName === '#MemVisualisation' || graphName === '#CpuVisualisation' ||
|
|
graphName === '#NetVisualisation') {
|
|
d3.select(graphName)
|
|
.select('.' + line)
|
|
.attr('transform', 'translate(' + elemSize/size + ')')
|
|
.transition()
|
|
.attr('transform', 'translate(' + 0 + ')');
|
|
}
|
|
|
|
// make a space for new data
|
|
for (var i = oldData.length - 1; i > 0; i--) {
|
|
oldData[i].time = oldData[i - 1].time;
|
|
}
|
|
|
|
// push new data at time '0'
|
|
oldData.push({time: 0, value: parseInt(newData)});
|
|
|
|
// remove extra items
|
|
if (oldData.length > size) {
|
|
oldData.shift();
|
|
}
|
|
},
|
|
|
|
_updateMemGraph: function() {
|
|
var svg = d3.select('#MemVisualisation');
|
|
|
|
this._setUpAxis('mem');
|
|
|
|
svg.select('.line')
|
|
.attr('d', this._d3MemLine(this._memStatsData));
|
|
|
|
svg.select('.x-axis')
|
|
.call(this._d3MemXAxis);
|
|
|
|
svg.transition()
|
|
.duration(500)
|
|
.select('.y-axis')
|
|
.call(this._d3MemYAxis);
|
|
},
|
|
|
|
_updateCpuGraph: function() {
|
|
var svg = d3.select('#CpuVisualisation');
|
|
|
|
this._setUpAxis('cpu');
|
|
|
|
svg.select('.line')
|
|
.attr('d', this._d3CpuLine(this._cpuStatsData));
|
|
|
|
svg.select('.x-axis')
|
|
.call(this._d3CpuXAxis);
|
|
|
|
svg.transition()
|
|
.select('.y-axis')
|
|
.duration(500)
|
|
.call(this._d3CpuYAxis);
|
|
},
|
|
|
|
_updateNetGraph: function() {
|
|
var svg = d3.select('#NetVisualisation');
|
|
|
|
this._setUpAxis('net');
|
|
|
|
svg.select('.lineSent')
|
|
.attr('d', this._d3NetSentLine(this._sentStatsData));
|
|
svg.select('.lineRecv')
|
|
.attr('d', this._d3NetRecvLine(this._recvStatsData));
|
|
|
|
svg.select('.x-axis')
|
|
.call(this._d3NetXAxis);
|
|
|
|
svg.transition()
|
|
.select('.y-axis')
|
|
.duration(500)
|
|
.call(this._d3NetYAxis);
|
|
},
|
|
|
|
onSocketMessage: function(e) {
|
|
var textMsg;
|
|
if (typeof e.data === 'string') {
|
|
textMsg = e.data;
|
|
}
|
|
else {
|
|
textMsg = '';
|
|
}
|
|
|
|
if (textMsg.startsWith('settings')) {
|
|
textMsg = textMsg.substring('settings '.length);
|
|
textMsg = textMsg.split(' ');
|
|
|
|
var memStatsSize, memStatsInterval, cpuStatsSize, cpuStatsInterval;
|
|
var i, j, data;
|
|
memStatsSize = this._memStatsSize;
|
|
memStatsInterval = this._memStatsInterval;
|
|
cpuStatsSize = this._cpuStatsSize;
|
|
cpuStatsInterval = this._cpuStatsInterval;
|
|
for (i = 0; i < textMsg.length; i++) {
|
|
var setting = textMsg[i].split('=');
|
|
if (setting[0] === 'mem_stats_size') {
|
|
memStatsSize = parseInt(setting[1]);
|
|
}
|
|
else if (setting[0] === 'mem_stats_interval') {
|
|
memStatsInterval = parseInt(setting[1]);
|
|
}
|
|
else if (setting[0] === 'cpu_stats_size') {
|
|
cpuStatsSize = parseInt(setting[1]);
|
|
}
|
|
else if (setting[0] === 'cpu_stats_interval') {
|
|
cpuStatsInterval = parseInt(setting[1]);
|
|
}
|
|
else if (setting[0] === 'net_stats_size') {
|
|
this._netStatsSize = parseInt(setting[1]);
|
|
}
|
|
else if (setting[0] === 'net_stats_interval') {
|
|
this._netStatsInterval = parseInt(setting[1]);
|
|
}
|
|
}
|
|
|
|
// Fix the axes according to changed data
|
|
if (memStatsInterval !== this._memStatsInterval) {
|
|
// We can possibly reuse the data with a bit of work
|
|
this._initStatsData('mem', memStatsSize, memStatsInterval, true);
|
|
}
|
|
else if (memStatsSize > this._memStatsSize) {
|
|
this._initStatsData('mem', memStatsSize - this._memStatsSize, memStatsInterval, false);
|
|
}
|
|
else {
|
|
// just strip the extra items
|
|
for (i = 0; i < this._memStatsSize - memStatsSize; i++) {
|
|
this._memStatsData.shift();
|
|
}
|
|
}
|
|
|
|
this._memStatsSize = memStatsSize;
|
|
this._memStatsInterval = memStatsInterval;
|
|
|
|
// Similar Logic as above for CPU stats
|
|
if (cpuStatsInterval !== this._cpuStatsInterval) {
|
|
this._initStatsData('cpu', cpuStatsSize, cpuStatsInterval, true);
|
|
}
|
|
else if (cpuStatsSize > this._cpuStatsSize) {
|
|
this._initStatsData('cpu', cpuStatsSize - this._cpuStatsSize, cpuStatsInterval, false);
|
|
}
|
|
else {
|
|
for (i = 0; i < this._cpuStatsSize - cpuStatsSize; i++) {
|
|
this._cpuStatsData.shift();
|
|
}
|
|
}
|
|
|
|
this._cpuStatsSize = cpuStatsSize;
|
|
this._cpuStatsInterval = cpuStatsInterval;
|
|
|
|
this._initStatsData('sent', this._netStatsSize, this._netStatsInterval, true);
|
|
this._initStatsData('recv', this._netStatsSize, this._netStatsInterval, true);
|
|
|
|
}
|
|
else if (textMsg.startsWith('mem_stats')) {
|
|
textMsg = textMsg.split(' ')[1];
|
|
if (textMsg.endsWith(',')) {
|
|
// This is the result of query, not notification
|
|
data = textMsg.substring(0, textMsg.length - 1).split(',');
|
|
for (i = this._memStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
|
|
this._memStatsData[i].value = parseInt(data[j]);
|
|
}
|
|
|
|
this._createGraph('mem');
|
|
}
|
|
else {
|
|
// this is a notification data; append to _memStatsData
|
|
data = textMsg.trim();
|
|
this._addNewData(this._memStatsData, data, 'mem');
|
|
this._updateMemGraph();
|
|
}
|
|
}
|
|
else if (textMsg.startsWith('cpu_stats')) {
|
|
textMsg = textMsg.split(' ')[1];
|
|
if (textMsg.endsWith(',')) {
|
|
// This is the result of query, not notification
|
|
data = textMsg.substring(0, textMsg.length - 1).split(',');
|
|
|
|
for (i = this._cpuStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
|
|
this._cpuStatsData[i].value = parseInt(data[j]);
|
|
}
|
|
|
|
this._createGraph('cpu');
|
|
}
|
|
else {
|
|
// this is a notification data; append to _cpuStatsData
|
|
data = textMsg.trim();
|
|
this._addNewData(this._cpuStatsData, data, 'cpu');
|
|
this._updateCpuGraph();
|
|
}
|
|
}
|
|
else if (textMsg.startsWith('sent_activity')) {
|
|
textMsg = textMsg.split(' ')[1];
|
|
if (textMsg.endsWith(',')) {
|
|
// This is the result of query, not notification
|
|
data = textMsg.substring(0, textMsg.length - 1).split(',');
|
|
|
|
for (i = this._sentStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
|
|
this._sentStatsData[i].value = parseInt(data[j]);
|
|
}
|
|
|
|
if ($('#NetVisualisation').html() === '')
|
|
this._createGraph('net');
|
|
}
|
|
else {
|
|
// this is a notification data; append to _sentStatsData
|
|
data = textMsg.trim();
|
|
this._addNewData(this._sentStatsData, parseInt(data), 'sent');
|
|
this._updateNetGraph();
|
|
}
|
|
}
|
|
else if (textMsg.startsWith('recv_activity')) {
|
|
textMsg = textMsg.split(' ')[1];
|
|
if (textMsg.endsWith(',')) {
|
|
// This is the result of query, not notification
|
|
data = textMsg.substring(0, textMsg.length - 1).split(',');
|
|
|
|
for (i = this._recvStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
|
|
this._recvStatsData[i].value = parseInt(data[j]);
|
|
}
|
|
|
|
if ($('#NetVisualisation').html() === '')
|
|
this._createGraph('net');
|
|
}
|
|
else {
|
|
// this is a notification data; append to _recvStatsData
|
|
data = textMsg.trim();
|
|
this._addNewData(this._recvStatsData, parseInt(data), 'recv');
|
|
this._updateNetGraph();
|
|
}
|
|
}
|
|
},
|
|
|
|
onSocketClose: function() {
|
|
clearInterval(this._basicStatsIntervalId);
|
|
this.base.call(this);
|
|
}
|
|
});
|
|
|
|
Admin.Analytics = function(host) {
|
|
return new AdminSocketAnalytics(host);
|
|
};
|