system: add disk usage task

This commit is contained in:
Girish Ramakrishnan
2025-07-16 23:09:06 +02:00
parent 11a6cf8236
commit 5539f74bea
10 changed files with 150 additions and 94 deletions
+41 -52
View File
@@ -8,19 +8,18 @@ exports = module.exports = {
getSwaps,
checkDiskSpace,
getMemory,
getDiskUsage,
updateDiskUsage,
startUpdateDiskUsage,
getLogs,
getBlockDevices,
runSystemChecks,
getProvider,
getCpus,
getFilesystems
getFilesystems,
getFilesystemUsage
};
const apps = require('./apps.js'),
assert = require('assert'),
{ AsyncTask } = require('./asynctask.js'),
backups = require('./backups.js'),
BoxError = require('./boxerror.js'),
debug = require('debug')('box:system'),
@@ -35,26 +34,27 @@ const apps = require('./apps.js'),
paths = require('./paths.js'),
safe = require('safetydance'),
shell = require('./shell.js')('system'),
tasks = require('./tasks.js'),
volumes = require('./volumes.js');
const DU_CMD = path.join(__dirname, 'scripts/du.sh');
const HDPARM_CMD = path.join(__dirname, 'scripts/hdparm.sh');
const REBOOT_CMD = path.join(__dirname, 'scripts/reboot.sh');
async function du(file) {
async function du(file, options) {
assert.strictEqual(typeof file, 'string');
assert.strictEqual(typeof options, 'object');
const [error, stdoutResult] = await safe(shell.sudo([ DU_CMD, file ], { encoding: 'utf8' }));
const [error, stdoutResult] = await safe(shell.sudo([ DU_CMD, file ], { encoding: 'utf8', signal: options.signal }));
if (error) throw new BoxError(BoxError.FS_ERROR, error);
return parseInt(stdoutResult.trim(), 10);
}
async function hdparm(file) {
async function hdparm(file, options) {
assert.strictEqual(typeof file, 'string');
assert.strictEqual(typeof options, 'object');
const [error, stdoutResult] = await safe(shell.sudo([ HDPARM_CMD, file ], { encoding: 'utf8' }));
const [error, stdoutResult] = await safe(shell.sudo([ HDPARM_CMD, file ], { encoding: 'utf8', signal: options.signal }));
if (error) throw new BoxError(BoxError.FS_ERROR, error);
const lines = stdoutResult.split('\n');
@@ -218,62 +218,57 @@ async function getMemory() {
};
}
async function getDiskUsage() {
const cache = safe.JSON.parse(safe.fs.readFileSync(paths.DISK_USAGE_CACHE_FILE, 'utf8'));
if (cache?.disks) {
cache.filesystems = cache.disks; // legacy cache file had "disks"
delete cache.disks;
class FilesystemUsageTask extends AsyncTask {
#filesystem;
constructor(filesystem) {
super(`FileSystemUsageTask(${filesystem.filesystem})`);
this.#filesystem = filesystem;
}
return cache;
}
async function updateDiskUsage(progressCallback) {
assert.strictEqual(typeof progressCallback, 'function');
async _run(signal) {
const { filesystem, type, contents } = this.#filesystem;
const filesystems = await getFilesystems();
const now = Date.now();
let percent = 5;
let percent = 1;
const dockerDf = await docker.df();
const excludePaths = (safe.fs.readFileSync(paths.DISK_USAGE_EXCLUDE_FILE, 'utf8') || '').split('\n');
const fsCount = Object.keys(filesystems).length;
for (const fsPath in filesystems) {
const filesystem = filesystems[fsPath];
if (filesystem.type === 'ext4' || filesystem.type === 'xfs') { // hdparm only works with block devices
const [speedError, speed] = await safe(hdparm(fsPath));
if (speedError) progressCallback({ message: `hdparm error: ${speedError.message}`});
filesystem.speed = speedError ? -1 : speed;
if (type === 'ext4' || type === 'xfs') { // hdparm only works with block devices
this.emitProgress(percent, 'Calculating Disk Speed');
const [speedError, speed] = await safe(hdparm(filesystem, { signal }));
if (speedError) debug(`hdparm error ${filesystem}: ${speedError.message}`);
this.emitData({ speed: speedError ? -1 : speed });
} else {
filesystem.speed = -1;
this.emitData({ speed: -1 });
}
percent += (100/fsCount);
progressCallback({ percent, message: `Checking contents of ${fsPath}`});
const dockerDf = await docker.df();
for (const content of filesystem.contents) {
progressCallback({ message: `Checking du of ${content.id} ${content.path}`});
for (const content of contents) {
percent += (100/contents.length);
if (signal.aborted) return;
this.emitProgress(percent,`Checking du of ${content.id} ${content.path}`);
if (content.id === 'docker') {
content.usage = dockerDf.LayersSize;
} else if (content.id === 'docker-volumes') {
content.usage = dockerDf.Volumes.map((v) => v.UsageData.Size).reduce((a,b) => a + b, 0);
} else if (excludePaths.includes(fsPath)) {
debug(`updateDiskUsage: skipping since path ${fsPath} is excluded`);
content.usage = 0;
} else {
const [error, usage] = await safe(du(content.path));
if (error) progressCallback({ message: `du error: ${error.message}`}); // can happen if app is installing etc
const [error, usage] = await safe(du(content.path, { signal }));
if (error) debug(`du error ${content.path}: ${error.message}`); // can happen if app is installing etc
content.usage = usage || 0;
}
progressCallback({ message: `du of ${JSON.stringify(content)}: ${content.usage}`});
this.emitData({ content });
}
}
}
if (!safe.fs.writeFileSync(paths.DISK_USAGE_CACHE_FILE, JSON.stringify({ ts: now, filesystems }, null, 4), 'utf8')) throw new BoxError(BoxError.FS_ERROR, `Could not write du cache file: ${safe.error.message}`);
async function getFilesystemUsage(fsPath) {
assert.strictEqual(typeof fsPath, 'string');
return filesystems;
const filesystems = await getFilesystems();
if (!(fsPath in filesystems)) throw new BoxError(BoxError.BAD_FIELD, 'No such filesystem');
const filesystem = filesystems[fsPath];
return new FilesystemUsageTask(filesystem);
}
async function reboot() {
@@ -303,12 +298,6 @@ async function getInfo() {
};
}
async function startUpdateDiskUsage() {
const taskId = await tasks.add(tasks.TASK_UPDATE_DISK_USAGE, []);
safe(tasks.startTask(taskId, {}), { debug }); // background
return taskId;
}
async function getLogs(unit, options) {
assert.strictEqual(typeof unit, 'string');
assert(options && typeof options === 'object');