Comparing version 1.2.0 to 2.0.0
@@ -0,1 +1,15 @@ | ||
### 2.0 | ||
There is no BC break but because the code got refactored a lot and that the default retrieval method has changed we're bumping a major version. | ||
- allow multiple pids | ||
- remove `advanced` option | ||
- `start` result is now a `Date` instance | ||
- don't use `/proc` files anymore but use `ps` instead | ||
- more tests | ||
### 1.2.0 | ||
Introduce `advanced` option to get time, and start | ||
### 1.1.0 | ||
@@ -2,0 +16,0 @@ |
43
index.js
var os = require('os') | ||
var stats = require('./lib/stats') | ||
var platform = require('./lib/platform') | ||
var history = {} | ||
var wrapper = function (statType) { | ||
var wrapper = function (method) { | ||
return function (pid, options, cb) { | ||
@@ -11,33 +13,24 @@ if (typeof options === 'function') { | ||
return stats[statType](pid, options, cb) | ||
if (method === platform.UNSUPPORTED) { | ||
return cb(new Error(os.platform() + ' is not supported yet, please open an issue (https://github.com/soyuka/pidusage)'), null) | ||
} | ||
options.history = history | ||
return stats[method](pid, options, cb) | ||
} | ||
} | ||
var pusage = { | ||
darwin: wrapper('ps'), | ||
sunos: wrapper('ps'), | ||
freebsd: wrapper('ps'), | ||
netbsd: wrapper('proc'), | ||
win: wrapper('win'), | ||
linux: wrapper('proc'), | ||
aix: wrapper('ps'), | ||
unsupported: function (pid, options, cb) { | ||
cb = typeof options === 'function' ? options : cb | ||
exports.stat = wrapper(platform) | ||
cb(new Error(os.platform() + ' is not supported yet, please fire an issue (https://github.com/soyuka/pidusage)')) | ||
exports.unmonitor = function (pid) { | ||
if (!pid) { | ||
for (var i in history) { | ||
delete history[i] | ||
} | ||
return | ||
} | ||
} | ||
var platform = os.platform() | ||
platform = platform.match(/^win/) ? 'win' : platform // nor is windows a winner... | ||
platform = pusage[platform] ? platform : 'unsupported' | ||
exports.stat = function () { | ||
pusage[platform].apply(stats, [].slice.call(arguments)) | ||
delete history[pid] | ||
} | ||
exports.unmonitor = function (pid) { | ||
delete stats.history[pid] | ||
} | ||
exports._history = stats.history | ||
exports._history = history |
244
lib/stats.js
@@ -1,232 +0,36 @@ | ||
var os = require('os') | ||
var fs = require('fs') | ||
var p = require('path') | ||
var exec = require('child_process').exec | ||
var spawn = require('child_process').spawn | ||
var helpers = require('./helpers') | ||
var format = require('util').format | ||
var PLATFORM = os.platform() | ||
// Statistics processor files | ||
var wmic = require('./wmic') | ||
var ps = require('./ps') | ||
var procfile = require('./procfile') | ||
var stats = { | ||
history: {}, | ||
cpu: null, // used to store cpu informations | ||
proc: function (pid, options, done) { | ||
var self = this | ||
/** | ||
* Just a callback wrapper to keep backward compatibility | ||
*/ | ||
function callback (err, statistics, options, done) { | ||
if (err) return done(err, null) | ||
if (this.cpu !== null) { | ||
fs.readFile('/proc/uptime', 'utf8', function (err, uptime) { | ||
if (err) { | ||
return done(err, null) | ||
} | ||
// BC | ||
if (statistics.length === 1) { | ||
return done(null, statistics[0]) | ||
} | ||
if (uptime === undefined) { | ||
console.error("[pidusage] We couldn't find uptime from /proc/uptime") | ||
self.cpu.uptime = os.uptime() | ||
} else { | ||
self.cpu.uptime = uptime.split(' ')[0] | ||
} | ||
return done(null, statistics) | ||
} | ||
return self.proc_calc(pid, options, done) | ||
}) | ||
} else { | ||
helpers.cpu(function (err, cpu) { | ||
if (err) { | ||
return done(err, null) | ||
} | ||
self.cpu = cpu | ||
return self.proc_calc(pid, options, done) | ||
}) | ||
} | ||
}, | ||
proc_calc: function (pid, options, done) { | ||
pid = parseInt(pid, 10) | ||
var history = this.history[pid] ? this.history[pid] : {} | ||
var cpu = this.cpu | ||
var self = this | ||
// Arguments to path.join must be strings | ||
fs.readFile(p.join('/proc', '' + pid, 'stat'), 'utf8', function (err, infos) { | ||
if (err) { | ||
return done(err, null) | ||
} | ||
// https://github.com/arunoda/node-usage/commit/a6ca74ecb8dd452c3c00ed2bde93294d7bb75aa8 | ||
// preventing process space in name by removing values before last ) (pid (name) ...) | ||
var index = infos.lastIndexOf(')') | ||
infos = infos.substr(index + 2).split(' ') | ||
// according to http://man7.org/linux/man-pages/man5/proc.5.html (index 0 based - 2) | ||
// In kernels before Linux 2.6, start was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks | ||
var stat = { | ||
utime: parseFloat(infos[11]), | ||
stime: parseFloat(infos[12]), | ||
cutime: parseFloat(infos[13]), | ||
cstime: parseFloat(infos[14]), | ||
start: parseFloat(infos[19]) / cpu.clockTick, | ||
rss: parseFloat(infos[21]) | ||
} | ||
// http://stackoverflow.com/questions/16726779/total-cpu-usage-of-an-application-from-proc-pid-stat/16736599#16736599 | ||
var childrens = options.childrens ? stat.cutime + stat.cstime : 0 | ||
var total = stat.stime - (history.stime || 0) + stat.utime - (history.utime || 0) + childrens | ||
total = total / cpu.clockTick | ||
// time elapsed between calls | ||
var seconds = history.uptime !== undefined ? cpu.uptime - history.uptime : stat.start - cpu.uptime | ||
seconds = Math.abs(seconds) | ||
seconds = seconds === 0 ? 1 : seconds // we sure can't divide through 0 | ||
self.history[pid] = stat | ||
self.history[pid].uptime = cpu.uptime | ||
var cpuPercent = (total / seconds) * 100 | ||
var memory = stat.rss * cpu.pagesize | ||
if (!options.advanced) { | ||
return done(null, { | ||
cpu: cpuPercent, | ||
memory: memory | ||
}) | ||
} | ||
return done(null, { | ||
cpu: cpuPercent, | ||
memory: memory, | ||
time: stat.utime + stat.stime, | ||
start: stat.start | ||
}) | ||
module.exports = { | ||
procfile: function (pid, options, done) { | ||
procfile(pid, options, function (err, statistics) { | ||
callback(err, statistics, options, done) | ||
}) | ||
}, | ||
/** | ||
* Get pid informations through ps command | ||
* @param {int} pid | ||
* @return {Function} done (err, stat) | ||
* on os x skip headers with pcpu=,rss= | ||
* on linux it could be --no-header | ||
* on solaris 11 can't figure out a way to do this properly so... | ||
*/ | ||
ps: function (pid, options, done) { | ||
pid = parseInt(pid, 10) | ||
var cmd = 'ps -o pcpu,rss -p ' | ||
if (options.advanced) { | ||
cmd = 'ps -o pcpu,rss,time,start -p ' | ||
if (PLATFORM === 'aix') { | ||
cmd = 'ps -o pcpu,rssize,time,start -p ' | ||
} | ||
} else if (PLATFORM === 'aix') { | ||
cmd = 'ps -o pcpu,rssize -p ' // this one could work on other platforms see AIX section in man ps | ||
} | ||
exec(cmd + pid, function (err, stdout, stderr) { | ||
if (err) { | ||
return done(err, null) | ||
} | ||
stdout = stdout.split(os.EOL)[1] | ||
stdout = stdout.replace(/^\s+/, '').replace(/\s\s+/g, ' ').split(' ') | ||
var cpuPercent = parseFloat(stdout[0].replace(',', '.')) | ||
var memory = parseFloat(stdout[1]) * 1024 | ||
if (!options.advanced) { | ||
return done(null, { | ||
cpu: cpuPercent, | ||
memory: memory | ||
}) | ||
} | ||
return done(null, { | ||
cpu: cpuPercent, | ||
memory: memory, | ||
time: parseFloat(stdout[2]), | ||
start: parseFloat(stdout[3]) | ||
}) | ||
ps(pid, options, function (err, statistics) { | ||
callback(err, statistics, options, done) | ||
}) | ||
}, | ||
/** | ||
* This is really in a beta stage | ||
*/ | ||
win: function (pid, options, done) { | ||
pid = parseInt(pid, 10) | ||
var history = this.history[pid] ? this.history[pid] : {} | ||
// http://social.msdn.microsoft.com/Forums/en-US/469ec6b7-4727-4773-9dc7-6e3de40e87b8/cpu-usage-in-for-each-active-process-how-is-this-best-determined-and-implemented-in-an?forum=csharplanguage | ||
var args = 'PROCESS ' + pid + ' get workingsetsize,usermodetime,kernelmodetime' | ||
var wmic = spawn('wmic', args.split(' '), {detached: true}) | ||
var stdout = '' | ||
var stderr = '' | ||
var self = this | ||
// Note: On Windows the returned value includes fractions of a second. Use Math.floor() to get whole seconds. | ||
var uptime = Math.floor(os.uptime()) | ||
wmic.stdout.on('data', function (d) { | ||
stdout += d.toString() | ||
wmic: function (pid, options, done) { | ||
wmic(pid, options, function (err, statistics) { | ||
callback(err, statistics, options, done) | ||
}) | ||
wmic.stderr.on('data', function (d) { | ||
stderr += d.toString() | ||
}) | ||
wmic.on('error', function (err) { | ||
console.error('[pidusage] Command "wmic ' + args + '" failed with error %s', err) | ||
}) | ||
wmic.on('close', function (code) { | ||
stdout = stdout.trim() | ||
stderr = stderr.trim() | ||
if (!stdout || code !== 0) { | ||
var error = format('%s Wmic errored, please open an issue on https://github.com/soyuka/pidusage with this message.%s', new Date().toString(), os.EOL) | ||
error += format('Command was "wmic %s" %s System informations: %s - release: %s %s - type %s %s', args, os.EOL, os.EOL, os.release(), os.EOL, os.type(), os.EOL) | ||
stderr = error + (stderr ? format('Wmic reported the following error: %s.', stderr) : 'Wmic reported no errors (stderr empty).') | ||
stderr = format('%s%s%sWmic exited with code %d.', os.EOL, stderr, os.EOL, code) | ||
stderr = format('%s%sStdout was %s', stderr, os.EOL, stdout || 'empty') | ||
return done(new Error(stderr, null)) | ||
} | ||
stdout = stdout.split(os.EOL)[1].replace(/\s\s+/g, ' ').split(' ') | ||
var stats = { | ||
kernelmodetime: parseFloat(stdout[0]), | ||
usermodetime: parseFloat(stdout[1]) | ||
} | ||
var workingsetsize = parseFloat(stdout[2]) | ||
// process usage since last call | ||
var total = stats.kernelmodetime - (history.kernelmodetime || 0) + stats.usermodetime - (history.usermodetime || 0) | ||
total = total / 10000000 | ||
// time elapsed between calls | ||
var seconds = history.uptime !== undefined ? uptime - history.uptime : 0 | ||
var cpu = 0 | ||
if (seconds > 0) { | ||
cpu = (total / seconds) * 100 | ||
} | ||
self.history[pid] = stats | ||
self.history[pid].uptime = uptime | ||
if (!options.advanced) { | ||
return done(null, {cpu: cpu, memory: workingsetsize}) | ||
} | ||
return done(null, { | ||
cpu: cpu, | ||
memory: workingsetsize, | ||
time: stats.usermodetime + stats.kernelmodetime, | ||
start: seconds | ||
}) | ||
}) | ||
wmic.stdin.end() | ||
} | ||
} | ||
module.exports = stats |
{ | ||
"name": "pidusage", | ||
"version": "1.2.0", | ||
"version": "2.0.0", | ||
"description": "Cross-platform process cpu % and memory usage of a PID — Edit", | ||
@@ -8,6 +8,9 @@ "main": "index.js", | ||
"devDependencies": { | ||
"chai": "~3.4.1", | ||
"mocha": "~2.3.4", | ||
"faucet": "0.0.1", | ||
"mockery": "1.4.0", | ||
"standard": "^10.0.3" | ||
"nanobench": "^2.1.1", | ||
"standard": "^10.0.3", | ||
"string-to-stream": "^1.1.0", | ||
"tape": "^4.9.0", | ||
"through": "^2.3.8" | ||
}, | ||
@@ -18,3 +21,3 @@ "engines": { | ||
"scripts": { | ||
"test": "standard index.js lib/*.js test/*.js && mocha test/test.js --reporter min && node test/stresstest.js" | ||
"test": "standard index.js lib/*.js test/*.js && node test/test.js | faucet" | ||
}, | ||
@@ -21,0 +24,0 @@ "repository": { |
@@ -47,3 +47,3 @@ pidusage | ||
```javascript | ||
pusage(process.pid, {advanced: true}, function (err, stat) { | ||
pusage.stat(process.pid, {advanced: true}, function (err, stat) { | ||
console.log(stat.time, stat.start) | ||
@@ -58,4 +58,4 @@ }) | ||
- `memory` memory bytes | ||
- `time` user + system time | ||
- `start` time process was started | ||
- `time` total cpu time in miliseconds | ||
- `start` Date when process was started | ||
``` | ||
@@ -106,4 +106,8 @@ | ||
#### pidusage-promise | ||
Need promise? Use [`pidusage-promise`](https://github.com/soyuka/pidusage-promise)! | ||
## Licence | ||
MIT |
201
test/test.js
@@ -1,163 +0,70 @@ | ||
/* global beforeEach, afterEach, it, describe */ | ||
var mockery = require('mockery') | ||
var expect = require('chai').expect | ||
var os = require('os') | ||
var path = require('path') | ||
var test = require('tape') | ||
var fork = require('child_process').fork | ||
var platform = require('../lib/platform') | ||
var pidusage = require('../') | ||
// classic "drop somewhere"... yeah I'm a lazy guy | ||
var formatBytes = function (bytes, precision) { | ||
var kilobyte = 1024 | ||
var megabyte = kilobyte * 1024 | ||
var gigabyte = megabyte * 1024 | ||
var terabyte = gigabyte * 1024 | ||
test('integration', function (t) { | ||
t.plan(5) | ||
if ((bytes >= 0) && (bytes < kilobyte)) { | ||
return bytes + ' B ' | ||
} else if ((bytes >= kilobyte) && (bytes < megabyte)) { | ||
return (bytes / kilobyte).toFixed(precision) + ' KB ' | ||
} else if ((bytes >= megabyte) && (bytes < gigabyte)) { | ||
return (bytes / megabyte).toFixed(precision) + ' MB ' | ||
} else if ((bytes >= gigabyte) && (bytes < terabyte)) { | ||
return (bytes / gigabyte).toFixed(precision) + ' GB ' | ||
} else if (bytes >= terabyte) { | ||
return (bytes / terabyte).toFixed(precision) + ' TB ' | ||
} else { | ||
return bytes + ' B ' | ||
} | ||
} | ||
t.equal(typeof pidusage.stat, 'function') | ||
t.equal(typeof pidusage.unmonitor, 'function') | ||
describe('pid usage', function () { | ||
this.timeout(10000) | ||
beforeEach(function () { | ||
mockery.enable({ | ||
warnOnReplace: false, | ||
warnOnUnregistered: false, | ||
useCleanCache: true | ||
}) | ||
pidusage.stat(process.pid, function (err, stats) { | ||
t.comment('Pid: ' + process.pid + '. Platform: ' + os.platform()) | ||
console.log(stats) | ||
t.error(err) | ||
t.deepEqual(Object.keys(stats).sort(), ['cpu', 'memory', 'pid', 'start', 'time']) | ||
t.ok(typeof stats.start, 'Date') | ||
}) | ||
}) | ||
afterEach(function () { | ||
mockery.deregisterAll() | ||
mockery.disable() | ||
}) | ||
test('unmonitor with pid (only wmic and procfile)', function (t) { | ||
if (platform === 'ps' || platform === platform.UNSUPPORTED) { | ||
t.end() | ||
return | ||
} | ||
it('should get pid usage', function (cb) { | ||
var pusage = require('../').stat | ||
pusage(process.pid, function (err, stat) { | ||
expect(err).to.equal(null) | ||
expect(stat).to.be.an('object') | ||
expect(stat).to.have.property('cpu') | ||
expect(stat).to.have.property('memory') | ||
t.plan(3) | ||
console.log('Pcpu: %s', stat.cpu) | ||
console.log('Mem: %s', formatBytes(stat.memory)) | ||
cb() | ||
}) | ||
pidusage.stat(process.pid, {advanced: true}, function (err, stats) { | ||
t.error(err) | ||
t.ok(pidusage._history[process.pid]) | ||
pidusage.unmonitor(process.pid) | ||
t.notOk(pidusage._history[process.pid]) | ||
}) | ||
}) | ||
it('should get advanced pid usage', function (cb) { | ||
var pusage = require('../').stat | ||
pusage(process.pid, {advanced: true}, function (err, stat) { | ||
expect(err).to.equal(null) | ||
expect(stat).to.be.an('object') | ||
expect(stat).to.have.property('cpu') | ||
expect(stat).to.have.property('memory') | ||
expect(stat).to.have.property('time') | ||
expect(stat).to.have.property('start') | ||
test('unmonitor without pid (only wmic and procfile)', function (t) { | ||
if (platform === 'ps' || platform === platform.UNSUPPORTED) { | ||
t.end() | ||
return | ||
} | ||
console.log('Pcpu: %s', stat.cpu) | ||
console.log('Mem: %s', formatBytes(stat.memory)) | ||
t.plan(3) | ||
cb() | ||
}) | ||
pidusage.stat(process.pid, {advanced: true}, function (err, stats) { | ||
t.error(err) | ||
t.ok(pidusage._history[process.pid]) | ||
pidusage.unmonitor() | ||
t.notOk(pidusage._history[process.pid]) | ||
}) | ||
}) | ||
it('should get pid usage multiple time', function (cb) { | ||
var pusage = require('../').stat | ||
var num = 0 | ||
var interval | ||
test('integration mutliple pids', function (t) { | ||
t.plan(3) | ||
var p = fork(path.join(__dirname, './fixtures/http.js')) | ||
var pids = [process.pid, p.pid] | ||
function launch () { | ||
pusage(process.pid, function (err, stat) { | ||
expect(err).to.equal(null) | ||
expect(stat).to.be.an('object') | ||
expect(stat).to.have.property('cpu') | ||
expect(stat).to.have.property('memory') | ||
console.log('Pcpu: %s', stat.cpu) | ||
console.log('Mem: %s', formatBytes(stat.memory)) | ||
if (++num === 5) { | ||
clearInterval(interval) | ||
cb() | ||
} else { | ||
setTimeout(launch, 100) | ||
} | ||
}) | ||
} | ||
interval = setTimeout(launch, 1000) | ||
pidusage.stat(pids, function (err, stats) { | ||
t.error(err) | ||
t.equal(stats.length, 2) | ||
t.deepEqual(Object.keys(stats[1]).sort(), ['cpu', 'memory', 'pid', 'start', 'time']) | ||
p.kill() | ||
}) | ||
}) | ||
it('should calculate correct cpu when user space time is zero (kernel module)', function (cb) { | ||
// force platform to linux to test this case | ||
var os = require('os') | ||
os.platform = function () { | ||
return 'linux' | ||
} | ||
// override readFile to simulate the system time only (kernel module) case | ||
var fs = require('fs') | ||
var clockTick = 100 | ||
fs.readFile = function (path, encoding, callback) { | ||
if (path === '/proc/uptime') { | ||
callback(null, '0 0') | ||
} else { // proc/<pid>/stat | ||
var infos = '0 (test)' | ||
for (var i = 0; i < 22; i++) { | ||
if (i === 12) { | ||
infos += ' ' + currentStime | ||
} else { | ||
infos += ' 0' | ||
} | ||
} | ||
callback(null, infos) | ||
} | ||
} | ||
var helpers = require('../lib/helpers') | ||
helpers.cpu = function (next) { | ||
next(null, { | ||
clockTick: clockTick, | ||
uptime: clockTick, | ||
pagesize: 4096 | ||
}) | ||
} | ||
// mock out to simulate kernel module and linux platform | ||
mockery.registerMock('fs', fs) | ||
mockery.registerMock('os', os) | ||
mockery.registerMock('./helpers.js', helpers) | ||
var pusage = require('..') | ||
// set the previous history as if kernel module usage had been called before | ||
var kernelModulePid = 0 | ||
var currentStime = 10000 * clockTick | ||
var previousStime = 2000 * clockTick | ||
pusage._history[kernelModulePid] = {} | ||
pusage._history[kernelModulePid].uptime = 0 | ||
pusage._history[kernelModulePid].utime = 0 | ||
pusage._history[kernelModulePid].stime = previousStime | ||
pusage.stat(kernelModulePid, function (err, stat) { | ||
if (err) { | ||
return cb(err) | ||
} | ||
expect(stat.cpu).to.be.equal((currentStime - previousStime) / clockTick) | ||
cb() | ||
}) | ||
}) | ||
}) | ||
require('./procfile')(test) | ||
require('./ps')(test) | ||
require('./wmic')(test) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32395
23
734
111
7
4
9