diff --git a/CHANGES b/CHANGES index 0e8dc34e9..bb59a9519 100644 --- a/CHANGES +++ b/CHANGES @@ -2560,5 +2560,5 @@ * remove external df module * Show remaining disk space in usage graph * Make users and groups available for the new app link dialog - +* Show swaps in disk graphs diff --git a/src/docker.js b/src/docker.js index adb54220d..afb7574e1 100644 --- a/src/docker.js +++ b/src/docker.js @@ -349,7 +349,7 @@ async function createSubcontainer(app, name, cmd, options) { 'syslog-format': 'rfc5424' } }, - Memory: system.getMemoryAllocation(memoryLimit), + Memory: await system.getMemoryAllocation(memoryLimit), MemorySwap: memoryLimit, // Memory + Swap PortBindings: isAppContainer ? dockerPortBindings : { }, PublishAllPorts: false, diff --git a/src/mail.js b/src/mail.js index a0e29ed62..38694ce70 100644 --- a/src/mail.js +++ b/src/mail.js @@ -714,7 +714,7 @@ async function configureMail(mailFqdn, mailDomain, serviceConfig) { const tag = infra.images.mail.tag; const memoryLimit = serviceConfig.memoryLimit || exports.DEFAULT_MEMORY_LIMIT; - const memory = system.getMemoryAllocation(memoryLimit); + const memory = await system.getMemoryAllocation(memoryLimit); const cloudronToken = hat(8 * 128), relayToken = hat(8 * 128); const certificatePath = await reverseProxy.getCertificatePath(mailFqdn, mailDomain); diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 6771e8f0c..4fe5fddbc 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -167,10 +167,13 @@ async function getConfig(req, res, next) { } async function getDisks(req, res, next) { - const [error, result] = await safe(system.getDisks()); - if (error) return next(BoxError.toHttpError(error)); + const [getDisksError, disks] = await safe(system.getDisks()); + if (getDisksError) return next(BoxError.toHttpError(getDisksError)); - next(new HttpSuccess(200, { disks: result })); + let [getSwapsError, swaps] = await safe(system.getSwaps()); + if (getSwapsError) return next(BoxError.toHttpError(getSwapsError)); + + next(new HttpSuccess(200, { disks, swaps })); } async function getDiskUsage(req, res, next) { diff --git a/src/services.js b/src/services.js index 98e4561b7..6e4e7f162 100644 --- a/src/services.js +++ b/src/services.js @@ -781,7 +781,7 @@ async function applyMemoryLimit(id) { debug(`applyMemoryLimit: ${containerName} ${JSON.stringify(serviceConfig)}`); - const memory = system.getMemoryAllocation(memoryLimit); + const memory = await system.getMemoryAllocation(memoryLimit); await docker.update(containerName, memory, memoryLimit); } @@ -921,7 +921,7 @@ async function startTurn(existingInfra) { const serviceConfig = await getServiceConfig('turn'); const tag = infra.images.turn.tag; const memoryLimit = serviceConfig.memoryLimit || SERVICES['turn'].defaultMemoryLimit; - const memory = system.getMemoryAllocation(memoryLimit); + const memory = await system.getMemoryAllocation(memoryLimit); const realm = settings.dashboardFqdn(); let turnSecret = await blobs.getString(blobs.ADDON_TURN_SECRET); @@ -1637,7 +1637,7 @@ async function startGraphite(existingInfra) { const serviceConfig = await getServiceConfig('graphite'); const tag = infra.images.graphite.tag; const memoryLimit = serviceConfig.memoryLimit || 256 * 1024 * 1024; - const memory = system.getMemoryAllocation(memoryLimit); + const memory = await system.getMemoryAllocation(memoryLimit); const upgrading = existingInfra.version !== 'none' && requiresUpgrade(existingInfra.images.graphite.tag, tag); @@ -1731,7 +1731,7 @@ async function setupRedis(app, options) { // Compute redis memory limit based on app's memory limit (this is arbitrary) const memoryLimit = app.servicesConfig['redis']?.memoryLimit || APP_SERVICES['redis'].defaultMemoryLimit; - const memory = system.getMemoryAllocation(memoryLimit); + const memory = await system.getMemoryAllocation(memoryLimit); const recoveryMode = app.servicesConfig['redis']?.recoveryMode || false; const readOnly = !recoveryMode ? '--read-only' : ''; diff --git a/src/sftp.js b/src/sftp.js index 4925074c1..97718d597 100644 --- a/src/sftp.js +++ b/src/sftp.js @@ -49,7 +49,7 @@ async function start(existingInfra) { const serviceConfig = servicesConfig['sftp'] || {}; const tag = infra.images.sftp.tag; const memoryLimit = serviceConfig.memoryLimit || exports.DEFAULT_MEMORY_LIMIT; - const memory = system.getMemoryAllocation(memoryLimit); + const memory = await system.getMemoryAllocation(memoryLimit); const cloudronToken = hat(8 * 128); await ensureKeys(); diff --git a/src/system.js b/src/system.js index 14c9ef224..89ebb014f 100644 --- a/src/system.js +++ b/src/system.js @@ -2,6 +2,7 @@ exports = module.exports = { getDisks, + getSwaps, checkDiskSpace, getMemory, getMemoryAllocation, @@ -35,6 +36,25 @@ async function du(file) { return parseInt(stdoutResult.trim(), 10); } +async function getSwaps() { + const stdout = safe.child_process.execSync('swapon --noheadings --raw --bytes --show=type,size,used,name', { encoding: 'utf8' }); + if (!stdout) return {}; + + const swaps = {}; + for (const line of stdout.trim().split('\n')) { + const parts = line.split(' ', 4); + const name = parts[3]; + swaps[name] = { + name: parts[3], + type: parts[0], // partition or file + size: parseInt(parts[1]), + used: parseInt(parts[2]), + }; + } + + return swaps; +} + async function getDisks() { let [dfError, dfEntries] = await safe(df.disks()); if (dfError) throw new BoxError(BoxError.FS_ERROR, `Error running df: ${dfError.message}`); @@ -120,25 +140,23 @@ async function checkDiskSpace() { await notifications.alert(notifications.ALERT_DISK_SPACE, 'Server is running out of disk space', markdownMessage); } -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; +async function getSwapSize() { + const swaps = await getSwaps(); + return Object.keys(swaps).map(n => swaps[n].size).reduce((acc, cur) => acc + cur); } async function getMemory() { return { memory: os.totalmem(), - swap: getSwapSize() + swap: await getSwapSize() }; } -function getMemoryAllocation(limit) { +async function getMemoryAllocation(limit) { let ratio = parseFloat(safe.fs.readFileSync(paths.SWAP_RATIO_FILE, 'utf8'), 10); if (!ratio) { - const pc = os.totalmem() / (os.totalmem() + getSwapSize()); + const pc = os.totalmem() / (os.totalmem() + await getSwapSize()); ratio = Math.round(pc * 10) / 10; // a simple ratio } diff --git a/src/test/system-test.js b/src/test/system-test.js index b28521dd8..0538a7afd 100644 --- a/src/test/system-test.js +++ b/src/test/system-test.js @@ -22,7 +22,16 @@ describe('System', function () { const disks = await system.getDisks(); expect(disks).to.be.ok(); - expect(disks.some(d => d.mountpoint === '/')).to.be.ok(); + expect(Object.keys(disks).some(fs => disks[fs].mountpoint === '/')).to.be.ok(); + }); + + it('can get swaps', async function () { + // does not work on archlinux 8! + if (require('child_process').execSync('uname -a').toString().indexOf('-arch') !== -1) return; + + const swaps = await system.getSwaps(); + expect(swaps).to.be.ok(); + expect(Object.keys(swaps).some(n => swaps[n].type === 'partition')).to.be.ok(); }); it('can check for disk space', async function () {