From f676aa56cb5a9aca7443b4ff94c4ea538e3227fa Mon Sep 17 00:00:00 2001 From: AntonSelezev Date: Mon, 17 Aug 2026 18:03:00 +0300 Subject: [PATCH 1/2] feat: add /stats/health endpoint to monitor Redis and server status --- sama-stats/index.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/sama-stats/index.js b/sama-stats/index.js index 7bf5ebb..4004a8e 100644 --- a/sama-stats/index.js +++ b/sama-stats/index.js @@ -2,6 +2,8 @@ const path = require('node:path') const Redis = require('ioredis') const pointOfView = require('@fastify/view') +const PING_TIMEOUT_MS = 3_000 + let redisConnection = void 0 let lastFetchDate = void 0 let lastServerStats = {} @@ -63,6 +65,36 @@ 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 } + } +} + module.exports = (fastifyApp, redisOptions) => { redisConnection = new Redis(redisOptions) @@ -82,6 +114,15 @@ 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', From 4ea2ff985946d5e2f846060ec4691ae2ed232db7 Mon Sep 17 00:00:00 2001 From: AntonSelezev Date: Tue, 18 Aug 2026 15:57:46 +0300 Subject: [PATCH 2/2] changelog: v0.1.1 --- CHANGELOG.md | 6 ++++++ package-lock.json | 34 +++++++++++++++++++++++++++++++--- package.json | 5 +++-- sama-stats/index.js | 15 ++++++++++++++- 4 files changed, 54 insertions(+), 6 deletions(-) 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 4004a8e..ab1a1ac 100644 --- a/sama-stats/index.js +++ b/sama-stats/index.js @@ -1,5 +1,6 @@ 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 @@ -95,6 +96,18 @@ const pingRedis = async () => { } } +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) @@ -128,7 +141,7 @@ module.exports = (fastifyApp, redisOptions) => { method: 'GET', url: '/stats/data/sama-server', handler: async (req, reply) => { - reply.send({ fetchDate: lastFetchDate, stats: lastServerStats }) + reply.send({ fetchDate: lastFetchDate, stats: formatStats(lastServerStats) }) }, });