From cfebc770fd98b5bac9b92307e7f3c8a168b21eae Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 23 Jul 2025 14:28:16 -0700 Subject: [PATCH 1/2] add diagnostic service --- server/controllers/diagnosticController.js | 70 +----------------- server/index.js | 6 +- server/service/business/diagnosticService.js | 78 ++++++++++++++++++++ 3 files changed, 86 insertions(+), 68 deletions(-) create mode 100644 server/service/business/diagnosticService.js diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index 6ba880dcb..a4ade8f84 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -1,5 +1,3 @@ -import v8 from "v8"; -import os from "os"; import { asyncHandler } from "../utils/errorUtils.js"; const SERVICE_NAME = "diagnosticController"; @@ -10,75 +8,13 @@ const obs = new PerformanceObserver((items) => { }); obs.observe({ entryTypes: ["measure"] }); class DiagnosticController { - constructor(db) { - this.db = db; + constructor({ diagnosticService }) { + this.diagnosticService = diagnosticService; } - getCPUUsage = async () => { - const startUsage = process.cpuUsage(); - const timingPeriod = 1000; // measured in ms - await new Promise((resolve) => setTimeout(resolve, timingPeriod)); - const endUsage = process.cpuUsage(startUsage); - const cpuUsage = { - userUsageMs: endUsage.user / 1000, - systemUsageMs: endUsage.system / 1000, - usagePercentage: ((endUsage.user + endUsage.system) / 1000 / timingPeriod) * 100, - }; - return cpuUsage; - }; - getSystemStats = asyncHandler( async (req, res, next) => { - // Memory Usage - const totalMemory = os.totalmem(); - const freeMemory = os.freemem(); - - const osStats = { - freeMemoryBytes: freeMemory, // bytes - totalMemoryBytes: totalMemory, // bytes - }; - - const used = process.memoryUsage(); - const memoryUsage = {}; - for (let key in used) { - memoryUsage[`${key}Mb`] = Math.round((used[key] / 1024 / 1024) * 100) / 100; // MB - } - - // CPU Usage - const cpuMetrics = await this.getCPUUsage(); - - // V8 Heap Statistics - const heapStats = v8.getHeapStatistics(); - const v8Metrics = { - totalHeapSizeBytes: heapStats.total_heap_size, // bytes - usedHeapSizeBytes: heapStats.used_heap_size, // bytes - heapSizeLimitBytes: heapStats.heap_size_limit, // bytes - }; - - // Event Loop Delay - let eventLoopDelay = 0; - performance.mark("start"); - await new Promise((resolve) => setTimeout(resolve, 0)); - performance.mark("end"); - performance.measure("eventLoopDelay", "start", "end"); - const entries = performance.getEntriesByName("eventLoopDelay"); - if (entries.length > 0) { - eventLoopDelay = entries[0].duration; - } - - // Uptime - const uptimeMs = process.uptime() * 1000; // ms - - // Combine Metrics - const diagnostics = { - osStats, - memoryUsage, - cpuUsage: cpuMetrics, - v8HeapStats: v8Metrics, - eventLoopDelayMs: eventLoopDelay, - uptimeMs, - }; - + const diagnostics = await this.diagnosticService.getSystemStats(); return res.success({ msg: "OK", data: diagnostics, diff --git a/server/index.js b/server/index.js index 6b91874c4..c3c23995e 100755 --- a/server/index.js +++ b/server/index.js @@ -60,6 +60,7 @@ import SuperSimpleQueueHelper from "./service/infrastructure/SuperSimpleQueue/Su // Business services import UserService from "./service/business/userService.js"; import CheckService from "./service/business/checkService.js"; +import DiagnosticService from "./service/business/diagnosticService.js"; //Network service and dependencies import NetworkService from "./service/infrastructure/networkService.js"; @@ -196,6 +197,7 @@ const startApp = async () => { settingsService, stringService, }); + const diagnosticService = new DiagnosticService(); // const jobQueueHelper = new JobQueueHelper({ // redisService, @@ -328,7 +330,9 @@ const startApp = async () => { db: ServiceRegistry.get(MongoDB.SERVICE_NAME), }); - const diagnosticController = new DiagnosticController(ServiceRegistry.get(MongoDB.SERVICE_NAME)); + const diagnosticController = new DiagnosticController({ + diagnosticService, + }); //Create routes const authRoutes = new AuthRoutes(authController); diff --git a/server/service/business/diagnosticService.js b/server/service/business/diagnosticService.js new file mode 100644 index 000000000..68b6565cd --- /dev/null +++ b/server/service/business/diagnosticService.js @@ -0,0 +1,78 @@ +import v8 from "v8"; +import os from "os"; + +const SERVICE_NAME = "diagnosticService"; + +class DiagnosticService { + static SERVICE_NAME = SERVICE_NAME; + constructor() {} + + getCPUUsage = async () => { + const startUsage = process.cpuUsage(); + const timingPeriod = 1000; // measured in ms + await new Promise((resolve) => setTimeout(resolve, timingPeriod)); + const endUsage = process.cpuUsage(startUsage); + const cpuUsage = { + userUsageMs: endUsage.user / 1000, + systemUsageMs: endUsage.system / 1000, + usagePercentage: ((endUsage.user + endUsage.system) / 1000 / timingPeriod) * 100, + }; + return cpuUsage; + }; + + getSystemStats = async () => { + // Memory Usage + const totalMemory = os.totalmem(); + const freeMemory = os.freemem(); + + const osStats = { + freeMemoryBytes: freeMemory, // bytes + totalMemoryBytes: totalMemory, // bytes + }; + + const used = process.memoryUsage(); + const memoryUsage = {}; + for (let key in used) { + memoryUsage[`${key}Mb`] = Math.round((used[key] / 1024 / 1024) * 100) / 100; // MB + } + + // CPU Usage + const cpuMetrics = await this.getCPUUsage(); + + // V8 Heap Statistics + const heapStats = v8.getHeapStatistics(); + const v8Metrics = { + totalHeapSizeBytes: heapStats.total_heap_size, // bytes + usedHeapSizeBytes: heapStats.used_heap_size, // bytes + heapSizeLimitBytes: heapStats.heap_size_limit, // bytes + }; + + // Event Loop Delay + let eventLoopDelay = 0; + performance.mark("start"); + await new Promise((resolve) => setTimeout(resolve, 0)); + performance.mark("end"); + performance.measure("eventLoopDelay", "start", "end"); + const entries = performance.getEntriesByName("eventLoopDelay"); + if (entries.length > 0) { + eventLoopDelay = entries[0].duration; + } + + // Uptime + const uptimeMs = process.uptime() * 1000; // ms + + // Combine Metrics + const diagnostics = { + osStats, + memoryUsage, + cpuUsage: cpuMetrics, + v8HeapStats: v8Metrics, + eventLoopDelayMs: eventLoopDelay, + uptimeMs, + }; + + return diagnostics; + }; +} + +export default DiagnosticService; From da49e155896f53c135d87cff87a77dc5bc9060cd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 23 Jul 2025 14:31:40 -0700 Subject: [PATCH 2/2] jsdocs, move observer --- server/controllers/diagnosticController.js | 44 ++++++++++++++++---- server/service/business/diagnosticService.js | 13 +++++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index a4ade8f84..585684211 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -1,19 +1,48 @@ import { asyncHandler } from "../utils/errorUtils.js"; const SERVICE_NAME = "diagnosticController"; - -const obs = new PerformanceObserver((items) => { - const entry = items.getEntries()[0]; - performance.clearMarks(); -}); -obs.observe({ entryTypes: ["measure"] }); +/** + * Diagnostic Controller + * + * Handles system diagnostic and monitoring requests including system statistics, + * performance metrics, and health checks. + * + * @class DiagnosticController + * @description Manages system diagnostics and performance monitoring + */ class DiagnosticController { + /** + * Creates an instance of DiagnosticController. + * + * @param {Object} dependencies - The dependencies required by the controller + * @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring + */ constructor({ diagnosticService }) { this.diagnosticService = diagnosticService; } + /** + * Retrieves comprehensive system statistics and performance metrics. + * + * @async + * @function getSystemStats + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @returns {Promise} Success response with system diagnostics data + * @description Returns system performance metrics, memory usage, CPU statistics, + * and other diagnostic information useful for monitoring system health. + * @example + * GET /diagnostics/stats + * // Response includes: + * // - Memory usage (heap, external, arrayBuffers) + * // - CPU usage statistics + * // - System uptime + * // - Performance metrics + * // - Database connection status + * // - Active processes/connections + */ getSystemStats = asyncHandler( - async (req, res, next) => { + async (req, res) => { const diagnostics = await this.diagnosticService.getSystemStats(); return res.success({ msg: "OK", @@ -24,4 +53,5 @@ class DiagnosticController { "getSystemStats" ); } + export default DiagnosticController; diff --git a/server/service/business/diagnosticService.js b/server/service/business/diagnosticService.js index 68b6565cd..5c37cba14 100644 --- a/server/service/business/diagnosticService.js +++ b/server/service/business/diagnosticService.js @@ -5,7 +5,18 @@ const SERVICE_NAME = "diagnosticService"; class DiagnosticService { static SERVICE_NAME = SERVICE_NAME; - constructor() {} + constructor() { + /** + * Performance Observer for monitoring system performance metrics. + * Clears performance marks after each measurement to prevent memory leaks. + */ + const obs = new PerformanceObserver((items) => { + // Get the first entry but we don't need to store it + items.getEntries()[0]; + performance.clearMarks(); + }); + obs.observe({ entryTypes: ["measure"] }); + } getCPUUsage = async () => { const startUsage = process.cpuUsage();