diff --git a/CHANGELOG.md b/CHANGELOG.md index 40698cc..6af7d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.1 + +### Important Notes + +- Update stats formatting + ## 0.0.1 (Example) ### Important Notes diff --git a/package-lock.json b/package-lock.json index 832e4a2..912b6f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sama-push-queue-board", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sama-push-queue-board", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@bull-board/api": "^5.2.0", @@ -14,7 +14,8 @@ "@fastify/view": "7.4.1", "bullmq": "^3.14.2", "dotenv": "^16.1.1", - "fastify": "^4.17.0" + "fastify": "^4.17.0", + "pretty-ms": "9.3.0" } }, "node_modules/@bull-board/api": { @@ -928,6 +929,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pino": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/pino/-/pino-8.14.1.tgz", @@ -963,6 +976,21 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz", "integrity": "sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ==" }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", diff --git a/package.json b/package.json index ca8b68b..91c1dba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sama-push-queue-board", - "version": "0.1.0", + "version": "0.1.1", "description": "Push queue dashboard", "main": "index.js", "scripts": { @@ -19,6 +19,7 @@ "@fastify/view": "7.4.1", "bullmq": "^3.14.2", "dotenv": "^16.1.1", - "fastify": "^4.17.0" + "fastify": "^4.17.0", + "pretty-ms": "9.3.0" } } diff --git a/sama-stats/index.js b/sama-stats/index.js index 7bf5ebb..ab1a1ac 100644 --- a/sama-stats/index.js +++ b/sama-stats/index.js @@ -1,7 +1,10 @@ const path = require('node:path') const Redis = require('ioredis') +const { default: prettyMs } = require('pretty-ms') const pointOfView = require('@fastify/view') +const PING_TIMEOUT_MS = 3_000 + let redisConnection = void 0 let lastFetchDate = void 0 let lastServerStats = {} @@ -63,6 +66,48 @@ const updateClusterStats = async () => { const startFetchingServerStats = () => setInterval(updateClusterStats, process.env.SERVER_UPDATE_INTERVAL ?? 30_000) +const checkHealth = async (req, reply) => { + const redisResult = await pingRedis() + const dependencies = [ + redisResult, + ...(Object.values(lastServerStats) ?? []) + ] + + const isOk = dependencies.every(item => item?.status === 'ok') + + const status = { + status: isOk ? 'ok' : 'fail', + uptime_seconds: Math.floor(process.uptime()), + dependencies + } + + return status +} + +const pingRedis = async () => { + try { + await Promise.race([ + redisConnection.ping(), + new Promise((resolve, reject) => setTimeout(() => reject(new Error(`ping timed out after ${PING_TIMEOUT_MS}ms`)), PING_TIMEOUT_MS)) + ]) + return { name: 'redis', status: 'ok' } + } catch (error) { + return { name: 'redis', status: 'fail', error: error.message } + } +} + +const formatStats = (stats) => { + if (!Object.entries(stats).length || !stats) return stats + + stats = JSON.parse(JSON.stringify(stats)) + + Object.entries(stats).forEach(([endpoint, item]) => { + item.uptime = prettyMs(item.uptime_seconds * 1000) + }) + + return stats +} + module.exports = (fastifyApp, redisOptions) => { redisConnection = new Redis(redisOptions) @@ -82,12 +127,21 @@ module.exports = (fastifyApp, redisOptions) => { }); }, }); + + fastifyApp.route({ + method: 'GET', + url: '/stats/health', + handler: async (req, reply) => { + const stats = await checkHealth() + reply.send(stats) + }, + }); fastifyApp.route({ method: 'GET', url: '/stats/data/sama-server', handler: async (req, reply) => { - reply.send({ fetchDate: lastFetchDate, stats: lastServerStats }) + reply.send({ fetchDate: lastFetchDate, stats: formatStats(lastServerStats) }) }, });