Comparing version
@@ -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
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
32395
17.88%23
53.33%734
39.81%111
3.74%7
75%5
66.67%9
125%