Add diagnostics API endpoint.

Only available with write access and above.

Reports used and available filesystem sizes and database space
usage.
This commit is contained in:
D. Berge
2024-05-08 16:27:32 +02:00
parent ec26285e53
commit af0df23cc4
6 changed files with 121 additions and 1 deletions

View File

@@ -312,6 +312,9 @@ app.map({
}
}
},
'/diagnostics/': {
get: [ mw.auth.access.write, mw.etag.noSave, mw.admin.diagnostics.get ]
},
'/rss/': {
get: [ mw.rss.get ]
}

View File

@@ -0,0 +1,17 @@
const diagnostics = require('../../../../lib/diagnostics');
module.exports = async function (req, res, next) {
try {
const d = await diagnostics();
if (req.user?.role != "admin" && req.user?.role != "user") {
}
res.status(200).json(d);
} catch (err) {
next(err);
return;
}
next();
};

View File

@@ -0,0 +1,4 @@
module.exports = {
get: require('./get')
}

View File

@@ -0,0 +1,3 @@
module.exports = {
diagnostics: require('./diagnostics')
};

View File

@@ -18,5 +18,6 @@ module.exports = {
openapi: require('./openapi'),
rss: require('./rss'),
etag: require('./etag'),
version: require('./version')
version: require('./version'),
admin: require('./admin')
};

View File

@@ -0,0 +1,92 @@
const { statfs } = require('fs').promises;
const { pool } = require('./db/connection');
const cfg = require('./config');
const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);
/** Return filesystem statistics
*/
async function df (fs="/") {
const s = await statfs(fs);
if (s) {
const total = (s.bsize * s.blocks); // bytes
const free = (s.bfree * s.bsize);
const available = (s.bavail * s.bsize);
const used = total - free;
const usedPercent = used/total * 100
return {
total,
free,
available,
used,
usedPercent
}
}
}
/** Return the size of the Dougal database
*/
async function dbSize () {
const client = await pool.connect();
let res;
try {
res = (await client.query("SELECT pg_database_size(current_database()) size;"))?.rows[0];
} catch (err) {
ERROR(err);
} finally {
client.release();
}
return res;
}
async function dbSchemaSizes () {
const text = `
SELECT pid,
(sum(table_size)::bigint) size,
((sum(table_size) / pg_database_size(current_database())) * 100) percent
FROM (
SELECT pg_catalog.pg_namespace.nspname as schema_name,
pg_relation_size(pg_catalog.pg_class.oid) as table_size
FROM pg_catalog.pg_class
JOIN pg_catalog.pg_namespace ON relnamespace = pg_catalog.pg_namespace.oid
) t
JOIN public.projects p ON schema_name = p.schema
GROUP BY pid
ORDER BY pid
`;
const client = await pool.connect();
let res;
try {
res = (await client.query(text))?.rows;
} catch (err) {
ERROR(err);
} finally {
client.release();
}
return res;
}
async function diagnostics () {
const paths = cfg._("global.imports.paths") ?? {};
const data = {};
for (path in paths) {
data[path] = await df(paths[path]);
}
const res = {
storage: {
root: await df("/"),
data
},
database: {
...(await dbSize()),
projects: Object.fromEntries((await dbSchemaSizes()).map(row => [ row.pid, {size: row.size, percent: row.percent} ]))
}
};
return res;
}
module.exports = diagnostics;