185 lines
8.4 KiB
JavaScript
185 lines
8.4 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
getSystem,
|
|
getByApp
|
|
};
|
|
|
|
const apps = require('./apps.js'),
|
|
assert = require('assert'),
|
|
BoxError = require('./boxerror.js'),
|
|
fs = require('fs'),
|
|
safe = require('safetydance'),
|
|
superagent = require('superagent'),
|
|
system = require('./system.js');
|
|
|
|
// for testing locally: curl 'http://127.0.0.1:8417/graphite-web/render?format=json&from=-1min&target=absolute(collectd.localhost.du-docker.capacity-usage)'
|
|
// the datapoint is (value, timestamp) https://buildmedia.readthedocs.org/media/pdf/graphite/0.9.16/graphite.pdf
|
|
const GRAPHITE_RENDER_URL = 'http://127.0.0.1:8417/graphite-web/render';
|
|
|
|
// https://rootlesscontaine.rs/getting-started/common/cgroup2/#checking-whether-cgroup-v2-is-already-enabled
|
|
const CGROUP_VERSION = fs.existsSync('/sys/fs/cgroup/cgroup.controllers') ? '2' : '1';
|
|
|
|
async function getByApp(app, fromMinutes, noNullPoints) {
|
|
assert.strictEqual(typeof app, 'object');
|
|
assert.strictEqual(typeof fromMinutes, 'number');
|
|
assert.strictEqual(typeof noNullPoints, 'boolean');
|
|
|
|
const timeBucketSize = fromMinutes > (24 * 60) ? (6*60) : 5;
|
|
|
|
const memoryQuery = {
|
|
target: null, // filled below
|
|
format: 'json',
|
|
from: `-${fromMinutes}min`,
|
|
until: 'now'
|
|
};
|
|
if (CGROUP_VERSION === '1') {
|
|
memoryQuery.target = `summarize(collectd.localhost.table-${app.id}-memory.gauge-memsw_usage_in_bytes, "${timeBucketSize}min", "avg")`;
|
|
} else {
|
|
memoryQuery.target = `summarize(sum(collectd.localhost.table-${app.id}-memory.gauge-memory_current, collectd.localhost.table-${app.id}-memory.gauge-memory_swap_current), "${timeBucketSize}min", "avg")`;
|
|
}
|
|
if (noNullPoints) memoryQuery.noNullPoints = true;
|
|
|
|
const [memoryError, memoryResponse] = await safe(superagent.get(GRAPHITE_RENDER_URL)
|
|
.query(memoryQuery)
|
|
.timeout(30 * 1000)
|
|
.ok(() => true));
|
|
|
|
if (memoryError) throw new BoxError(BoxError.NETWORK_ERROR, memoryError.message);
|
|
if (memoryResponse.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${memoryResponse.status} ${memoryResponse.text}`);
|
|
|
|
let diskDataPoints;
|
|
if (app.manifest.addons.localstorage) {
|
|
const diskQuery = {
|
|
target: `summarize(collectd.localhost.du-${app.id}.capacity-usage, "${timeBucketSize}min", "avg")`,
|
|
format: 'json',
|
|
from: `-${fromMinutes}min`,
|
|
until: 'now'
|
|
};
|
|
if (noNullPoints) diskQuery.noNullPoints = true;
|
|
|
|
const [diskError, diskResponse] = await safe(superagent.get(GRAPHITE_RENDER_URL)
|
|
.query(diskQuery)
|
|
.timeout(30 * 1000)
|
|
.ok(() => true));
|
|
|
|
if (diskError) throw new BoxError(BoxError.NETWORK_ERROR, diskError.message);
|
|
if (diskResponse.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${diskResponse.status} ${diskResponse.text}`);
|
|
|
|
// we may not have any datapoints
|
|
if (diskResponse.body.length === 0) diskDataPoints = [];
|
|
else diskDataPoints = diskResponse.body[0].datapoints;
|
|
} else {
|
|
diskDataPoints = [];
|
|
}
|
|
|
|
// app proxy instances have no container and thus no datapoints
|
|
return { memory: memoryResponse.body[0] || { datapoints: [] }, disk: { datapoints: diskDataPoints } };
|
|
}
|
|
|
|
async function getSystem(fromMinutes, noNullPoints) {
|
|
assert.strictEqual(typeof fromMinutes, 'number');
|
|
assert.strictEqual(typeof noNullPoints, 'boolean');
|
|
|
|
const timeBucketSize = fromMinutes > (24 * 60) ? (6*60) : 5;
|
|
|
|
const cpuQuery = `summarize(sum(collectd.localhost.aggregation-cpu-average.cpu-system, collectd.localhost.aggregation-cpu-average.cpu-user), "${timeBucketSize}min", "avg")`;
|
|
const memoryQuery = `summarize(sum(collectd.localhost.memory.memory-used, collectd.localhost.swap.swap-used), "${timeBucketSize}min", "avg")`;
|
|
|
|
const query = {
|
|
target: [ cpuQuery, memoryQuery ],
|
|
format: 'json',
|
|
from: `-${fromMinutes}min`,
|
|
until: 'now'
|
|
};
|
|
|
|
const [memCpuError, memCpuResponse] = await safe(superagent.get(GRAPHITE_RENDER_URL)
|
|
.query(query)
|
|
.timeout(30 * 1000)
|
|
.ok(() => true));
|
|
|
|
if (memCpuError) throw new BoxError(BoxError.NETWORK_ERROR, memCpuError.message);
|
|
if (memCpuResponse.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${memCpuResponse.status} ${memCpuResponse.text}`);
|
|
|
|
const allApps = await apps.list();
|
|
const appResponses = {};
|
|
for (const app of allApps) {
|
|
appResponses[app.id] = await getByApp(app, fromMinutes, noNullPoints);
|
|
}
|
|
|
|
const diskInfo = await system.getDisks();
|
|
|
|
// segregate locations into the correct disks based on 'filesystem'
|
|
diskInfo.disks.forEach(function (disk, index) {
|
|
disk.id = index;
|
|
disk.contains = [];
|
|
|
|
if (disk.filesystem === diskInfo.platformDataDisk) disk.contains.push({ type: 'standard', label: 'Platform data', id: 'platformdata', usage: 0 });
|
|
if (disk.filesystem === diskInfo.boxDataDisk) disk.contains.push({ type: 'standard', label: 'Box data', id: 'boxdata', usage: 0 });
|
|
if (disk.filesystem === diskInfo.dockerDataDisk) disk.contains.push({ type: 'standard', label: 'Docker images', id: 'docker', usage: 0 });
|
|
if (disk.filesystem === diskInfo.mailDataDisk) disk.contains.push({ type: 'standard', label: 'Email data', id: 'maildata', usage: 0 });
|
|
if (disk.filesystem === diskInfo.backupsDisk) disk.contains.push({ type: 'standard', label: 'Backup data', id: 'cloudron-backup', usage: 0 });
|
|
|
|
// attach appIds which reside on this disk
|
|
const apps = Object.keys(diskInfo.apps).filter(function (appId) { return diskInfo.apps[appId] === disk.filesystem; });
|
|
apps.forEach(function (appId) {
|
|
disk.contains.push({ type: 'app', id: appId, label: '', usage: 0 });
|
|
});
|
|
|
|
// attach volumeIds which reside on this disk
|
|
const volumes = Object.keys(diskInfo.volumes).filter(function (volumeId) { return diskInfo.volumes[volumeId] === disk.filesystem; });
|
|
volumes.forEach(function (volumeId) {
|
|
disk.contains.push({ type: 'volume', id: volumeId, label: '', usage: 0 });
|
|
});
|
|
});
|
|
|
|
for (const disk of diskInfo.disks) {
|
|
// /dev/sda1 -> sda1
|
|
// /dev/mapper/foo.com -> mapper_foo_com (see #348)
|
|
let diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1);
|
|
diskName = diskName.replace(/\/|\./g, '_');
|
|
|
|
const target = [
|
|
`absolute(collectd.localhost.df-${diskName}.df_complex-free)`,
|
|
`absolute(collectd.localhost.df-${diskName}.df_complex-reserved)`, // reserved for root (default: 5%) tune2fs -l/m
|
|
`absolute(collectd.localhost.df-${diskName}.df_complex-used)`
|
|
];
|
|
|
|
const diskQuery = {
|
|
target: target,
|
|
format: 'json',
|
|
from: '-1day',
|
|
until: 'now'
|
|
};
|
|
|
|
const [diskError, diskResponse] = await safe(superagent.get(GRAPHITE_RENDER_URL).query(diskQuery).timeout(30 * 1000).ok(() => true));
|
|
if (diskError) throw new BoxError(BoxError.NETWORK_ERROR, diskError.message);
|
|
if (diskResponse.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${diskResponse.status} ${diskResponse.text}`);
|
|
|
|
disk.size = diskResponse.body[2].datapoints[0][0] + diskResponse.body[1].datapoints[0][0] + diskResponse.body[0].datapoints[0][0];
|
|
disk.free = diskResponse.body[0].datapoints[0][0];
|
|
disk.occupied = diskResponse.body[2].datapoints[0][0];
|
|
|
|
for (const content of disk.contains) {
|
|
const query = {
|
|
target: `absolute(collectd.localhost.du-${content.id}.capacity-usage)`,
|
|
format: 'json',
|
|
from: '-1day',
|
|
until: 'now'
|
|
};
|
|
|
|
const [error, response] = await safe(superagent.get(GRAPHITE_RENDER_URL).query(query).timeout(30 * 1000).ok(() => true));
|
|
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
|
if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${response.status} ${response.text}`);
|
|
|
|
// we may not have any datapoints
|
|
if (response.body.length === 0) content.usage = null;
|
|
else content.usage = response.body[0].datapoints[0][0];
|
|
|
|
console.log(content)
|
|
}
|
|
}
|
|
|
|
return { cpu: memCpuResponse.body[0], memory: memCpuResponse.body[1], apps: appResponses, disks: diskInfo.disks };
|
|
}
|