diff --git a/README.md b/README.md index 5e2c352..a854e26 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Realtime Health Status API for Node applications with Express framework. ## Features: 1. `/status` api to serve the health statuses 2. Custom configurations to customize your health API -3. Include CPU, Memory, Load, Request and Response statistics with health API +3. Include CPU, Memory, Custom services(e.g: Docker) Load, Request and Response statistics with health API 4. Attach status of the dependent services/consumed services with your health API 5. Customize your server API statuses with dependent services/consumed services 6. Secure your health endpoint before exposing your server related details @@ -112,6 +112,7 @@ when you enable API Security for health API, | ── common | ☐ | true | Retrieve common(OS, Uptime) information | | ── cpu | ☐ | true | Retrieve CPU(Cores, Speeds) information | | ── memory | ☐ | true | Retrieve memory(Total, Free) information | +| ── services | ☐ | undefined | Retrieve running service information from the server (Array of process names) | | | | This is the example configuration to configure required system information, @@ -121,6 +122,7 @@ This is the example configuration to configure required system information, common: true, cpu: true, memory: true, + services: ["mysql", "apache2", "docker"] } ... ``` @@ -158,10 +160,11 @@ Structure should follow this pattern : `{ apiId: { ...api object } }`. API objec "response": { // Response configuration [Object] "statusCodes": true, // Attach statusCodes with responses [Boolean] }, - "systemInformation": { // Attach system information with responses [Boolean/Object] - "common": true, // Attach common information [Boolean] - "cpu": true, // Attach cpu information [Boolean] - "memory": true // Attach memory information [Boolean] + "systemInformation": { // Attach system information with responses [Boolean/Object] + "common": true, // Attach common information [Boolean] + "cpu": true, // Attach cpu information [Boolean] + "memory": true // Attach memory information [Boolean] + "services": ["mysql", "apache2"] // Array of process names [Array] }, "consumedServicesAsyncMode": false, // Consumed Services request mode [Boolean] "consumedServices": { // Consumed services configuration [Object] diff --git a/src/collectSystemInformation.js b/src/collectSystemInformation.js index 3286f7c..f320228 100644 --- a/src/collectSystemInformation.js +++ b/src/collectSystemInformation.js @@ -92,6 +92,30 @@ const getMemoryInformation = async () => { return memoryInfo; }; +/** + * Collect system service's information + * @param {*} serviceProcessNames Comma separated process names. E.g: "mysql, apache" + */ +const getServicesInformation = async (serviceProcessNames) => { + let serviceInfo = {}; + try { + const serviceResponse = await systemInfo.services(serviceProcessNames); + + serviceResponse.forEach(res => { + const { name, running, pids, pcpu, pmem } = res; + serviceInfo[name] = { + running, + totalProcesses: pids ? pids.length : 0, + cpu: Number(pcpu.toFixed(2)), + memory: Number(pmem.toFixed(2)), + }; + }); + } catch(error) { + serviceInfo = { error, message: 'Error occurred while collecting service metrics' }; + } + return serviceInfo; +}; + /** * Collect system information such as OS, CPU and Memory information * @param {*} config { common: boolean, cpu: boolean, memory: boolean } @@ -99,7 +123,7 @@ const getMemoryInformation = async () => { const collectSystemInformation = async (config) => { const response = {}; if (config) { - const { common, cpu, memory } = config; + const { common, cpu, memory, services } = config; if (common) { response.common = await getCommonInformation(); } @@ -109,8 +133,9 @@ const collectSystemInformation = async (config) => { if (memory) { response.memory = await getMemoryInformation(); } - - // TODO: Add Services status : mysql, apache2 + if (services) { + response.services = await getServicesInformation(services); + } } return response; }; diff --git a/src/configValidator.js b/src/configValidator.js index 245bd29..87395cd 100644 --- a/src/configValidator.js +++ b/src/configValidator.js @@ -52,7 +52,7 @@ const configValidator = config => { }; } } else if (typeof systemInformation === "object") { - const { common, cpu, memory } = systemInformation; + const { common, cpu, memory, services } = systemInformation; if (typeof common !== "boolean") { systemInformation.common = true; } @@ -62,6 +62,9 @@ const configValidator = config => { if (typeof memory !== "boolean") { systemInformation.memory = defaultConfig.systemInformation.memory; } + if (services && Array.isArray(services)) { + systemInformation.services = services.join(); + } } else { config.systemInformation = defaultConfig.systemInformation; } diff --git a/test/collectSystemInformation.test.js b/test/collectSystemInformation.test.js index c51e951..c00be88 100644 --- a/test/collectSystemInformation.test.js +++ b/test/collectSystemInformation.test.js @@ -35,7 +35,21 @@ describe("should collect system information provide accurate system metrics", () systemInfo.mem = () => ({ total: 1073741824, active: 734003200 - }) + }) + systemInfo.services = (commaSepServices) => { + const res = [] + const serviceNames = commaSepServices.split(","); + serviceNames.forEach(name => { + res.push({ + name, + running: true, + pids: [1], + pcpu: 1.23456, + pmem: 0.7896 + }) + }); + return res; + } }) it("should return accurate common information", async () => { @@ -53,6 +67,7 @@ describe("should collect system information provide accurate system metrics", () expect(res).not.haveOwnProperty('cpu') expect(res).not.haveOwnProperty('memory') + expect(res).not.haveOwnProperty('services') }) it("should return accurate cpu information", async () => { @@ -72,6 +87,7 @@ describe("should collect system information provide accurate system metrics", () expect(res).not.haveOwnProperty('common') expect(res).not.haveOwnProperty('memory') + expect(res).not.haveOwnProperty('services') }) it("should return accurate memory information", async () => { @@ -83,6 +99,23 @@ describe("should collect system information provide accurate system metrics", () expect(res).not.haveOwnProperty('common') expect(res).not.haveOwnProperty('cpu') + expect(res).not.haveOwnProperty('services') + }) + + it("should return accurate services information", async () => { + const res = await collectSystemInformation({ services: "abc,cde" }) + console.log(res) + expect(res).haveOwnProperty('services') + expect(res.services).haveOwnProperty("abc") + expect(res.services.abc.running).equal(true) + expect(res.services.abc.totalProcesses).equal(1) + expect(res.services.abc.cpu).equal(1.23) + expect(res.services.abc.memory).equal(0.79) + expect(res.services).haveOwnProperty("cde") + + expect(res).not.haveOwnProperty('common') + expect(res).not.haveOwnProperty('cpu') + expect(res).not.haveOwnProperty('memory') }) }) @@ -99,6 +132,7 @@ describe("should collect system information provide accurate system metrics", () systemInfo.osInfo = () => { throw testError } systemInfo.cpu = () => { throw testError } systemInfo.mem = () => { throw testError } + systemInfo.services = () => { throw testError } }) it("should return proper error details if common metrics collection failed", async () => { const res = await collectSystemInformation({ common: true }) @@ -111,6 +145,7 @@ describe("should collect system information provide accurate system metrics", () expect(res).not.haveOwnProperty('cpu') expect(res).not.haveOwnProperty('memory') + expect(res).not.haveOwnProperty('services') }) it("should return proper error details if cpu metrics collection failed", async () => { @@ -124,6 +159,7 @@ describe("should collect system information provide accurate system metrics", () expect(res).not.haveOwnProperty('common') expect(res).not.haveOwnProperty('memory') + expect(res).not.haveOwnProperty('services') }) it("should return proper error details if memory metrics collection failed", async () => { @@ -137,6 +173,20 @@ describe("should collect system information provide accurate system metrics", () expect(res).not.haveOwnProperty('common') expect(res).not.haveOwnProperty('cpu') + expect(res).not.haveOwnProperty('services') + }) + + it("should return proper error details if service metrics collection failed", async () => { + const res = await collectSystemInformation({ services: "abc,cde" }) + expect(res).haveOwnProperty('services') + expect(res.services.abc).to.be.undefined; + expect(res.services.cde).to.be.undefined; + expect(res.services).haveOwnProperty('error') + expect(res.services.error).to.equal(testError); + + expect(res).not.haveOwnProperty('common') + expect(res).not.haveOwnProperty('cpu') + expect(res).not.haveOwnProperty('memory') }) }) }); diff --git a/test/configValidator.test.js b/test/configValidator.test.js index 58826ee..823ad19 100644 --- a/test/configValidator.test.js +++ b/test/configValidator.test.js @@ -93,66 +93,6 @@ describe('should validate configurations through configValidator', () => { expect(typeof config.response.statusCodes).is.equal("boolean"); expect(config.response.statusCodes).is.equal(defaultConfig.response.statusCodes) }) - - it ("should set valid systemInformation if custom config have a invalid property", () => { - customConfig.systemInformation = undefined; - let config = configValidator(customConfig); - expect(config.systemInformation).is.not.undefined; - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation).is.equal(defaultConfig.systemInformation) - - customConfig.systemInformation = "ABC" - config = configValidator(customConfig); - expect(config.systemInformation).is.not.equal("ABC"); - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation).is.equal(defaultConfig.systemInformation) - }) - - it ("should systemInformation accept booleans to represent the whole response state", () => { - customConfig.systemInformation = true - let config = configValidator(customConfig); - expect(config.systemInformation).is.not.true; - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation).is.equal(defaultConfig.systemInformation) - - customConfig.systemInformation = false - config = configValidator(customConfig); - expect(config.systemInformation).is.not.false - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation.common).false; - expect(config.systemInformation.cpu).false; - expect(config.systemInformation.memory).false; - }) - - it ("should systemInformation accept valid object properties to represent the each response state", () => { - customConfig.systemInformation = { common: true, cpu: true, memory: true} - let config = configValidator(customConfig); - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation.common).true; - expect(config.systemInformation.cpu).true; - expect(config.systemInformation.memory).true; - - customConfig.systemInformation = { common: true, cpu: false, memory: true} - config = configValidator(customConfig); - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation.common).true; - expect(config.systemInformation.cpu).false; - expect(config.systemInformation.memory).true; - - customConfig.systemInformation = { common: false, cpu: false, memory: false} - config = configValidator(customConfig); - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation.common).false; - expect(config.systemInformation.cpu).false; - expect(config.systemInformation.memory).false; - - customConfig.systemInformation = { common: "Abc", cpu: "123", memory: 45 } - config = configValidator(customConfig); - expect(typeof config.systemInformation).is.equal("object"); - expect(config.systemInformation.common).true; - expect(config.systemInformation.cpu).true; - expect(config.systemInformation.memory).true; - }) }) describe("should validate consumed services configurations", () => { @@ -303,4 +243,83 @@ describe('should validate configurations through configValidator', () => { expect(config.apis.mockId.dependsOn[0].isRequired).is.equal(defaultConfig.apis.defaultApi.dependsOn[0].isRequired) }); }); + + describe("should validate system information configurations", () => { + it ("should set valid systemInformation if custom config have a invalid property", () => { + customConfig.systemInformation = undefined; + let config = configValidator(customConfig); + expect(config.systemInformation).is.not.undefined; + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation).is.equal(defaultConfig.systemInformation) + expect(config.systemInformation).not.haveOwnProperty("services") + + customConfig.systemInformation = "ABC" + config = configValidator(customConfig); + expect(config.systemInformation).is.not.equal("ABC"); + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation).is.equal(defaultConfig.systemInformation) + expect(config.systemInformation).not.haveOwnProperty("services") + }) + + it ("should systemInformation accept booleans to represent the whole response state", () => { + customConfig.systemInformation = true + let config = configValidator(customConfig); + expect(config.systemInformation).is.not.true; + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation).is.equal(defaultConfig.systemInformation) + expect(config.systemInformation).not.haveOwnProperty("services") + + customConfig.systemInformation = false + config = configValidator(customConfig); + expect(config.systemInformation).is.not.false + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation.common).false; + expect(config.systemInformation.cpu).false; + expect(config.systemInformation.memory).false; + expect(config.systemInformation).not.haveOwnProperty("services") + }) + + it ("should systemInformation accept valid object properties to represent the each response state", () => { + customConfig.systemInformation = { common: true, cpu: true, memory: true} + let config = configValidator(customConfig); + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation.common).true; + expect(config.systemInformation.cpu).true; + expect(config.systemInformation.memory).true; + expect(config.systemInformation).not.haveOwnProperty("services") + + customConfig.systemInformation = { common: true, cpu: false, memory: true} + config = configValidator(customConfig); + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation.common).true; + expect(config.systemInformation.cpu).false; + expect(config.systemInformation.memory).true; + expect(config.systemInformation).not.haveOwnProperty("services") + + customConfig.systemInformation = { common: false, cpu: false, memory: false} + config = configValidator(customConfig); + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation.common).false; + expect(config.systemInformation.cpu).false; + expect(config.systemInformation.memory).false; + expect(config.systemInformation).not.haveOwnProperty("services") + + customConfig.systemInformation = { common: "Abc", cpu: "123", memory: 45 } + config = configValidator(customConfig); + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation.common).true; + expect(config.systemInformation.cpu).true; + expect(config.systemInformation.memory).true; + expect(config.systemInformation).not.haveOwnProperty("systemInforservicesmation") + + customConfig.systemInformation = { services: ["abc", "cde"] } + config = configValidator(customConfig); + expect(typeof config.systemInformation).is.equal("object"); + expect(config.systemInformation.common).true; + expect(config.systemInformation.cpu).true; + expect(config.systemInformation.memory).true; + expect(config.systemInformation).haveOwnProperty("services") + expect(config.systemInformation.services).equal("abc,cde") + }) + }) }); \ No newline at end of file