Skip to content

Commit

Permalink
Merge pull request #4 from adobe/cpu-metrics
Browse files Browse the repository at this point in the history
added cpu metrics, fails if no file found
  • Loading branch information
jdelbick committed Oct 7, 2019
2 parents 2dba656 + 55b0ff0 commit 5942ed2
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 122 deletions.
30 changes: 28 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in
accordance with the terms of the Adobe license agreement accompanying
it. If you have received this file from a source other than Adobe,
then your use, modification, or distribution of it requires the prior
written permission of Adobe.
written permission of Adobe.
*/

module.exports = {
"extends": "problems",
"extends": [
"problems"
],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"plugins": [
"mocha"
],
"rules": {
"prefer-arrow-callback": 0,
"prefer-template": 1,
Expand All @@ -26,5 +31,26 @@ module.exports = {
"no-console": [0, {"allow": true}],

"template-curly-spacing": [1, "never"],

// mocha rules intended to catch common problems:
// - tests marked with .only() is usually only during development
// - tests with identical titles are confusing
// - tests defined using () => {} notation do not have access to globals
// - tests nested in tests is confusing
// - empty tests point to incomplete code
// - mocha allows for synch tests, async tests using 'done' callback,
// async tests using Promise. Combining callback and a return of some value
// indicates mixing up the test types
// - multiple before/after hooks in a single test suite/test is confusing
// - passing async functions to describe() is usually wrong, the individual tests
// can be async however
"mocha/no-exclusive-tests": "error",
"mocha/no-identical-title": "error",
"mocha/no-mocha-arrows": "error",
"mocha/no-nested-tests": "error",
"mocha/no-pending-tests": "error",
"mocha/no-return-and-callback": "error",
"mocha/no-sibling-hooks": "error",
"mocha/no-async-describe": "error"
}
};
60 changes: 42 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ Calculated values:
- `containerUsage()`: `stats.rss` + `kmem.usage_in_bytes`
- `containerUsagePercentage()`:`stats.rss` + `kmem.usage_in_bytes` / `limit_in_bytes`

### CPU Metrics:
[CPU](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt) reads from path `/sys/fs/cgroup/`:

- `cpuacct.usage`: total CPU time (in nanoseconds) obtained by this cgroup (CPU time obtained by all the tasks)
in the system
- `cpuacct.stat`: reports the user and system CPU time consumed by all tasks in this cgroup (including tasks lower in the hierarchy):
- `user`: CPU time (in nanoseconds) spent by tasks of the cgroup in user mode
- `system`: CPU time (in nanoseconds) spent by tasks of the cgroup in kernel mode
- `cpuacct.usage_percpu`: CPU time (in nanoseconds) consumed on each CPU by all tasks in this cgroup (including tasks lower in the hierarchy).


### Installation

Expand All @@ -26,29 +36,43 @@ npm install cgroup-metrics

```javascript
const cgroup = require('cgroup-metrics');

// You can access each metric separately using the async functions for each metric

// Memory Metrics
const memory = cgroup.memory();
const containerUsage = await memory.containerUsage();
console.log(containerUsage);

const containerUsagePercentage = await memory.containerUsagePercentage(containerUsage);
console.log(containerUsagePercentage);

// CPU Metrics
const cpu = cgroup.cpu();
const cpuacct_usage = await cpu.usage();
console.log(`Total CPU usage: ${cpuacct_usage}`);

const cpuacct_stats = await cpu.stat();
console.log(`CPU user count: ${cpuacct_stat.user}`);
console.log(`CPU system count: ${cpuacct_stat.system}`);

const cpuacct_usage_percpu = await cpu.usage_percpu();
console.log(`CPU usage per CPU task: ${cpuacct_usage_percpu}`);

// Or you can use the function `getAllMetrics` to get an object of all the metrics
const metrics = await cgroup.getAllMetrics();

console.log(`Container usage: ${metrics.memory.containerUsage}`);
console.log(`Container usage percentage: ${metrics.memory.containerUsagePercentage}`);

// cgroup.memory() returns a promise
memory.containerUsage().then((res) => {
console.log(res);
});
memory.containerUsagePercentage().then((res) => {
console.log(res)
})

// Or in an async function:
async function getContainerUsage() {
const containerUsage = await memory.containerUsage();
console.log(containerUsage);

const containerUsagePercentage = await memory.containerUsagePercentage(containerUsage);
console.log(containerUsagePercentage);
}
getContainerUsage();
console.log(`Total CPU usage: ${metrics.cpuacct.usage}`);
console.log(`CPU user count: ${metrics.cpuacct.stat.user}`);
console.log(`CPU system count: ${metrics.cpuacct.stat.system}`);
console.log(`CPU usage per CPU task: ${metrics.cpuacct.usage_percpu}`);
```
### Error Handling

If there is no container running or there is an issue reading the file path, the function call will return `null`
If there is no container running or there is an issue reading the file path, the function call will error

### Contributing

Expand Down
115 changes: 87 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in
accordance with the terms of the Adobe license agreement accompanying
it. If you have received this file from a source other than Adobe,
then your use, modification, or distribution of it requires the prior
written permission of Adobe.
written permission of Adobe.
*/

'use strict';
Expand All @@ -18,52 +18,111 @@ const fs = require('fs');
*/
function memory() {
return {
containerUsage: async function() {
const rss = await readMetric('stat');
const kmemUsage = await readMetric('kmem.usage_in_bytes');
containerUsage: async () => {
const rss = await readMetric('memory/memory.stat');
const kmemUsage = await readMetric('memory/memory.kmem.usage_in_bytes');
if (rss !== null && kmemUsage !== null) {
return (rss + kmemUsage);
}
return null
},
containerUsagePercentage: async function(containerUsage=false) {
containerUsagePercentage: async (containerUsage=false) => {
if (!(containerUsage)) {
const rss = await readMetric('stat');
const kmemUsage = await readMetric('kmem.usage_in_bytes');
const rss = await readMetric('memory/memory.stat');
const kmemUsage = await readMetric('memory/memory.kmem.usage_in_bytes');
containerUsage = (rss !== null && kmemUsage !== null)? (rss + kmemUsage): null;
}
const limit = await readMetric('limit_in_bytes');
const limit = await readMetric('memory/memory.limit_in_bytes');
if (containerUsage !== null && limit !== null) {
return ((containerUsage) / limit);
}
return null
}
}
}
}

/**
* Reads metrics from `/sys/fs/cgroup/memory`
* @param {String} metric What metric to read from `/sys/fs/cgroup/memory`
* Reads metrics from `/sys/fs/cgroup/cpuacct`
* @returns Three asyncronous functions `usage`, `stat`, and `usage_percpu`
*/
function cpu() {
return {

// returns a value
usage: async () => {
return readMetric('cpuacct/cpuacct.usage');
},

// returns an object {user: <user-cpu-amount> , system: <system-cpu-amount>}
stat: async () => {
const stat = await readMetric('cpuacct/cpuacct.stat');
return stat
},

// returns a list of cpu usages by task
usage_percpu: async () => {
return readMetric('cpuacct/cpuacct.usage_percpu');
}

}
}

/**
* Calls cpu() and memory() functions to get all metrics at once
* @returns {Object} map of each metric to its result
*/
async function getAllMetrics() {

const memory_container_usage = await memory().containerUsage();
const memory_container_usage_perc = await memory().containerUsagePercentage(memory_container_usage);

const cpuacct_usage = await cpu().usage();
const cpuacct_stat = await cpu().stat();
const cpuacct_usage_percpu = await cpu().usage_percpu();

return {
memory: {
containerUsage: memory_container_usage,
containerUsagePercentage: memory_container_usage_perc,
},
cpuacct: {
usage: cpuacct_usage,
stat: cpuacct_stat,
usage_percpu: cpuacct_usage_percpu
}
}
}


/**
* Reads metrics from `/sys/fs/cgroup/`
* @param {String} metric What metric to read from `/sys/fs/cgroup/`
* @returns metric value
*/
function readMetric(metric) {
return new Promise((resolve, reject) => {
fs.readFile(`/sys/fs/cgroup/memory/memory.${metric}`, function (err, data) {
if (err) {
// Error reading '/sys/fs/cgroup/memory/memory.${metric}'
return reject(err);
}
else if (metric === 'stat') {
async function readMetric(metric) {
try {
const data = fs.readFileSync(`/sys/fs/cgroup/${metric}`)
if (metric === 'memory/memory.stat') {
// get rss
const rss = data.toString().split('\n')[1].split(' ')[1];
resolve(parseFloat(rss));
}
resolve(parseFloat(data.toString()));
})
}).catch(() => {
return null;
})

return(parseFloat(rss));
}
if (metric === 'cpuacct/cpuacct.stat') {
const user = data.toString().split('\n')[0].split(' ')[1];
const system = data.toString().split('\n')[1].split(' ')[1];
return {user: user, system: system};
}
if (metric === 'cpuacct/cpuacct.usage_percpu') {
return data.toString().trim().split(' ');
}
return(parseFloat(data.toString()));
} catch (e) {
throw Error(`Error reading file /sys/fs/cgroup/${metric}, Message: ${e.message || e}`)
}
}

module.exports = { memory }
module.exports = {
memory,
cpu,
getAllMetrics
}
Loading

0 comments on commit 5942ed2

Please sign in to comment.