-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
abluchet
committed
Mar 21, 2018
1 parent
bbe4d6d
commit 32fd43d
Showing
9 changed files
with
303 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
FROM node:alpine | ||
|
||
RUN mkdir -p /var/pidusage | ||
|
||
WORKDIR /var/pidusage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
var os = require('os') | ||
var fs = require('fs') | ||
var exec = require('child_process').exec | ||
var parallel = require('./parallel') | ||
|
||
/** | ||
* Gathers Clock, PageSize and system uptime through /proc/uptime | ||
* This method is mocked in procfile tests | ||
*/ | ||
function updateCpu (cpu, next) { | ||
if (cpu !== null) { | ||
getRealUptime(function (err, uptime) { | ||
if (err) return next(err) | ||
cpu.uptime = uptime | ||
next(null, cpu) | ||
}) | ||
return | ||
} | ||
|
||
parallel([ | ||
getClockAndPageSize, | ||
getRealUptime | ||
], function (err, data) { | ||
if (err) return next(err) | ||
|
||
cpu = { | ||
clockTick: data[0].clockTick, | ||
pageSize: data[0].pageSize, | ||
uptime: data[1] | ||
} | ||
|
||
next(null, cpu) | ||
}) | ||
} | ||
|
||
module.exports = updateCpu | ||
|
||
/** | ||
* Fallback on os.uptime(), though /proc/uptime is more precise | ||
*/ | ||
function getRealUptime (next) { | ||
fs.readFile('/proc/uptime', 'utf8', function (err, uptime) { | ||
if (err || uptime === undefined) { | ||
console.warn("[pidusage] We couldn't find uptime from /proc/uptime, using os.uptime() value") | ||
return next(null, os.uptime()) | ||
} | ||
|
||
return next(null, parseFloat(uptime.split(' ')[0])) | ||
}) | ||
} | ||
|
||
function getClockAndPageSize (next) { | ||
parallel([ | ||
function getClockTick (cb) { | ||
getconf('CLK_TCK', {default: 100}, cb) | ||
}, | ||
function getPageSize (cb) { | ||
getconf('PAGESIZE', {default: 4096}, cb) | ||
} | ||
], function (err, data) { | ||
if (err) return next(err) | ||
|
||
next(null, {clockTick: data[0], pageSize: data[1]}) | ||
}) | ||
} | ||
|
||
function getconf (keyword, options, next) { | ||
if (typeof options === 'function') { | ||
next = options | ||
options = { default: '' } | ||
} | ||
|
||
exec('getconf ' + keyword, function (error, stdout, stderr) { | ||
if (error !== null) { | ||
console.error('Error while getting ' + keyword, error) | ||
return next(null, options.default) | ||
} | ||
|
||
stdout = parseInt(stdout) | ||
|
||
if (!isNaN(stdout)) { | ||
return next(null, stdout) | ||
} | ||
|
||
return next(null, options.default) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// execute an array of asynchronous functions in parallel | ||
// @param {Array} fns - an array of functions | ||
// @param {Function} done - callback(err, results) | ||
function parallel (fns, options, done) { | ||
if (typeof options === 'function') { | ||
done = options | ||
options = {} | ||
} | ||
|
||
var keys | ||
if (!Array.isArray(fns)) { keys = Object.keys(fns) } | ||
var pending = keys ? keys.length : fns.length | ||
var results = keys ? {} : [] | ||
|
||
function each (i, err, result) { | ||
results[i] = result | ||
|
||
if (--pending === 0 || (err && !options.graceful)) { | ||
done && done(err, results) | ||
done = null | ||
} | ||
} | ||
|
||
if (keys) { | ||
keys.forEach(function (key) { | ||
fns[key](function (err, res) { | ||
each(key, err, res) | ||
}) | ||
}) | ||
} else { | ||
fns.forEach(function (fn, i) { | ||
fn(function (err, res) { | ||
each(i, err, res) | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
module.exports = parallel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
var fs = require('fs') | ||
var path = require('path') | ||
var updateCpu = require('./helpers/cpu') | ||
var parallel = require('./helpers/parallel') | ||
var history = require('./history') | ||
var cpu = null | ||
|
||
function readProcFile (pid, options, done) { | ||
var hst = history.get(pid, options.maxage) | ||
if (hst === undefined) hst = {} | ||
|
||
// Arguments to path.join must be strings | ||
fs.readFile(path.join('/proc', '' + pid, 'stat'), 'utf8', function (err, infos) { | ||
if (err) { | ||
if (err.code === 'ENOENT') { | ||
err.message = 'No maching pid found' | ||
} | ||
|
||
return done(err, null) | ||
} | ||
|
||
var date = Date.now() | ||
// 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 = { | ||
ppid: parseInt(infos[1]), | ||
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]), | ||
uptime: cpu.uptime | ||
} | ||
|
||
// 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 - (hst.stime || 0) + stat.utime - (hst.utime || 0) + childrens) / cpu.clockTick | ||
// time elapsed between calls in seconds | ||
var seconds = Math.abs(hst.uptime !== undefined ? stat.uptime - hst.uptime : stat.start - stat.uptime) | ||
if (seconds === 0) seconds = 1 // we sure can't divide through 0 | ||
|
||
history.set(stat, options.maxage) | ||
|
||
var cpuPercent = Math.min(Math.round((total / seconds) * 100000) / 1000, 100.0) | ||
var memory = stat.rss * cpu.pageSize | ||
|
||
return done(null, { | ||
cpu: cpuPercent, | ||
memory: memory, | ||
ctime: (stat.utime + stat.stime) / cpu.clockTick, | ||
elapsed: date - (stat.start * 1000), | ||
timestamp: stat.start * 1000, // start date | ||
pid: pid, | ||
ppid: stat.ppid | ||
}) | ||
}) | ||
} | ||
|
||
function procfile (pid, options, done) { | ||
updateCpu(cpu, function (err, result) { | ||
if (err) return done(err) | ||
|
||
cpu = result | ||
var fns = {} | ||
|
||
pid.forEach(function (id, i) { | ||
fns[id] = function (cb) { | ||
readProcFile(id, options, cb) | ||
} | ||
}) | ||
|
||
parallel(fns, {graceful: true}, done) | ||
}) | ||
} | ||
|
||
module.exports = procfile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import mockery from 'mockery' | ||
import test from 'ava' | ||
|
||
test.before(() => { | ||
mockery.enable({ | ||
warnOnReplace: false, | ||
warnOnUnregistered: false, | ||
useCleanCache: true | ||
}) | ||
}) | ||
|
||
test.beforeEach(() => { | ||
mockery.resetCache() | ||
}) | ||
|
||
test.after(() => { | ||
mockery.disable() | ||
}) | ||
|
||
test('procfile stat', async t => { | ||
var fs = require('fs') | ||
fs.readFile = function (path, encoding, callback) { | ||
if (path === '/proc/uptime') { | ||
callback(null, '100 0') | ||
return | ||
} | ||
|
||
// proc/<pid>/stat | ||
var infos = '0 (test)' | ||
for (var i = 0; i < 22; i++) { | ||
if (i === 12) { | ||
infos += ' ' + 10000 // currentStime 10000 * clockTick | ||
} else { | ||
infos += ' 0' | ||
} | ||
} | ||
callback(null, infos) | ||
} | ||
|
||
fs.existsSync = function (path) { | ||
if (path === '/etc/alpine-release') { return true } | ||
return false | ||
} | ||
|
||
var os = require('os') | ||
os.platform = function () { return 'linux' } | ||
|
||
mockery.registerMock('os', os) | ||
mockery.registerMock('fs', fs) | ||
mockery.registerMock('./cpu.js', function (cpu, next) { | ||
next({ | ||
clockTick: 100, | ||
uptime: 100, | ||
pagesize: 4096 | ||
}) | ||
}) | ||
|
||
var m = require('..') | ||
var stat = await m(10) | ||
t.is(stat.cpu, 100) | ||
t.is(stat.memory, 0) | ||
t.is(stat.ppid, 0) | ||
t.is(stat.pid, 10) | ||
t.is(typeof stat.elapsed, 'number', 'elapsed') | ||
t.false(isNaN(stat.elapsed), 'elapsed') | ||
t.is(typeof stat.timestamp, 'number', 'timestamp') | ||
t.false(isNaN(stat.timestamp), 'timestamp') | ||
}) |