From 21677bcca58e637fbd938ea0dcadd04739138f6d Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Wed, 17 Aug 2016 14:51:21 +0200 Subject: [PATCH] Added Docker support --- README.md | 41 ++++++- lib/index.js | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 3 files changed, 349 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0e17fc2..4a0b4b5 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,15 @@ si.cpu() ``` +## News and Changes + +### Latest Activity + +- Version 3.1.0: added [Docker][docker-url] support. Now you can scan your docker containers and get their stats +- Version 3.0.0: added DisksIO - overall diskIO and IOPS values for all mounted volumes + +Here all changes more detailed: + ### Major (breaking) Changes - Version 3 - works only with [node.js][nodejs-url] **v4.0.0** and above (using now internal ES6 promise function, arrow functions, ...) @@ -52,7 +61,10 @@ si.cpu() New Functions -- `disksIO`: returns overall diskIO and IOPS values for all mounted volumes +- `dockerContainers`: returns a list of all docker containers (new in version 3.1) +- `dockerContainerStats`: returns statistics for a specific docker container (new in version 3.1) +- `dockerAll`: returns a list of all docker containers including their stats (new in version 3.1) +- `disksIO`: returns overall diskIO and IOPS values for all mounted volumes (new in version 3.0) Bug Fixes @@ -137,7 +149,8 @@ This library is splitted in several sections: 8. Processes 9. Users 10. Internet -11. GetAll +11. Docker +12. GetAll ### Function Reference and OS Support @@ -258,6 +271,28 @@ This library is splitted in several sections: | - status | X | X | status code | | - ms | X | X | response time in ms | | si.inetLatency(host, cb) | X | X | response-time (ms) to external resource
host parameter is optional (default 8.8.8.8)| +| si.dockerContainers(all, cb) | X | X | returns array of active/all docker containers | +| - [0].id | X | X | ID of container | +| - [0].name | X | X | name of container | +| - [0].image | X | X | name of image | +| - [0].imageID | X | X | ID of image | +| - [0].command | X | X | command | +| - [0].created | X | X | creation time | +| - [0].state | X | X | created, running, exited | +| - [0].ports | X | X | array of ports | +| - [0].mounts | X | X | array of mounts | +| si.dockerContainerStats(id, cb) | X | X | statistics for a specific container | +| - id | X | X | Container ID | +| - mem_usage | X | X | memory usage in bytes | +| - mem_limit | X | X | memory limit (max mem) in bytes | +| - mem_percent | X | X | memory usage in percent | +| - cpu_percent | X | X | cpu usage in percent | +| - pids | X | X | number of processes | +| - netIO.rx | X | X | received bytes via network | +| - netIO.wx | X | X | sent bytes via network | +| - blockIO.r | X | X | bytes read from BlockIO | +| - blockIO.w | X | X | bytes written to BlockIO | +| si.dockerAll(cb) | X | X | list of all containers including their stats
in one single array | | si.getStaticData(cb) | X | X | all static data at once | | si.getDynamicData(srv,iface,cb) | X | X | all dynamic data at once | | si.getAllData(srv,iface,cb) | X | X | all data at once | @@ -319,6 +354,7 @@ I am happy to discuss any comments and suggestions. Please feel free to contact | Version | Date | Comment | | -------------- | -------------- | -------- | +| 3.1.0 | 2016-08-18 | added Docker stats | | 3.0.1 | 2016-08-17 | Bug-Fix disksIO, users, updated docs | | 3.0.0 | 2016-08-03 | new major version 3.0 | | 2.0.5 | 2016-03-02 | changed .gitignore | @@ -406,6 +442,7 @@ All other trademarks are the property of their respective owners. [npmjs-license]: https://img.shields.io/npm/l/systeminformation.svg?style=flat-square [nodejs-url]: https://nodejs.org/en/ +[docker-url]: https://www.docker.com/ [daviddm-img]: https://img.shields.io/david/sebhildebrandt/systeminformation.svg?style=flat-square [daviddm-url]: https://david-dm.org/sebhildebrandt/systeminformation diff --git a/lib/index.js b/lib/index.js index 9446938..560803d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -27,7 +27,8 @@ // 8. Processes // 9. Users/Sessions // 10. Internet -// 11. GetAll - get all data +// 11. Docker +// 12. GetAll - get all data // // ================================================================================== // @@ -78,6 +79,7 @@ // -------------------------------- // // version date comment +// 3.1.0 2016-08-18 added docker stats // 3.0.1 2016-08-17 Bug-Fix disksIO, users, updated docs // 3.0.0 2016-08-03 new major version 3.0 // 2.0.5 2016-02-22 some more tiny correction ... @@ -119,6 +121,7 @@ var _cpu_speed = '0.00'; var _fs_speed = {}; var _disk_io = {}; var _default_iface; +var _docker_container_stats = {}; const NOT_SUPPORTED = 'not supported'; @@ -338,6 +341,14 @@ function osInfo(callback) { exec("cat /etc/*-release", function (error, stdout) { if (!error) { + /** + * @namespace + * @property {string} DISTRIB_ID + * @property {string} NAME + * @property {string} DISTRIB_RELEASE + * @property {string} VERSION_ID + * @property {string} DISTRIB_CODENAME + */ var release = {}; var lines = stdout.toString().split('\n'); lines.forEach(function (line) { @@ -1929,7 +1940,295 @@ function inetLatency(host, callback) { exports.inetLatency = inetLatency; // ---------------------------------------------------------------------------------- -// 11. get all +// 11. Docker +// ---------------------------------------------------------------------------------- + +// -------------------------- +// get containers (parameter all: get also inactive/exited containers) + +function dockerContainers(all, callback) { + + function inContainers(containers, id) { + let filtered = containers.filter(obj => { + /** + * @namespace + * @property {string} Id + */ + return (obj.Id && (obj.Id == id)) + }); + return (filtered.length > 0); + } + // fallback - if only callback is given + if (isFunction(all) && !callback) { + callback = all; + all = false; + } + + all = all || false; + var result = []; + return new Promise((resolve, reject) => { + process.nextTick(() => { + if (_windows) { + let error = new Error(NOT_SUPPORTED); + if (callback) { callback(NOT_SUPPORTED) } + reject(error); + } + let cmd = "curl --unix-socket /var/run/docker.sock http:/containers/json" + (all ? "?all=1": ""); + exec(cmd, function (error, stdout) { + if (!error) { + try { + let jsonString = stdout.toString(); + var docker_containers = JSON.parse(jsonString); + // console.log(docker_containers) + if (docker_containers && Object.prototype.toString.call(docker_containers) === '[object Array]' && docker_containers.length > 0) { + docker_containers.forEach(function (element) { + /** + * @namespace + * @property {string} Id + * @property {string} Name + * @property {string} Image + * @property {string} ImageID + * @property {string} Command + * @property {number} Created + * @property {string} State + * @property {Array} Names + * @property {Array} Ports + * @property {Array} Mounts + */ + + if (element.Names && Object.prototype.toString.call(element.Names) === '[object Array]' && element.Names.length > 0) { + element.Name = element.Names[0].replace(/^\/|\/$/g, ''); + } + result.push({ + id: element.Id, + name: element.Name, + image: element.Image, + imageID: element.ImageID, + command: element.Command, + created: element.Created, + state: element.State, + ports: element.Ports, + mounts: element.Mounts, + // hostconfig: element.HostConfig, + // network: element.NetworkSettings + }) + }); + } + } catch (err) { + } + } + + // GC in _docker_container_stats + for (var key in _docker_container_stats) { + if (_docker_container_stats.hasOwnProperty(key)) { + if (!inContainers(docker_containers, key)) delete _docker_container_stats[key]; + } + } + if (callback) { callback(result) } + resolve(result); + }); + }); + }); +} + +exports.dockerContainers = dockerContainers; + +// -------------------------- +// helper functions for calculation of docker stats + +function docker_calcCPUPercent(cpu_stats, id) { + /** + * @namespace + * @property {object} cpu_usage + * @property {number} cpu_usage.total_usage + * @property {number} system_cpu_usage + * @property {object} cpu_usage + * @property {Array} cpu_usage.percpu_usage + */ + + var cpuPercent = 0.0; + // calculate the change for the cpu usage of the container in between readings + var cpuDelta = cpu_stats.cpu_usage.total_usage - (_docker_container_stats[id] && _docker_container_stats[id].prev_CPU ? _docker_container_stats[id].prev_CPU : 0); + // calculate the change for the entire system between readings + var systemDelta = cpu_stats.system_cpu_usage - (_docker_container_stats[id] && _docker_container_stats[id].prev_system ? _docker_container_stats[id].prev_system : 0); + + if (systemDelta > 0.0 && cpuDelta > 0.0) { + cpuPercent = (cpuDelta / systemDelta) * cpu_stats.cpu_usage.percpu_usage.length * 100.0; + } + if (!_docker_container_stats[id]) _docker_container_stats[id] = {}; + _docker_container_stats[id].prev_CPU = cpu_stats.cpu_usage.total_usage; + _docker_container_stats[id].prev_system = cpu_stats.system_cpu_usage; + + return cpuPercent +} + +function docker_calcNetworkIO(networks) { + var rx; + var tx; + for (var key in networks) { + // skip loop if the property is from prototype + if (!networks.hasOwnProperty(key)) continue; + + /** + * @namespace + * @property {number} rx_bytes + * @property {number} tx_bytes + */ + var obj = networks[key]; + rx =+ obj.rx_bytes; + tx =+ obj.tx_bytes; + } + return { + rx: rx, + tx: tx + } +} + +function docker_calcBlockIO(blkio_stats) { + let result = { + r: 0, + w: 0 + }; + + /** + * @namespace + * @property {Array} io_service_bytes_recursive + */ + if (blkio_stats && blkio_stats.io_service_bytes_recursive && Object.prototype.toString.call( blkio_stats.io_service_bytes_recursive ) === '[object Array]' && blkio_stats.io_service_bytes_recursive.length > 0) { + blkio_stats.io_service_bytes_recursive.forEach( function(element) { + /** + * @namespace + * @property {string} op + * @property {number} value + */ + + if (element.op && element.op.toLowerCase() == 'read' && element.value) { + result.r += element.value; + } + if (element.op && element.op.toLowerCase() == 'write' && element.value) { + result.w += element.value; + } + }) + } + return result; +} + +// -------------------------- +// container Stats (for one container) + +function dockerContainerStats(containerID, callback) { + containerID = containerID || ''; + var result = { + id: containerID, + mem_usage: 0, + mem_limit: 0, + mem_percent: 0, + cpu_percent: 0, + pids: 0, + netIO: { + rx: 0, + wx: 0 + }, + blockIO: { + r: 0, + w: 0 + } + }; + return new Promise((resolve, reject) => { + process.nextTick(() => { + if (_windows) { + let error = new Error(NOT_SUPPORTED); + if (callback) { callback(NOT_SUPPORTED) } + reject(error); + } + if (containerID) { + let cmd = "curl --unix-socket /var/run/docker.sock http:/containers/" + containerID + "/stats?stream=0"; + exec(cmd, function (error, stdout) { + if (!error) { + let jsonString = stdout.toString(); + try { + let stats = JSON.parse(jsonString); + /** + * @namespace + * @property {Object} memory_stats + * @property {number} memory_stats.usage + * @property {number} memory_stats.limit + * @property {Object} cpu_stats + * @property {Object} pids_stats + * @property {number} pids_stats.current + * @property {Object} networks + * @property {Object} blkio_stats + */ + + //console.log(stats); + if (!stats.message) { + result.mem_usage = (stats.memory_stats && stats.memory_stats.usage ? stats.memory_stats.usage : 0); + result.mem_limit = (stats.memory_stats && stats.memory_stats.limit ? stats.memory_stats.limit : 0); + result.mem_percent = (stats.memory_stats && stats.memory_stats.usage && stats.memory_stats.limit ? stats.memory_stats.usage / stats.memory_stats.limit * 100.0 : 0); + result.cpu_percent = (stats.cpu_stats ? docker_calcCPUPercent(stats.cpu_stats, containerID) : 0); + result.pids = (stats.pids_stats && stats.pids_stats.current ? stats.pids_stats.current : 0); + if (stats.networks) result.netIO = docker_calcNetworkIO(stats.networks); + if (stats.blkio_stats)result.blockIO = docker_calcBlockIO(stats.blkio_stats); + } + } catch (err) { + } + } + if (callback) { callback(result) } + resolve(result); + }); + } else { + if (callback) { callback(result) } + resolve(result); + } + }); + }); +} + +exports.dockerContainerStats = dockerContainerStats; + +function dockerAll(callback) { + return new Promise((resolve, reject) => { + process.nextTick(() => { + if (_windows) { + let error = new Error(NOT_SUPPORTED); + if (callback) { callback(NOT_SUPPORTED) } + reject(error); + } + dockerContainers(true).then(result => { + if (result && Object.prototype.toString.call( result ) === '[object Array]' && result.length > 0) { + var l = result.length; + result.forEach( function(element) { + dockerContainerStats(element.id).then(res => { + // include stats in array + element.mem_usage = res.mem_usage; + element.mem_limit = res.mem_limit; + element.mem_percent = res.mem_percent; + element.cpu_percent = res.cpu_percent; + element.pids = res.pids; + element.netIO = res.netIO; + element.blockIO = res.blockIO; + + // all done?? + l -= 1; + if (l == 0) { + if (callback) { callback(result) } + resolve(result); + } + }) + }) + } else { + if (callback) { callback(result) } + resolve(result); + } + }) + }); + }); +} + +exports.dockerAll = dockerAll; + +// ---------------------------------------------------------------------------------- +// 12. get all // ---------------------------------------------------------------------------------- // -------------------------- @@ -2023,6 +2322,13 @@ function getDynamicData(srv, iface, callback) { // get time data.time = time(); + + /** + * @namespace + * @property {Object} versions + * @property {string} versions.node + * @property {string} versions.v8 + */ data.node = process.versions.node; data.v8 = process.versions.v8; diff --git a/package.json b/package.json index bac503f..922793f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "network connections", "processes", "users", - "internet" + "internet", + "docker" ], "repository": { "type": "git",