├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── index.js ├── lib ├── cpu.js ├── disk.js ├── memory.js └── stats.js ├── optionsparser.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Visual Studio Code 40 | .vscode 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - npm install 8 | 9 | os: 10 | - linux 11 | - osx 12 | 13 | script: 14 | - npm test 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 sv-code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microstats 2 | 3 | [![NPM Version][npm-image]][npm-url] 4 | [![Linux Build][travis-image]][travis-url] 5 | 6 | > microstats is a node utility that can be used to monitor OS events such as CPU utilization, memory and disk consumption. It can be used to 'alert' the user when user defined thresholds are breached. Currently available for linux, macos and windows. 7 | 8 | ## Install 9 | 10 | ```bash 11 | npm install microstats 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```bash 17 | const microstats = require('microstats') 18 | 19 | // Event emits 20 | microstats.on('memory', function(value) { console.log('MEMORY:', memory } 21 | microstats.on('cpu', function(value) { console.log('CPU:', memory } 22 | microstats.on('disk', function(value) { console.log('DISK:', memory } 23 | 24 | let options = {} 25 | microstats.start(options, function(err) { 26 | if(err) console.log(err); 27 | }) 28 | 29 | ... 30 | ... 31 | microstats.stop(); 32 | ``` 33 | 34 | ## options 35 | 36 | 3 modes of operation - 37 | 38 | - 'once': Will check all stats, report current numbers and stop. 39 | 40 | ```bash 41 | let options = { frequency: 'once' } 42 | ``` 43 | 44 | - 'timer': Will check all stats periodically on a user defined timer and report each check. 45 | 46 | ```bash 47 | let options = { frequency: '5s' } 48 | ``` 49 | 50 | Values for frequency could be something like 51 | 52 | ```bash 53 | '5s' // 5 seconds 54 | '5m' // 5 minutes 55 | '5h' // 5 hours 56 | ``` 57 | 58 | - 'onalert': Will check all stats periodically, but report only when the numbers exceed user defined threshold. 59 | 60 | ```bash 61 | options = { 62 | frequency: 'onalert' 63 | memoryalert: { used: '>60%' } 64 | cpualert: { load: '>80%' } 65 | diskalert: { used: '>70%' } 66 | } 67 | ``` 68 | If 'onalert' is used without specifying the thresholds, a default threshold value of 50% will be used for all stats. 69 | 70 | ## diskalert 71 | 72 | diskalert options can be customized to specify the disks / mounts to be monitored. If no 'filesystem(s)' or 'mount(s)' are specified, all the available disks will be considered. 73 | 74 | - linux/macos example(s) 75 | ```bash 76 | options = { 77 | frequency: 'once' 78 | diskalert: { 79 | used: '>70%', 80 | mount: '/' 81 | } 82 | } 83 | 84 | options = { 85 | frequency: 'once' 86 | diskalert: { 87 | used: '>70%', 88 | mounts: ['/home', /dev'] 89 | } 90 | } 91 | ``` 92 | 93 | - windows example(s) 94 | ```bash 95 | options = { 96 | frequency: 'once' 97 | diskalert: { 98 | used: '>70%', 99 | filesystem: 'C:' 100 | } 101 | } 102 | 103 | options = { 104 | frequency: 'once' 105 | diskalert: { 106 | used: '>70%', 107 | filesystems: ['C:','D:'] 108 | } 109 | } 110 | ``` 111 | 112 | ## Sample output 113 | ```bash 114 | MEMORY: { usedpct: 55.33, total: 34359738368, free: 15349305344 } 115 | DISK: { filesystem: '/dev/disk0s2', 116 | mount: '/', 117 | usedpct: 40.64, 118 | total: 976265452, 119 | free: 579478940 } 120 | DISK: { filesystem: '/dev/disk1s2', 121 | mount: '/Volumes/MyPhotos', 122 | usedpct: 88.05, 123 | total: 246865880, 124 | free: 29491752 } 125 | CPU: { loadpct: 39.28, userpct: 10.71, syspct: 28.57, idlepct: 60.71 } 126 | ``` 127 | 128 | ## License 129 | 130 | [MIT](http://vjpr.mit-license.org) 131 | 132 | [npm-image]: https://img.shields.io/npm/v/microstats.svg 133 | [npm-url]: https://npmjs.org/package/microstats 134 | [travis-image]: https://travis-ci.org/sv-code/microstats.svg?branch=master 135 | [travis-url]: https://travis-ci.org/sv-code/microstats 136 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against this version of Node.js 2 | environment: 3 | nodejs_version: "0.10" 4 | 5 | # Install scripts. (runs after repo cloning) 6 | install: 7 | # Get the latest stable version of Node.js or io.js 8 | - ps: Install-Product node $env:nodejs_version 9 | # install modules 10 | - npm install 11 | 12 | # Post-install test scripts. 13 | test_script: 14 | # Output useful info for debugging. 15 | - node --version 16 | - npm --version 17 | # run tests 18 | - npm test 19 | 20 | # Don't actually build. 21 | build: off 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EventEmitter = require('events').EventEmitter 3 | , statEmitter = module.exports = new EventEmitter() 4 | , optionsparser = require('./optionsparser') 5 | , stats = require('./lib/stats'); 6 | 7 | var on = false; 8 | 9 | statEmitter.start = function(options, cb) { 10 | if(!isPlatformSupported()) { 11 | if(cb && typeof(cb) === 'function') return cb('Platform currently unsupported'); 12 | else return; 13 | } 14 | 15 | let frequency, memusedThreshold = 0, cpuloadThreshold = 0, diskusedThreshold = 0, diskfilesystems, mounts; 16 | try { 17 | frequency = optionsparser.getFrequency(options); 18 | 19 | if(options && frequency.mode === 'onalert') { 20 | memusedThreshold = optionsparser.getMemoryUsedAlertThreshold(options.memoryalert); 21 | cpuloadThreshold = optionsparser.getCpuLoadAlertThreshold(options.cpualert); 22 | diskusedThreshold = optionsparser.getDiskUsedAlertThreshold(options.diskalert); 23 | } 24 | 25 | if(options) { 26 | diskfilesystems = optionsparser.getDiskFilesystems(options.diskalert); 27 | mounts = optionsparser.getDiskMounts(options.diskalert); 28 | } 29 | 30 | //console.log(frequency, memusedThreshold, cpuloadThreshold, diskusedThreshold, diskfilesystems, mounts); 31 | } 32 | catch(err) { 33 | if(cb && typeof(cb) === 'function') return cb(err); 34 | else return; 35 | } 36 | 37 | on = true; 38 | var check = setInterval(function() { 39 | if(!on) { 40 | clearInterval(check); 41 | return; 42 | } 43 | 44 | stats.memory(statEmitter, { threshold: memusedThreshold }); 45 | stats.cpu(statEmitter, { threshold: cpuloadThreshold }); 46 | stats.disk(statEmitter, { threshold: diskusedThreshold, diskfilesystems: diskfilesystems, mounts: mounts }); 47 | 48 | if(frequency.mode === 'once') { 49 | clearInterval(check); 50 | } 51 | 52 | }, frequency.interval); 53 | 54 | if(cb && typeof(cb) === 'function') return cb(null); 55 | else return; 56 | } 57 | 58 | statEmitter.stop = function() { 59 | on = false; 60 | } 61 | 62 | function isPlatformSupported() { 63 | return process.env.platform !== 'other'; 64 | } 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /lib/cpu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const exec = require('child_process').exec; 3 | 4 | switch(process.env.platform) { 5 | case 'linux': 6 | module.exports = function(statEmitter, options) { 7 | let top = 'top -bn3 | grep Cpu'; 8 | exec(top, {stdio:['inherit','pipe','pipe']}, function(err, output, code) { 9 | if(err) return; 10 | 11 | let lines = output.split('\n'); 12 | lines.forEach(function(line) { 13 | if(line.includes('Cpu')) { 14 | line = line.replace(/\s+/g, ' '); 15 | //console.log('line',line); 16 | let segments = line.split(','); 17 | let userpct = parseFloat(sanitizeLinuxCpuInfo(segments[0].split(' ')[1])); 18 | let syspct = parseFloat(sanitizeLinuxCpuInfo(segments[1].split(' ')[1])); 19 | let idlepct = parseFloat(sanitizeLinuxCpuInfo(segments[3].split(' ')[1])); 20 | let loadpct = Number((userpct + syspct).toFixed(2)); 21 | 22 | //console.log('userpct', userpct, 'syspct', syspct, 'loadpct', loadpct); 23 | 24 | if(!options.threshold || options.threshold === 0 || loadpct > options.threshold) { 25 | statEmitter.emit('cpu', { loadpct: loadpct, userpct: userpct, syspct: syspct, idlepct: idlepct }); 26 | } 27 | } 28 | }); 29 | 30 | }); 31 | } 32 | break; 33 | 34 | case 'macos': 35 | module.exports = function(statEmitter, options) { 36 | let top = 'top -l1 | grep "CPU usage"'; 37 | exec(top, {stdio:['inherit','pipe','pipe']}, function(err, output, code) { 38 | if(err) return; 39 | 40 | let segments = output.split(','); 41 | let userpct = Number(parseFloat(segments[0].split(' ')[2].split('%')[0]).toFixed(2)); 42 | let syspct = Number(parseFloat(segments[1].split(' ')[1].split('%')[0]).toFixed(2)); 43 | let idlepct = Number(parseFloat(segments[2].split(' ')[1].split('%')[0]).toFixed(2)); 44 | let loadpct = Number((userpct + syspct).toFixed(2)); 45 | 46 | if(!options.threshold || options.threshold === 0 || loadpct > options.threshold) { 47 | statEmitter.emit('cpu', { loadpct: loadpct, userpct: userpct, syspct: syspct, idlepct: idlepct }); 48 | } 49 | }); 50 | } 51 | break; 52 | 53 | case 'win': 54 | module.exports = function(statEmitter, options) { 55 | let wmic = 'wmic cpu get loadpercentage /value | find "Load"'; 56 | exec(wmic, function(err, output, code) { 57 | if(err) return; 58 | 59 | let loadpct = parseFloat(output.split('=')[1].split('\r\r\n')[0]); 60 | 61 | if(!isNaN(loadpct) && (!options.threshold || options.threshold === 0 || loadpct > options.threshold)) { 62 | statEmitter.emit('cpu', { loadpct: loadpct }); 63 | } 64 | }); 65 | } 66 | break; 67 | 68 | default: 69 | //throw('Unsupported platform'); 70 | 71 | } 72 | 73 | function sanitizeLinuxCpuInfo(value) { 74 | //console.log('value', value); 75 | if(value && value.includes('%')) { 76 | return value.split('%')[0]; 77 | } 78 | 79 | return value; 80 | } -------------------------------------------------------------------------------- /lib/disk.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const exec = require('child_process').exec; 3 | 4 | switch(process.env.platform) { 5 | case 'linux': 6 | case 'macos': 7 | module.exports = function(statEmitter, options) { 8 | let df = "df -Pkl | grep -v Capacity | awk '{ print $1\" \"$2\" \"$3\" \"$4\" \"$5\" \"$6\" \"$7}'"; 9 | exec(df, {stdio:['inherit','pipe','pipe']}, function(err, output, code) { 10 | if(err) return; 11 | 12 | let drives = output.split('\n'); 13 | drives.forEach(function(drive) { 14 | let drivedetails = drive.split(' '); 15 | let filesystem = drivedetails[0], total, free, mount; 16 | if(!filesystem) return; 17 | if(options.diskfilesystems && options.diskfilesystems.indexOf(filesystem) < 0) return; 18 | 19 | if(isNaN(drivedetails[1])) { 20 | total = parseInt(drivedetails[2]); 21 | free = parseInt(drivedetails[4]); 22 | mount = drivedetails[6]; 23 | } 24 | else { 25 | total = parseInt(drivedetails[1]); 26 | free = parseInt(drivedetails[3]); 27 | mount = drivedetails[5]; 28 | } 29 | if(options.mounts && options.mounts.indexOf(mount) < 0) return; 30 | let usedpct = Number(parseFloat((total - free) / total * 100).toFixed(2)); 31 | 32 | if(!options.threshold || options.threshold === 0 || usedpct > options.threshold) { 33 | statEmitter.emit('disk', { filesystem: filesystem, mount: mount, usedpct: usedpct, total: total, free: free }); 34 | } 35 | }); 36 | 37 | }); 38 | } 39 | break; 40 | 41 | case 'win': 42 | module.exports = function(statEmitter, options) { 43 | let wmic = 'wmic LOGICALDISK GET Name, Size, FreeSpace /value | findstr "FreeSpace Name Size"'; 44 | exec(wmic, function(err, output, code) { 45 | if(err) return; 46 | 47 | let segments = output.split('\n'); 48 | for(let i=0; i options.threshold) { 61 | statEmitter.emit('disk', { filesystem: filesystem, usedpct: usedpct, total: total, free: free }); 62 | } 63 | } 64 | } 65 | 66 | }); 67 | } 68 | break; 69 | 70 | default: 71 | //throw('Unsupported platform'); 72 | } 73 | -------------------------------------------------------------------------------- /lib/memory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const os = require('os'); 3 | 4 | module.exports = function(statEmitter, options) { 5 | let total = parseInt(os.totalmem()); 6 | let free = parseInt(os.freemem()); 7 | let usedpct = Number(parseFloat(((total - free) / total * 100)).toFixed(2)); 8 | 9 | if(!options.threshold || options.threshold === 0 || usedpct > options.threshold) { 10 | statEmitter.emit('memory', { usedpct: usedpct, total: total, free: free }); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const platform = process.platform; 3 | 4 | if(platform === 'linux') process.env.platform = 'linux'; 5 | else if(platform === 'darwin') process.env.platform = 'macos'; 6 | else if(platform === 'win32') process.env.platform = 'win'; 7 | else process.env.platform = 'other'; 8 | 9 | module.exports.memory = require('./memory'); 10 | module.exports.cpu = require('./cpu'); 11 | module.exports.disk = require('./disk'); 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /optionsparser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const util = require('util'); 3 | 4 | // frequency: '2s' 5 | exports.getFrequency = function(options) { 6 | let f = {}; 7 | 8 | if(!options || !options.frequency) { 9 | f.mode = 'once'; 10 | f.interval = 2000; // default 2s 11 | return f; 12 | } 13 | 14 | f.mode = options.frequency; 15 | if(options.frequency === 'once') { 16 | f.interval = 0; 17 | return f; 18 | } 19 | else if(options.frequency === 'onalert') { 20 | f.interval = 2000; // check for alert condition every 2 seconds 21 | return f; 22 | } 23 | else if(options.frequency.length !== 2 || isNaN(options.frequency[0])) { 24 | throw "Invalid frequency. Try something like 'once', 'onalert', '2m' or '1h'"; 25 | } 26 | 27 | f.mode = 'time'; 28 | let n = parseInt(options.frequency[0]); 29 | let s = options.frequency[1]; 30 | 31 | switch(s) { 32 | case 's': f.interval = n * 1000; break; 33 | case 'm': f.interval = n * 1000 * 60; break; 34 | case 'h': f.interval = n * 1000 * 60 * 60; break; 35 | default: f.interval = 2000; 36 | } 37 | 38 | return f; 39 | } 40 | 41 | // memory: used: '>80%' 42 | exports.getMemoryUsedAlertThreshold = function(memoptions) { 43 | if(!memoptions || !memoptions.used) return 50; // default 50% 44 | let u = memoptions.used; 45 | if(u.length < 3 || !u.includes('>') || !u.includes('%')) { 46 | throw "Invalid 'used' memory option. Try something like used: '>80%'"; 47 | } 48 | 49 | return parseInt(u.split('>')[1].split('%')[0]); 50 | } 51 | 52 | // cpu: load: '>80%' 53 | exports.getCpuLoadAlertThreshold = function(cpuoptions) { 54 | if(!cpuoptions || !cpuoptions.load) return 10; // default 50% 55 | let u = cpuoptions.load; 56 | if(u.length < 3 || !u.includes('>') || !u.includes('%')) { 57 | throw "Invalid 'load' cpu option. Try something like used: '>80%'"; 58 | } 59 | 60 | return parseInt(u.split('>')[1].split('%')[0]); 61 | } 62 | 63 | // disk: filesystem/filesystems: '/dev/disk01' 64 | // disk: filesystem/filesystems: ['/dev/disk01', '/dev/disk02'] 65 | exports.getDiskFilesystems = function(diskoptions) { 66 | let filesystems = []; 67 | if(!diskoptions || (!diskoptions.filesystem && !diskoptions.filesystems)) return null; 68 | let u = diskoptions.filesystem || diskoptions.filesystems; 69 | if(util.isArray(u)) filesystems = u; 70 | else filesystems.push(u); 71 | return filesystems; 72 | } 73 | 74 | // disk: mount/mounts: '/' 75 | // disk: mount/mounts: ['/', '/home/sv'] 76 | exports.getDiskMounts = function(diskoptions) { 77 | let mounts = []; 78 | if(!diskoptions || (!diskoptions.mount && !diskoptions.mounts)) return null; 79 | let u = diskoptions.mount || diskoptions.mounts; 80 | if(util.isArray(u)) mounts = u; 81 | else mounts.push(u); 82 | return mounts; 83 | } 84 | 85 | // disk: used: '>80%' 86 | exports.getDiskUsedAlertThreshold = function(diskoptions) { 87 | if(!diskoptions || !diskoptions.used) return 50; // default 50% 88 | let u = diskoptions.used; 89 | if(u.length < 3 || !u.includes('>') || !u.includes('%')) { 90 | throw "Invalid 'used' disk option. Try something like used: '>80%'"; 91 | } 92 | 93 | return parseInt(u.split('>')[1].split('%')[0]); 94 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microstats", 3 | "version": "0.1.1", 4 | "description": "A node module that monitors Memory, CPU and Disk utilization and alerts the caller through events, either periodically or when a user defined threshold is breached. Currently available for linux, macOS and windows. ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sv-code/microstats.git" 12 | }, 13 | "keywords": [ 14 | "CPU", 15 | "memory", 16 | "disk", 17 | "usage", 18 | "alerts" 19 | ], 20 | "author": "Srivathsan Venkat ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/sv-code/microstats/issues" 24 | }, 25 | "homepage": "https://github.com/sv-code/microstats#readme", 26 | "devDependencies": { 27 | "mocha": "^3.2.0", 28 | "morgan": "^1.7.0", 29 | "sinon": "^1.17.7" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var microstats = require('.'); 3 | 4 | microstats.on('memory', function(value) { 5 | console.log('MEMORY:', value); 6 | }); 7 | 8 | microstats.on('cpu', function(value) { 9 | console.log('CPU:', value) 10 | }); 11 | 12 | microstats.on('disk', function(value) { 13 | console.log('DISK:', value) 14 | }); 15 | 16 | var options1 = { frequency: 'once' } 17 | var options2 = { frequency: '5s' } 18 | var options3 = { frequency: 'onalert' } 19 | var options4 = { 20 | frequency: 'onalert', 21 | memoryalert: { used: '>15%' }, 22 | cpualert: { load: '>30%' }, 23 | diskalert: { //filesystem: 'C:', //filesystems: ['/dev/disk1', '/dev/disk0s4'], 24 | //mount: '/', //mounts: ['/'], 25 | used: '>10%' 26 | } 27 | }; 28 | 29 | var optionsArray = [ options1, options2, options3, options4 ]; 30 | optionsArray.forEach(function(options) { 31 | console.log('---Testing options:', options,'---') 32 | microstats.start(options, function(err) { 33 | if(err) console.log(err); 34 | }); 35 | 36 | setTimeout(function(){ 37 | microstats.stop(); 38 | }, 7000); 39 | }); 40 | 41 | --------------------------------------------------------------------------------