Files
cloudron-box/src/system.js
Girish Ramakrishnan adfb506af4 Fix disk usage graphs
2021-01-27 21:48:06 -08:00

188 lines
6.1 KiB
JavaScript

'use strict';
exports = module.exports = {
getDisks,
checkDiskSpace,
getMemory,
getMemoryAllocation
};
const apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
BoxError = require('./boxerror.js'),
debug = require('debug')('box:disks'),
df = require('@sindresorhus/df'),
docker = require('./docker.js'),
notifications = require('./notifications.js'),
os = require('os'),
paths = require('./paths.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
volumes = require('./volumes.js');
const dfAsync = async.asyncify(df), dfFileAsync = async.asyncify(df.file);
function getVolumeDisks(appsDataDisk, callback) {
assert.strictEqual(typeof appsDataDisk, 'string');
assert.strictEqual(typeof callback, 'function');
let volumeDisks = {};
volumes.list(function (error, allVolumes) {
if (error) return callback(error);
async.eachSeries(allVolumes, function (volume, iteratorDone) {
dfFileAsync(volume.hostPath, function (error, result) {
volumeDisks[volume.id] = error ? appsDataDisk : result.filesystem; // ignore any errors
iteratorDone();
});
}, function (error) {
callback(error, volumeDisks);
});
});
}
function getAppDisks(appsDataDisk, callback) {
assert.strictEqual(typeof appsDataDisk, 'string');
assert.strictEqual(typeof callback, 'function');
let appDisks = {};
apps.getAll(function (error, allApps) {
if (error) return callback(error);
async.eachSeries(allApps, function (app, iteratorDone) {
if (!app.dataDir) {
appDisks[app.id] = appsDataDisk;
return iteratorDone();
}
dfFileAsync(app.dataDir, function (error, result) {
appDisks[app.id] = error ? appsDataDisk : result.filesystem; // ignore any errors
iteratorDone();
});
}, function (error) {
callback(error, appDisks);
});
});
}
function getBackupDisk(callback) {
assert.strictEqual(typeof callback, 'function');
settings.getBackupConfig(function (error, backupConfig) {
if (error) return callback(error);
if (backupConfig.provider !== 'filesystem') return callback(null, null);
dfFileAsync(backupConfig.backupFolder, function (error, result) {
if (error) return callback(error);
callback(null, result.filesystem);
});
});
}
function getDisks(callback) {
assert.strictEqual(typeof callback, 'function');
docker.info(function (error, info) {
if (error) return callback(error);
async.series([
dfAsync,
dfFileAsync.bind(null, paths.BOX_DATA_DIR),
dfFileAsync.bind(null, paths.PLATFORM_DATA_DIR),
dfFileAsync.bind(null, paths.APPS_DATA_DIR),
dfFileAsync.bind(null, info.DockerRootDir),
getBackupDisk,
], function (error, values) {
if (error) return callback(new BoxError(BoxError.FS_ERROR, error));
// filter by ext4 and then sort to make sure root disk is first
const ext4Disks = values[0].filter((r) => r.type === 'ext4').sort((a, b) => a.mountpoint.localeCompare(b.mountpoint));
const disks = {
disks: ext4Disks, // root disk is first. { filesystem, type, size, used, avialable, capacity, mountpoint }
boxDataDisk: values[1].filesystem,
mailDataDisk: values[1].filesystem,
platformDataDisk: values[2].filesystem,
appsDataDisk: values[3].filesystem,
dockerDataDisk: values[4].filesystem,
backupsDisk: values[5],
apps: {}, // filled below
volumes: {} // filled below
};
async.series([
getAppDisks.bind(null, disks.appsDataDisk),
getVolumeDisks.bind(null, disks.appsDataDisk)
], function (error, values) {
if (error) return callback(new BoxError(BoxError.FS_ERROR, error));
disks.apps = values[0],
disks.volumes = values[1];
callback(null, disks);
});
});
});
}
function checkDiskSpace(callback) {
assert.strictEqual(typeof callback, 'function');
debug('Checking disk space');
getDisks(function (error, disks) {
if (error) {
debug('checkDiskSpace: error getting disks %s', error.message);
return callback();
}
var oos = disks.disks.some(function (entry) {
// ignore other filesystems but where box, app and platform data is
if (entry.filesystem !== disks.boxDataDisk
&& entry.filesystem !== disks.platformDataDisk
&& entry.filesystem !== disks.appsDataDisk
&& entry.filesystem !== disks.backupsDisk
&& entry.filesystem !== disks.dockerDataDisk) return false;
return (entry.available <= (1.25 * 1024 * 1024 * 1024)); // 1.5G
});
debug('checkDiskSpace: disk space checked. ok: %s', !oos);
notifications.alert(notifications.ALERT_DISK_SPACE, 'Server is running out of disk space', oos ? JSON.stringify(disks.disks, null, 4) : '', callback);
});
}
function getSwapSize() {
const stdout = safe.child_process.execSync('swapon --noheadings --raw --bytes --show=SIZE', { encoding: 'utf8' });
const swap = !stdout ? 0 : stdout.trim().split('\n').map(x => parseInt(x, 10) || 0).reduce((acc, cur) => acc + cur);
return swap;
}
function getMemory(callback) {
assert.strictEqual(typeof callback, 'function');
callback(null, {
memory: os.totalmem(),
swap: getSwapSize()
});
}
function getMemoryAllocation(limit) {
let ratio = parseFloat(safe.fs.readFileSync(paths.SWAP_RATIO_FILE, 'utf8'), 10);
if (!ratio) {
const pc = os.totalmem() / (os.totalmem() + getSwapSize());
ratio = Math.round(pc * 10) / 10; // a simple ratio
}
return Math.round(Math.round(limit * ratio) / 1048576) * 1048576; // nearest MB
}