'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(callback) { 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 ? volumeDisks.appsDataDisk : result.filesystem; // ignore any errors iteratorDone(); }); }, function (error) { callback(error, volumeDisks); }); }); } function getAppDisks(callback) { 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] = appDisks.appsDataDisk; return iteratorDone(); } dfFileAsync(app.dataDir, function (error, result) { appDisks[app.id] = error ? appDisks.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, getAppDisks, getVolumeDisks, ], 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: values[6], volumes: values[7] }; 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) { const pc = os.totalmem() / (os.totalmem() + getSwapSize()); const ratio = Math.round(pc * 10) / 10; // a simple ratio return Math.round(Math.round(limit * ratio) / 1048576) * 1048576; // nearest MB }