Skip to content

Commit

Permalink
Merge pull request #8 from APISquare/integrateCustomSystemService
Browse files Browse the repository at this point in the history
Integrate system services with system information
  • Loading branch information
suthagar23 authored Jun 9, 2020
2 parents 7dcdf40 + e1c6f1a commit 5b20b17
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 70 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -121,6 +122,7 @@ This is the example configuration to configure required system information,
common: true,
cpu: true,
memory: true,
services: ["mysql", "apache2", "docker"]
}
...
```
Expand Down Expand Up @@ -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]
Expand Down
31 changes: 28 additions & 3 deletions src/collectSystemInformation.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,38 @@ 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 }
*/
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();
}
Expand All @@ -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;
};
Expand Down
5 changes: 4 additions & 1 deletion src/configValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
52 changes: 51 additions & 1 deletion test/collectSystemInformation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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')
})
})

Expand All @@ -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 })
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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')
})
})
});
Expand Down
139 changes: 79 additions & 60 deletions test/configValidator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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")
})
})
});

0 comments on commit 5b20b17

Please sign in to comment.