aboutsummaryrefslogtreecommitdiff
path: root/src/api/utils/StatsGenerator.js
diff options
context:
space:
mode:
authorKana <[email protected]>2021-01-08 19:48:25 +0900
committerGitHub <[email protected]>2021-01-08 19:48:25 +0900
commit3cfb2721ce64ab94fdbc9c97b54602acc3c654be (patch)
treeb427becf78c51081e58f1b2af98b2662f7626a39 /src/api/utils/StatsGenerator.js
parentMerge pull request #248 from WeebDev/feature/stats-dashboard (diff)
parentfix: pg driver doesn't return anything without .returning() (diff)
downloadhost.fuwn.me-3cfb2721ce64ab94fdbc9c97b54602acc3c654be.tar.xz
host.fuwn.me-3cfb2721ce64ab94fdbc9c97b54602acc3c654be.zip
Merge pull request #250 from Zephyrrus/feature/system_status_page
Feature/system status page
Diffstat (limited to 'src/api/utils/StatsGenerator.js')
-rw-r--r--src/api/utils/StatsGenerator.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/api/utils/StatsGenerator.js b/src/api/utils/StatsGenerator.js
new file mode 100644
index 0000000..ce73cd2
--- /dev/null
+++ b/src/api/utils/StatsGenerator.js
@@ -0,0 +1,225 @@
+const si = require('systeminformation');
+
+class StatsGenerator {
+ // symbols would be better because they're unique, but harder to serialize them
+ static Type = Object.freeze({
+ // should contain key value: number
+ TIME: 'time',
+ // should contain key value: number
+ BYTE: 'byte',
+ // should contain key value: { used: number, total: number }
+ BYTE_USAGE: 'byteUsage',
+ // should contain key data: Array<{ key: string, value: number | string }>
+ // and optionally a count/total
+ DETAILED: 'detailed',
+ // hidden type should be skipped during iteration, can contain anything
+ // these should be treated on a case by case basis on the frontend
+ HIDDEN: 'hidden'
+ });
+
+ static statGenerators = {
+ system: StatsGenerator.getSystemInfo,
+ fileSystems: StatsGenerator.getFileSystemsInfo,
+ uploads: StatsGenerator.getUploadsInfo,
+ users: StatsGenerator.getUsersInfo,
+ albums: StatsGenerator.getAlbumStats
+ };
+
+ static keyOrder = Object.keys(StatsGenerator.statGenerators);
+
+ static async getSystemInfo() {
+ const os = await si.osInfo();
+
+ const currentLoad = await si.currentLoad();
+ const mem = await si.mem();
+ const time = si.time();
+ const nodeUptime = process.uptime();
+
+ return {
+ 'Platform': `${os.platform} ${os.arch}`,
+ 'Distro': `${os.distro} ${os.release}`,
+ 'Kernel': os.kernel,
+ 'CPU Load': `${currentLoad.currentload.toFixed(1)}%`,
+ 'CPUs Load': currentLoad.cpus.map(cpu => `${cpu.load.toFixed(1)}%`).join(', '),
+ 'System Memory': {
+ value: {
+ used: mem.active,
+ total: mem.total
+ },
+ type: StatsGenerator.Type.BYTE_USAGE
+ },
+ 'Memory Usage': {
+ value: process.memoryUsage().rss,
+ type: StatsGenerator.Type.BYTE
+ },
+ 'System Uptime': {
+ value: time.uptime,
+ type: StatsGenerator.Type.TIME
+ },
+ 'Node.js': `${process.versions.node}`,
+ 'Service Uptime': {
+ value: Math.floor(nodeUptime),
+ type: StatsGenerator.Type.TIME
+ }
+ };
+ }
+
+ static async getFileSystemsInfo() {
+ const stats = {};
+
+ const fsSize = await si.fsSize();
+ for (const fs of fsSize) {
+ stats[`${fs.fs} (${fs.type}) on ${fs.mount}`] = {
+ value: {
+ total: fs.size,
+ used: fs.used
+ },
+ type: StatsGenerator.Type.BYTE_USAGE
+ };
+ }
+
+ return stats;
+ }
+
+ static async getUploadsInfo(db) {
+ const stats = {
+ 'Total': 0,
+ 'Images': 0,
+ 'Videos': 0,
+ 'Others': {
+ data: {},
+ count: 0,
+ type: StatsGenerator.Type.DETAILED
+ },
+ 'Size in DB': {
+ value: 0,
+ type: StatsGenerator.Type.BYTE
+ }
+ };
+
+ const getFilesCountAndSize = async () => {
+ const uploads = await db.table('files').select('size');
+
+ return {
+ 'Total': uploads.length,
+ 'Size in DB': {
+ value: uploads.reduce((acc, upload) => acc + parseInt(upload.size, 10), 0),
+ type: StatsGenerator.Type.BYTE
+ }
+ };
+ };
+
+ const getImagesCount = async () => {
+ const Images = await db.table('files')
+ .where('type', 'like', `image/%`)
+ .count('id as count')
+ .then(rows => rows[0].count);
+
+ return { Images };
+ };
+
+ const getVideosCount = async () => {
+ const Videos = await db.table('files')
+ .where('type', 'like', `video/%`)
+ .count('id as count')
+ .then(rows => rows[0].count);
+
+ return { Videos };
+ };
+
+ const getOthersCount = async () => {
+ // rename to key, value from type, count
+ const data = await db.table('files')
+ .select('type as key')
+ .count('id as value')
+ .whereNot('type', 'like', `image/%`)
+ .whereNot('type', 'like', `video/%`)
+ .groupBy('key')
+ .orderBy('value', 'desc');
+
+ const count = data.reduce((acc, val) => acc + val.value, 0);
+
+ return {
+ Others: {
+ data,
+ count,
+ type: StatsGenerator.Type.DETAILED
+ }
+ };
+ };
+
+ const result = await Promise.all([getFilesCountAndSize(), getImagesCount(), getVideosCount(), getOthersCount()]);
+
+ return { ...stats, ...Object.assign({}, ...result) };
+ }
+
+ static async getUsersInfo(db) {
+ const stats = {
+ Total: 0,
+ Admins: 0,
+ Disabled: 0
+ };
+
+ const users = await db.table('users');
+ stats.Total = users.length;
+
+ for (const user of users) {
+ if (!user.enabled) {
+ stats.Disabled++;
+ }
+
+ if (user.isAdmin) {
+ stats.Admins++;
+ }
+ }
+
+ return stats;
+ }
+
+ static async getAlbumStats(db) {
+ const stats = {
+ 'Total': 0,
+ 'NSFW': 0,
+ 'Generated archives': 0,
+ 'Generated identifiers': 0,
+ 'Files in albums': 0
+ };
+
+ const albums = await db.table('albums');
+ stats.Total = albums.length;
+ for (const album of albums) {
+ if (album.nsfw) stats.NSFW++;
+ if (album.zipGeneratedAt) stats['Generated archives']++; // XXX: Bobby checks each after if a zip really exists on the disk. Is it really needed?
+ }
+
+ stats['Generated identifiers'] = await db.table('albumsLinks').count('id as count').then(rows => rows[0].count);
+ stats['Files in albums'] = await db.table('albumsFiles')
+ .whereNotNull('albumId')
+ .count('id as count')
+ .then(rows => rows[0].count);
+
+ return stats;
+ }
+
+ static async getStats(db) {
+ const res = {};
+
+ for (const [name, funct] of Object.entries(StatsGenerator.statGenerators)) {
+ res[name] = await funct(db);
+ }
+
+ return res;
+ }
+
+ static async getMissingStats(db, existingStats) {
+ const res = {};
+
+ for (const [name, funct] of Object.entries(StatsGenerator.statGenerators)) {
+ if (existingStats.indexOf(name) === -1) res[name] = await funct(db);
+ }
+
+ return res;
+ }
+}
+
+module.exports = StatsGenerator;