307 lines
13 KiB
JavaScript
307 lines
13 KiB
JavaScript
'use strict';
|
|
|
|
/* global Chart:false */
|
|
/* global asyncForEach:false */
|
|
/* global angular:false */
|
|
/* global $:false */
|
|
|
|
angular.module('Application').controller('GraphsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
|
|
Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });
|
|
|
|
$scope.memoryUsageSystem = [];
|
|
$scope.memoryUsageApps = [];
|
|
$scope.activeApp = null;
|
|
$scope.disks = [];
|
|
|
|
$scope.errorMessage = '';
|
|
|
|
$scope.installedApps = Client.getInstalledApps();
|
|
|
|
function bytesToMegaBytes(value) {
|
|
return (value/1024/1024).toFixed(2);
|
|
}
|
|
|
|
// http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
|
|
function getRandomColor() {
|
|
var letters = '0123456789ABCDEF'.split('');
|
|
var color = '#';
|
|
for (var i = 0; i < 6; i++ ) {
|
|
color += letters[Math.floor(Math.random() * 16)];
|
|
}
|
|
return color;
|
|
}
|
|
|
|
var colorIndex = 0;
|
|
var colors = [ '#2196F3', '#3995b1', '#f0ad4e', '#ff4c4c' ];
|
|
function getNextColor() {
|
|
if (colors[colorIndex+1]) return colors[colorIndex++];
|
|
return getRandomColor();
|
|
}
|
|
|
|
$scope.setError = function (context, error) {
|
|
$scope.errorMessage = 'Error loading ' + context + ' stats : ' + error.message + '. Try restarting the graphite service.';
|
|
};
|
|
|
|
$scope.setMemoryApp = function (app, color) {
|
|
$scope.activeApp = app;
|
|
|
|
var timePeriod = 12 * 60; // in minutes
|
|
var timeBucketSize = 60; // in minutes
|
|
|
|
var target;
|
|
if (app === 'system') target = 'summarize(collectd.localhost.memory.memory-used, "' + timeBucketSize + 'min", "avg")';
|
|
else target = 'summarize(collectd.localhost.table-' + app.id + '-memory.gauge-rss, "' + timeBucketSize + 'min", "avg")';
|
|
|
|
Client.graphs([target], '-' + timePeriod + 'min', {}, function (error, result) {
|
|
if (error) return $scope.setError('memory', error);
|
|
|
|
// translate the data from bytes to MB
|
|
var datapoints = result[0].datapoints.map(function (d) { return parseInt((d[0] / 1024 / 1024).toFixed(2)); });
|
|
var labels = datapoints.map(function (d, index) {
|
|
var dateTime = new Date(Date.now() - ((timePeriod - (index * timeBucketSize)) * 60 *1000));
|
|
return ('0' + dateTime.getHours()).slice(-2) + ':00';
|
|
});
|
|
|
|
var data = {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Memory',
|
|
backgroundColor: color || '#82C4F8',
|
|
borderColor: color || '#2196F3',
|
|
borderWidth: 2,
|
|
pointBackgroundColor: color || 'rgba(151,187,205,1)',
|
|
pointBorderColor: color || '#2196F3',
|
|
pointHoverBackgroundColor: color || '#82C4F8',
|
|
pointHoverBorderColor: color || '#82C4F8',
|
|
data: datapoints
|
|
}]
|
|
};
|
|
|
|
var scaleMax = 0;
|
|
if ($scope.activeApp === 'system') {
|
|
scaleMax = Client.getConfig().memory;
|
|
} else {
|
|
scaleMax = $scope.activeApp.memoryLimit || $scope.activeApp.manifest.memoryLimit || (256 * 1024 * 1024);
|
|
}
|
|
|
|
var stepSize;
|
|
if (scaleMax >= (8 * 1024 * 1024 * 1024)) stepSize = 1024;
|
|
else if (scaleMax >= (4 * 1024 * 1024 * 1024)) stepSize = 512;
|
|
else if (scaleMax >= (2 * 1024 * 1024 * 1024)) stepSize = 256;
|
|
else stepSize = 128;
|
|
|
|
var options = {
|
|
legend: {
|
|
display: false
|
|
},
|
|
scales: {
|
|
yAxes: [{
|
|
ticks: {
|
|
min: 0,
|
|
max: Math.round(scaleMax / (1024 * 1024)),
|
|
stepSize: stepSize,
|
|
beginAtZero: true
|
|
}
|
|
}]
|
|
}
|
|
};
|
|
|
|
var ctx = $('#memoryAppChart').get(0).getContext('2d');
|
|
new Chart(ctx, { type: 'line', data: data, options: options });
|
|
});
|
|
};
|
|
|
|
$scope.updateDiskGraphs = function () {
|
|
// https://graphite.readthedocs.io/en/latest/render_api.html#paths-and-wildcards
|
|
// on scaleway, for some reason docker devices are collected as part of collectd
|
|
// until we figure why just hardcode popular disk devices - https://www.mjmwired.net/kernel/Documentation/devices.txt
|
|
Client.disks(function (error, result) {
|
|
if (error) return $scope.setError('disk', error);
|
|
|
|
result.disks.forEach(function (disk, index) {
|
|
disk.id = index;
|
|
disk.contains = [];
|
|
|
|
if (disk.filesystem === result.platformDataDisk) disk.contains.push({ label: 'Platform data', id: 'platformdata', usage: 0 });
|
|
if (disk.filesystem === result.boxDataDisk) disk.contains.push({ label: 'Box data', id: 'boxdata', usage: 0 });
|
|
if (disk.filesystem === result.dockerDataDisk) disk.contains.push({ label: 'Docker images', id: 'docker', usage: 0 });
|
|
if (disk.filesystem === result.mailDataDisk) disk.contains.push({ label: 'Email data', id: 'maildata', usage: 0 });
|
|
|
|
const apps = Object.keys(result.apps).filter(function (appId) { return result.apps[appId] === disk.filesystem; });
|
|
apps.forEach(function (appId) {
|
|
var app = Client.getCachedAppSync(appId);
|
|
disk.contains.push({ app: app, label: app.label || app.fqdn, id: appId, usage: 0 });
|
|
});
|
|
});
|
|
|
|
$scope.disks = result.disks;
|
|
|
|
// lazy fetch graphite data
|
|
$scope.disks.forEach(function (disk) {
|
|
// /dev/sda1 -> sda1
|
|
// /dev/mapper/foo -> mapper_foo (see #348)
|
|
var diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1);
|
|
diskName = diskName.replace(/\//g, '_');
|
|
|
|
Client.graphs([
|
|
'absolute(collectd.localhost.df-' + diskName + '.df_complex-free)',
|
|
'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)',
|
|
'absolute(collectd.localhost.df-' + diskName + '.df_complex-used)'
|
|
], '-1min', {}, function (error, data) {
|
|
if (error) return $scope.setError('disk', error);
|
|
|
|
disk.size = data[2].datapoints[0][0] + data[1].datapoints[0][0] + data[0].datapoints[0][0];
|
|
disk.free = data[0].datapoints[0][0];
|
|
disk.occupied = data[2].datapoints[0][0] + data[1].datapoints[0][0];
|
|
|
|
colorIndex = 0;
|
|
disk.contains.forEach(function (content) {
|
|
content.color = getNextColor();
|
|
});
|
|
|
|
// get disk usage data
|
|
var graphiteQueries = disk.contains.map(function (content) {
|
|
return 'absolute(collectd.localhost.du-' + content.id + '.capacity-usage)';
|
|
});
|
|
|
|
Client.graphs(graphiteQueries, '-1day', { noNullPoints: true }, function (error, data) {
|
|
if (error) return $scope.setError('disk', error);
|
|
|
|
var usageOther = disk.occupied;
|
|
|
|
data.forEach(function (d) {
|
|
var content = disk.contains.find(function (content) { return d.target.indexOf(content.id) !== -1; });
|
|
if (!content) return; // didn't match any content
|
|
|
|
var tmp = d.datapoints[d.datapoints.length-1][0];
|
|
content.usage = tmp;
|
|
|
|
// deduct from overal disk usage to track other
|
|
usageOther -= tmp;
|
|
});
|
|
|
|
|
|
// add content container for other non tracked data
|
|
disk.contains.push({
|
|
label: 'Other',
|
|
id: 'other',
|
|
color: '#27CE65',
|
|
usage: usageOther
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.updateMemorySystemChart = function () {
|
|
var targets = [];
|
|
var targetsInfo = [];
|
|
|
|
targets.push('summarize(collectd.localhost.memory.memory-used, "1min", "avg")');
|
|
targetsInfo.push({ label: 'System', color: '#2196F3' });
|
|
|
|
targets.push('summarize(sum(collectd.localhost.memory.memory-buffered, collectd.localhost.memory.memory-cached), "1min", "avg")');
|
|
targetsInfo.push({ label: 'Cached', color: '#f0ad4e' });
|
|
|
|
targets.push('summarize(collectd.localhost.memory.memory-free, "1min", "avg")');
|
|
targetsInfo.push({ label: 'Free', color: '#27CE65' });
|
|
|
|
Client.graphs(targets, '-1min', {}, function (error, result) {
|
|
if (error) return $scope.setError('memory', error);
|
|
|
|
$scope.memoryUsageSystem = result.map(function (data, index) {
|
|
return {
|
|
value: bytesToMegaBytes(data.datapoints[0][0]),
|
|
color: targetsInfo[index].color,
|
|
highlight: targetsInfo[index].color,
|
|
label: targetsInfo[index].label
|
|
};
|
|
});
|
|
|
|
var tmp = {
|
|
datasets: [{
|
|
data: result.map(function (data) { return bytesToMegaBytes(data.datapoints[0][0]); }),
|
|
backgroundColor: result.map(function (data, index) { return targetsInfo[index].color; })
|
|
}],
|
|
labels: result.map(function (data, index) { return targetsInfo[index].label; })
|
|
};
|
|
|
|
var ctx = $('#memoryUsageSystemChart').get(0).getContext('2d');
|
|
new Chart(ctx, { type: 'doughnut', data: tmp, options: { legend: { display: false }}});
|
|
|
|
$('#memoryUsageSystemChart').get(0).onclick = function () {
|
|
$scope.setMemoryApp('system');
|
|
};
|
|
});
|
|
};
|
|
|
|
$scope.updateMemoryAppsChart = function () {
|
|
var targets = [];
|
|
var targetsInfo = [];
|
|
|
|
colorIndex = 0;
|
|
$scope.installedApps.forEach(function (app) {
|
|
targets.push('summarize(collectd.localhost.table-' + app.id + '-memory.gauge-rss, "1min", "avg")');
|
|
targetsInfo.push({
|
|
label: app.fqdn,
|
|
color: getNextColor(),
|
|
app: app
|
|
});
|
|
});
|
|
|
|
// we split up the request, to avoid too large query strings into graphite
|
|
var tmp = [];
|
|
var aggregatedResult= [];
|
|
|
|
while (targets.length > 0) tmp.push(targets.splice(0, 10));
|
|
|
|
asyncForEach(tmp, function (targets, callback) {
|
|
Client.graphs(targets, '-1min', {}, function (error, result) {
|
|
if (error) return callback(error);
|
|
|
|
aggregatedResult = aggregatedResult.concat(result);
|
|
|
|
callback(null);
|
|
});
|
|
}, function (error) {
|
|
if (error) return $scope.setError('memory', error);
|
|
|
|
$scope.memoryUsageApps = aggregatedResult.map(function (data, index) {
|
|
return {
|
|
value: bytesToMegaBytes(data.datapoints[0][0]),
|
|
color: targetsInfo[index].color,
|
|
highlight: targetsInfo[index].color,
|
|
label: targetsInfo[index].label
|
|
};
|
|
});
|
|
|
|
var tmp = {
|
|
datasets: [{
|
|
data: aggregatedResult.map(function (data) { return bytesToMegaBytes(data.datapoints[0][0]); }),
|
|
backgroundColor: aggregatedResult.map(function (data, index) { return targetsInfo[index].color; })
|
|
}],
|
|
labels: aggregatedResult.map(function (data, index) { return targetsInfo[index].label; })
|
|
};
|
|
|
|
var options = {
|
|
onClick: function (event, dataset) {
|
|
var selectedDataInfo = targetsInfo.find(function (info) { return info.label === dataset[0]._model.label; });
|
|
if (selectedDataInfo) $scope.setMemoryApp(selectedDataInfo.app, selectedDataInfo.color);
|
|
},
|
|
legend: { display: false }
|
|
};
|
|
|
|
var ctx = $('#memoryUsageAppsChart').get(0).getContext('2d');
|
|
new Chart(ctx, { type: 'doughnut', data: tmp, options: options });
|
|
});
|
|
};
|
|
|
|
Client.onReady($scope.updateDiskGraphs);
|
|
Client.onReady($scope.updateMemorySystemChart);
|
|
Client.onReady($scope.updateMemoryAppsChart);
|
|
Client.onReady($scope.setMemoryApp.bind(null, 'system'));
|
|
|
|
$('.modal-backdrop').remove();
|
|
}]);
|