Repository: soyuka/pidusage Branch: main Commit: 36383b8d70c2 Files: 32 Total size: 62.8 KB Directory structure: gitextract_s94v4rqx/ ├── .github/ │ └── workflows/ │ ├── alpine.yml │ ├── lint.yml │ ├── macos.yml │ ├── test.yml │ └── windows.yml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── examples/ │ ├── README.md │ ├── server.js │ └── stresstest.js ├── index.js ├── lib/ │ ├── bin.js │ ├── gwmi.js │ ├── helpers/ │ │ ├── cpu.js │ │ └── parallel.js │ ├── history.js │ ├── procfile.js │ ├── ps.js │ ├── stats.js │ └── wmic.js ├── package.json └── test/ ├── bench.js ├── fixtures/ │ └── _eventloop.js ├── gwmi.js ├── helpers/ │ └── _mocks.js ├── integration.js ├── procfile.js ├── ps.js └── wmic.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/alpine.yml ================================================ name: test-alpine on: push: branches: '*' pull_request: branches: '*' jobs: test: runs-on: ubuntu-latest name: Test alpine steps: - uses: actions/checkout@v2 - run: | docker build . -t pidusage docker run -v $(pwd):/var/pidusage pidusage:latest npm install docker run -v $(pwd):/var/pidusage pidusage:latest npm test ================================================ FILE: .github/workflows/lint.yml ================================================ name: lint on: push: branches: '*' pull_request: branches: '*' jobs: lint: runs-on: ubuntu-latest name: Lint steps: - name: Setup repo uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v1 - name: Install dev dependencies run: | npm install --only=dev - name: Run lint run: npm run lint ================================================ FILE: .github/workflows/macos.yml ================================================ name: test-macos on: push: branches: '*' pull_request: branches: '*' jobs: test: runs-on: macos-latest name: Test MacOS steps: - uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v2 - run: npm install - run: npm test ================================================ FILE: .github/workflows/test.yml ================================================ name: linux on: push: branches: '*' pull_request: branches: '*' jobs: test: runs-on: ubuntu-latest strategy: matrix: node: [ '18', '20', '22', '23' ] name: Test Node ${{ matrix.node }} steps: - uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - run: npm install - run: npm test coverage: runs-on: ubuntu-latest name: Test coverage steps: - uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v2 - run: npm install - run: npm run coverage - uses: codecov/codecov-action@v5 ================================================ FILE: .github/workflows/windows.yml ================================================ name: test-windows on: push: branches: '*' pull_request: branches: '*' jobs: test: runs-on: windows-latest name: Test Windows steps: - uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v2 - run: npm install - run: npm test ================================================ FILE: .gitignore ================================================ node_modules .nyc_output coverage ================================================ FILE: CHANGELOG.md ================================================ ### 4.0.1 - fix spawned wmic processes not exiting after wmic/gwmi detection in packaged apps, leading to infinite build up of "WMI Commandline Utility" processes and system instability ### 4.0.0 - fix wmic removed on Windows 11 and add gwmi support - node >= 17 ### 3.0.1 - removed dynamic requires to allow for bundling #154 - add ibm platform (os400) #158 ### 3.0 - removes node 8 support - add z/OS (os390) support - environment variables to configure pidusage (`PIDUSAGE_USE_PS`, `PIDUSAGE_MAXAGE`, `PIDUSAGE_SILENT`) - use a default Date when `uptime` returns `undefined` ### 2.0.17 - allow to manually clear the event loop when needed it'll clear itself after a given timeout (default to `60000ms` but you can specify it with the `maxage` option, [see this file](https://github.com/soyuka/pidusage/blob/master/test/fixtures/eventloop.js#L3)) [1740a4f](https://github.com/soyuka/pidusage/commit/2779e520d3414a8318c86279cf14bebae3264604) - fix elapsed and timestamp calculations on linux [#80](https://github.com/soyuka/pidusage/issues/80) [e5e2b01](https://github.com/soyuka/pidusage/commit/081984a04bc97ad8abd82315f936861cce1df0d6) ### 2.0.16 - fix ps on darwin, centisenconds multiplier was wrong and was giving bad cpu usage values [bcda538](https://github.com/soyuka/pidusage/commit/bcda538b76498c6d4bcaa36520238990554929c5) ### 2.0.15 - Fix Buffer.alloc for node < 4.5 ### 2.0.14 - Version unpublished because of a publish mistake from my part due to a npm error message that confused me. Explanation [in isse #71](https://github.com/soyuka/pidusage/issues/72#issuecomment-407572581) ### 2.0.12 - fix #69 with `ps` use elapsed instead of etime to avoid negative cpu values [0994268](https://github.com/soyuka/pidusage/commit/0994268c297e23efa3d9052f24cbacf2cbe31629) - fix typo in aix `ps` command #68 [7d55518](https://github.com/soyuka/pidusage/commit/7d55518b7d5d5aae964e64ddd7b13ecec75463a1) ### 2.0.10 - aix is using ps (was changed by mistake), still no aix CI though - add alpine to the test suite and make it use procfile - Improve procfile performances by keeping the procfile open [da1c5fb](https://github.com/soyuka/pidusage/commit/da1c5fb2480bdf8f871476d79161dac7733b89f3) ### 2.0.8 - After further discussion cpu formula got reverted to the initial one [f990f72](https://github.com/soyuka/pidusage/commit/f990f72fd185e4ba0a87048e6e6c59f814a016cc) ### 2.0.7 - Cpu formula changed a bit because of multi thread issues see [issue #58](https://github.com/soyuka/pidusage/issues/58) [88972d8](https://github.com/soyuka/pidusage/commit/88972d8cd38d4137b70261a830af22283b69c57c) ### 2.0.6 - procfiles are back because of performance issues [85e20fa](https://github.com/soyuka/pidusage/commit/85e20fa30aa9ff01d87d3ba9be7fec7f805fc5fb) ### 2.0 - allow multiple pids - remove `advanced` option - don't use `/proc` (procfiles) anymore but use `ps` instead - more tests - API change no more `stat` method, module exports a single function - no more `unmonitor` method, this is handed internally - the default call now returns more data: ``` { cpu: 10.0, // percentage (it may happen to be greater than 100%) memory: 357306368, // bytes ppid: 312, // PPID pid: 727, // PID ctime: 867000, // ms user + system time elapsed: 6650000, // ms since the start of the process timestamp: 864000000 // ms since epoch } ``` ### 1.2.0 Introduce `advanced` option to get time, and start ### 1.1.0 Windows: (wmic) goes back to the first version of wmic, naming `wmic process {pid} get workingsetsize,usermodetime,kernelmodetime`. CPU usage % is computed on the flight, per pid. ### 1.0.5 Windows: (wmic) Use raw data instead of formatted this should speed up wmic ### 0.1.0 API changes: ``` require('pidusage').stat(pid, fn) ``` instead of: ``` require('pidusage')(pid, fn) ``` Adds a `unmonitor` method to clear process history ================================================ FILE: Dockerfile ================================================ FROM node:alpine RUN mkdir -p /var/pidusage WORKDIR /var/pidusage ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 soyuka Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # pidusage [![Lint](https://github.com/soyuka/pidusage/workflows/lint/badge.svg?branch=main)](https://github.com/soyuka/pidusage/actions?query=workflow:lint+branch:main) [![MacOS](https://github.com/soyuka/pidusage/workflows/test-macos/badge.svg?branch=main)](https://github.com/soyuka/pidusage/actions?query=workflow:test-macos+branch:main) [![Ubuntu](https://github.com/soyuka/pidusage/workflows/linux/badge.svg?branch=main)](https://github.com/soyuka/pidusage/actions?query=workflow:linux+branch:main) [![Windows](https://github.com/soyuka/pidusage/workflows/test-windows/badge.svg?branch=main)](https://github.com/soyuka/pidusage/actions?query=workflow:test-windows+branch:main) [![Alpine](https://github.com/soyuka/pidusage/workflows/test-alpine/badge.svg?branch=main)](https://github.com/soyuka/pidusage/actions?query=workflow:test-alpine+branch:main) [![Code coverage](https://img.shields.io/codecov/c/github/soyuka/pidusage/master.svg)](https://codecov.io/gh/soyuka/pidusage) [![npm version](https://img.shields.io/npm/v/pidusage.svg)](https://www.npmjs.com/package/pidusage) [![license](https://img.shields.io/github/license/soyuka/pidusage.svg)](https://github.com/soyuka/pidusage/tree/master/license) Cross-platform process cpu % and memory usage of a PID. ## Synopsis Ideas from https://github.com/arunoda/node-usage but with no C-bindings. Please note that if you need to check a Node.JS script process cpu and memory usage, you can use [`process.cpuUsage`][node:cpuUsage] and [`process.memoryUsage`][node:memUsage] since node v6.1.0. This script remain useful when you have no control over the remote script, or if the process is not a Node.JS process. ## Usage ```js var pidusage = require('pidusage') pidusage(process.pid, function (err, stats) { console.log(stats) // => { // cpu: 10.0, // percentage (from 0 to 100*vcore) // memory: 357306368, // bytes // ppid: 312, // PPID // pid: 727, // PID // ctime: 867000, // ms user + system time // elapsed: 6650000, // ms since the start of the process // timestamp: 864000000 // ms since epoch // } cb() }) // It supports also multiple pids pidusage([727, 1234], function (err, stats) { console.log(stats) // => { // 727: { // cpu: 10.0, // percentage (from 0 to 100*vcore) // memory: 357306368, // bytes // ppid: 312, // PPID // pid: 727, // PID // ctime: 867000, // ms user + system time // elapsed: 6650000, // ms since the start of the process // timestamp: 864000000 // ms since epoch // }, // 1234: { // cpu: 0.1, // percentage (from 0 to 100*vcore) // memory: 3846144, // bytes // ppid: 727, // PPID // pid: 1234, // PID // ctime: 0, // ms user + system time // elapsed: 20000, // ms since the start of the process // timestamp: 864000000 // ms since epoch // } // } }) // If no callback is given it returns a promise instead const stats = await pidusage(process.pid) console.log(stats) // => { // cpu: 10.0, // percentage (from 0 to 100*vcore) // memory: 357306368, // bytes // ppid: 312, // PPID // pid: 727, // PID // ctime: 867000, // ms user + system time // elapsed: 6650000, // ms since the start of the process // timestamp: 864000000 // ms since epoch // } // Avoid using setInterval as they could overlap with asynchronous processing function compute(cb) { pidusage(process.pid, function (err, stats) { console.log(stats) // => { // cpu: 10.0, // percentage (from 0 to 100*vcore) // memory: 357306368, // bytes // ppid: 312, // PPID // pid: 727, // PID // ctime: 867000, // ms user + system time // elapsed: 6650000, // ms since the start of the process // timestamp: 864000000 // ms since epoch // } cb() }) } function interval(time) { setTimeout(function() { compute(function() { interval(time) }) }, time) } // Compute statistics every second: interval(1000) // Above example using async/await const compute = async () => { const stats = await pidusage(process.pid) // do something } // Compute statistics every second: const interval = async (time) => { setTimeout(async () => { await compute() interval(time) }, time) } interval(1000) ``` ## Compatibility | Property | Linux | FreeBSD | NetBSD | SunOS | macOS | Win | AIX | Alpine | --- | --- | --- | --- | --- | --- | --- | --- | --- | | `cpu` | ✅ | ❓ | ❓ | ❓ | ✅ | ℹ️ | ❓ | ✅ | | `memory` | ✅ | ❓ | ❓ | ❓ | ✅ | ✅ | ❓ | ✅ | | `pid` | ✅ | ❓ | ❓ | ❓ | ✅ | ✅ | ❓ | ✅ | | `ctime` | ✅ | ❓ | ❓ | ❓ | ✅ | ✅ | ❓ | ✅ | | `elapsed` | ✅ | ❓ | ❓ | ❓ | ✅ | ✅ | ❓ | ✅ | | `timestamp` | ✅ | ❓ | ❓ | ❓ | ✅ | ✅ | ❓ | ✅ | ✅ = Working ℹ️ = Not Accurate ❓ = Should Work ❌ = Not Working Please if your platform is not supported or if you have reported wrong readings [file an issue][new issue]. By default, pidusage will use `procfile` parsing on most unix systems. If you want to use `ps` instead use the `usePs` option: ``` pidusage(pid, {usePs: true}) ``` ## API ### pidusage(pids, [options = {}], [callback]) ⇒ [Promise.<Object>] Get pid informations. **Kind**: global function **Returns**: Promise.<Object> - Only when the callback is not provided. **Access**: public | Param | Type | Description | | --- | --- | --- | | pids | Number \| Array.<Number> \| String \| Array.<String> | A pid or a list of pids. | | [options] | object | Options object. See the table below. | | [callback] | function | Called when the statistics are ready. If not provided a promise is returned instead. | ### options Setting the options programatically will override environment variables | Param | Type | Environment variable | Default | Description | | --- | --- | --- | --- | --- | | [usePs] | boolean | `PIDUSAGE_USE_PS`| `false` | When true uses `ps` instead of proc files to fetch process information | | [maxage] | number | `PIDUSAGE_MAXAGE`| `60000` | Max age of a process on history. | `PIDUSAGE_SILENT=1` can be used to remove every console message triggered by pidusage. ### pidusage.clear() If needed this function can be used to delete all in-memory metrics and clear the event loop. This is not necessary before exiting as the interval we're registring does not hold up the event loop. ## Related - [pidusage-tree][gh:pidusage-tree] - Compute a pidusage tree ## Authors - **Antoine Bluchet** - [soyuka][github:soyuka] - **Simone Primarosa** - [simonepri][github:simonepri] See also the list of [contributors][contributors] who participated in this project. ## License This project is licensed under the MIT License - see the [LICENSE][license] file for details. [new issue]: https://github.com/soyuka/pidusage/issues/new [license]: https://github.com/soyuka/pidusage/tree/master/LICENSE [contributors]: https://github.com/soyuka/pidusage/contributors [github:soyuka]: https://github.com/soyuka [github:simonepri]: https://github.com/simonepri [gh:pidusage-tree]: https://github.com/soyuka/pidusage-tree [node:cpuUsage]: https://nodejs.org/api/process.html#process_process_cpuusage_previousvalue [node:memUsage]: https://nodejs.org/api/process.html#process_process_memoryusage ================================================ FILE: examples/README.md ================================================ # Example section ## server.js Used to be tested with an HTTP benchmark tool like this one: https://github.com/wg/wrk. Start the server, run the tests on `localhost:8020` ## stresstest.js Just start node stresstest.js (but be carefull though...) ================================================ FILE: examples/server.js ================================================ const http = require('http') const pidusage = require('../') http.createServer(function (req, res) { res.writeHead(200) res.end('hello world\n') }).listen(8020) const interval = setInterval(function () { pidusage(process.pid, function (err, stat) { if (err) { throw err } console.log(stat) }) }, 100) process.on('exit', function () { clearInterval(interval) }) ================================================ FILE: examples/stresstest.js ================================================ const pusage = require('../') // stress test to compare with top or another tool console.log('This is my PID: %s', process.pid) // classic "drop somewhere"... yeah I'm a lazy guy const formatBytes = function (bytes, precision) { const kilobyte = 1024 const megabyte = kilobyte * 1024 const gigabyte = megabyte * 1024 const terabyte = gigabyte * 1024 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 ' } } let i = 0 const bigMemoryLeak = [] const stress = function (cb) { let j = 500 const arr = [] while (j--) { arr[j] = [] for (let k = 0; k < 1000; k++) { arr[j][k] = { lorem: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum non odio venenatis, pretium ligula nec, fringilla ipsum. Sed a erat et sem blandit dignissim. Pellentesque sollicitudin felis eu mattis porta. Nullam nec nibh nisl. Phasellus convallis vulputate massa vitae fringilla. Etiam facilisis lectus in odio lacinia rutrum. Praesent facilisis vitae urna a suscipit. Aenean lacinia blandit lorem, et ullamcorper metus sagittis faucibus. Nam porta eros nisi, at adipiscing quam varius eu. Vivamus sed sem quis lorem varius posuere ut quis elit.' } } } bigMemoryLeak.push(arr) pusage(process.pid, function (err, stat) { if (err) { throw err } console.log('Pcpu: %s', stat.cpu) console.log('Mem: %s', formatBytes(stat.memory)) if (i === 100) { return cb(null, true) } else if (stat.memory > 3e8) { console.log("That's enough right?") cb(null, true) } i++ return cb(null, false) }) } const interval = function () { return setTimeout(function () { stress(function (err, stop) { if (err) { throw err } if (stop) { process.exit() } else { return interval() } }) }, 400) } setTimeout(function () { interval() }, 2000) ================================================ FILE: index.js ================================================ 'use strict' const stats = require('./lib/stats') /** * Get pid informations. * @public * @param {Number|Number[]|String|String[]} pids A pid or a list of pids. * @param {Object} [options={}] Options object * @param {Function} [callback=undefined] Called when the statistics are ready. * If not provided a promise is returned instead. * @returns {Promise.} Only when the callback is not provided. */ function pidusage (pids, options, callback) { if (typeof options === 'function') { callback = options options = {} } if (options === undefined) { options = {} } options = Object.assign({ usePs: /^true$/i.test(process.env.PIDUSAGE_USE_PS), maxage: process.env.PIDUSAGE_MAXAGE }, options) if (typeof callback === 'function') { stats(pids, options, callback) return } return new Promise(function (resolve, reject) { stats(pids, options, function (err, data) { if (err) return reject(err) resolve(data) }) }) } module.exports = pidusage module.exports.clear = require('./lib/history').clear ================================================ FILE: lib/bin.js ================================================ 'use strict' const spawn = require('child_process').spawn /** * Spawn a binary and read its stdout. * @param {String} cmd * @param {String[]} args * @param {Function} done(err, stdout) */ function run (cmd, args, options, done) { if (typeof options === 'function') { done = options options = undefined } let executed = false const ch = spawn(cmd, args, options) let stdout = '' let stderr = '' ch.stdout.on('data', function (d) { stdout += d.toString() }) ch.stderr.on('data', function (d) { stderr += d.toString() }) ch.on('error', function (err) { if (executed) return executed = true done(new Error(err)) }) ch.on('close', function (code, signal) { if (executed) return executed = true if (stderr) { return done(new Error(stderr)) } done(null, stdout, code) }) } module.exports = run ================================================ FILE: lib/gwmi.js ================================================ 'use strict' const os = require('os') const bin = require('./bin') const history = require('./history') function parseDate (datestr) { const year = datestr.substring(0, 4) const month = datestr.substring(4, 6) const day = datestr.substring(6, 8) const hour = datestr.substring(8, 10) const minutes = datestr.substring(10, 12) const seconds = datestr.substring(12, 14) const useconds = datestr.substring(15, 21) const sign = datestr.substring(21, 22) const tmz = parseInt(datestr.substring(22, 25), 10) const tmzh = Math.floor(tmz / 60) const tmzm = tmz % 60 return new Date( year + '-' + month + '-' + day + 'T' + hour + ':' + minutes + ':' + seconds + '.' + useconds + sign + (tmzh > 9 ? tmzh : '0' + tmzh) + '' + (tmzm > 9 ? tmzm : '0' + tmzm) ) } function gwmi (pids, options, done) { let whereClause = 'ProcessId=' + pids[0] for (let i = 1; i < pids.length; i++) { whereClause += ' or ' + 'ProcessId=' + pids[i] } const property = 'CreationDate,KernelModeTime,ParentProcessId,ProcessId,UserModeTime,WorkingSetSize' const args = ['win32_process', '-Filter', '\'' + whereClause + '\'', '| select ' + property, '| format-table'] bin('gwmi', args, { windowsHide: true, windowsVerbatimArguments: true, shell: 'powershell.exe' }, function (err, stdout, code) { if (err) { if (err.message.indexOf('No Instance(s) Available.') !== -1) { const error = new Error('No matching pid found') error.code = 'ENOENT' return done(error) } return done(err) } if (code !== 0) { return done(new Error('pidusage gwmi command exited with code ' + code)) } const date = Date.now() // Note: On Windows the returned value includes fractions of a second. // Use Math.floor() to get whole seconds. // Fallback on current date when uptime is not allowed (see https://github.com/soyuka/pidusage/pull/130) const uptime = Math.floor(os.uptime() || (date / 1000)) // Example of stdout on Windows 10 // CreationDate: is in the format yyyymmddHHMMSS.mmmmmmsUUU // KernelModeTime: is in units of 100 ns // UserModeTime: is in units of 100 ns // WorkingSetSize: is in bytes // // Refs: https://superuser.com/a/937401/470946 // Refs: https://msdn.microsoft.com/en-us/library/aa394372(v=vs.85).aspx // NB: The columns are returned in lexicographical order // // Stdout Format // // Active code page: 936 // // CreationDate KernelModeTime ParentProcessId ProcessId UserModeTime WorkingSetSize // ------------ -------------- --------------- --------- ------------ -------------- // 20220220185531.619182+480 981406250 18940 2804 572656250 61841408 stdout = stdout.split(os.EOL).slice(1) const index = stdout.findIndex(v => !!v) stdout = stdout.slice(index + 2) if (!stdout.length) { const error = new Error('No matching pid found') error.code = 'ENOENT' return done(error) } let again = false const statistics = {} for (let i = 0; i < stdout.length; i++) { const line = stdout[i].trim().split(/\s+/) if (!line || line.length === 1) { continue } const creation = parseDate(line[0]) const ppid = parseInt(line[2], 10) const pid = parseInt(line[3], 10) const kerneltime = Math.round(parseInt(line[1], 10) / 10000) const usertime = Math.round(parseInt(line[4], 10) / 10000) const memory = parseInt(line[5], 10) let hst = history.get(pid, options.maxage) if (hst === undefined) { again = true hst = { ctime: kerneltime + usertime, uptime: uptime } } // process usage since last call const total = (kerneltime + usertime - hst.ctime) / 1000 // time elapsed between calls in seconds const seconds = uptime - hst.uptime const cpu = seconds > 0 ? (total / seconds) * 100 : 0 history.set(pid, { ctime: usertime + kerneltime, uptime: uptime }, options.maxage) statistics[pid] = { cpu: cpu, memory: memory, ppid: ppid, pid: pid, ctime: usertime + kerneltime, elapsed: date - creation.getTime(), timestamp: date } } if (again) { return gwmi(pids, options, function (err, stats) { if (err) return done(err) done(null, Object.assign(statistics, stats)) }) } done(null, statistics) }) } module.exports = gwmi ================================================ FILE: lib/helpers/cpu.js ================================================ const os = require('os') const fs = require('fs') const exec = require('child_process').exec const 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) { if (!process.env.PIDUSAGE_SILENT) { console.warn("[pidusage] We couldn't find uptime from /proc/uptime, using os.uptime() value") } return next(null, os.uptime() || (new Date() / 1000)) } 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) { if (!process.env.PIDUSAGE_SILENT) { console.error('Error while calling "getconf ' + keyword + '"', error) } return next(null, options.default) } stdout = parseInt(stdout) if (!isNaN(stdout)) { return next(null, stdout) } return next(null, options.default) }) } ================================================ FILE: lib/helpers/parallel.js ================================================ // 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 = {} } let keys if (!Array.isArray(fns)) { keys = Object.keys(fns) } const length = keys ? keys.length : fns.length let pending = length const results = keys ? {} : [] function each (i, err, result) { results[i] = result if (--pending === 0 || (err && !options.graceful)) { if (options.graceful && err && length > 1) { err = null } 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 ================================================ FILE: lib/history.js ================================================ 'use strict' const DEFAULT_MAXAGE = 60000 const expiration = {} const history = {} const expireListeners = {} let size = 0 let interval = null function get (pid, maxage) { if (maxage <= 0) { return } if (history[pid] !== undefined) { expiration[pid] = Date.now() + (maxage || DEFAULT_MAXAGE) } return history[pid] } function set (pid, object, maxage, onExpire) { if (object === undefined || maxage <= 0) return expiration[pid] = Date.now() + (maxage || DEFAULT_MAXAGE) if (history[pid] === undefined) { size++ sheduleInvalidator(maxage) } history[pid] = object if (onExpire) { expireListeners[pid] = onExpire } } function sheduleInvalidator (maxage) { if (size > 0) { if (interval === null) { interval = setInterval(runInvalidator, (maxage || DEFAULT_MAXAGE) / 2) if (typeof interval.unref === 'function') { interval.unref() } } return } if (interval !== null) { clearInterval(interval) interval = null } } function runInvalidator () { const now = Date.now() const pids = Object.keys(expiration) for (let i = 0; i < pids.length; i++) { const pid = pids[i] if (expiration[pid] < now) { size-- if (expireListeners[pid]) { expireListeners[pid](history[pid]) } delete history[pid] delete expiration[pid] delete expireListeners[pid] } } sheduleInvalidator() } function deleteLoop (obj) { for (const i in obj) { delete obj[i] } } function clear () { if (interval !== null) { clearInterval(interval) interval = null } deleteLoop(history) deleteLoop(expiration) deleteLoop(expireListeners) } module.exports = { get: get, set: set, clear: clear } ================================================ FILE: lib/procfile.js ================================================ const fs = require('fs') const path = require('path') const updateCpu = require('./helpers/cpu') const parallel = require('./helpers/parallel') const history = require('./history') let cpuInfo = null const Buffer = require('safe-buffer').Buffer const SIZE = 1024 // if the stat file is bigger then this I'll buy you a drink function noop () {} function open (path, history, cb) { if (history.fd) { return cb(null, history.fd) } fs.open(path, 'r', cb) } function close (history) { if (history.fd) { fs.close(history.fd, noop) } } function readUntilEnd (fd, buf, cb) { let firstRead = false if (typeof buf === 'function') { cb = buf buf = Buffer.alloc(SIZE) firstRead = true } fs.read(fd, buf, 0, SIZE, 0, function (err, bytesRead, buffer) { if (err) { cb(err) return } const data = Buffer.concat([buf, buffer], firstRead ? bytesRead : buf.length + bytesRead) if (bytesRead === SIZE) { readUntilEnd(fd, data, cb) return } cb(null, buf) }) } function readProcFile (pid, options, done) { let hst = history.get(pid, options.maxage) let again = false if (hst === undefined) { again = true hst = {} } // Arguments to path.join must be strings open(path.join('/proc', '' + pid, 'stat'), hst, function (err, fd) { if (err) { if (err.code === 'ENOENT') { err.message = 'No matching pid found' } return done(err, null) } if (err) { return done(err) } readUntilEnd(fd, function (err, buffer) { if (err) { return done(err) } let infos = buffer.toString('utf8') const date = Date.now() // https://github.com/arunoda/node-usage/commit/a6ca74ecb8dd452c3c00ed2bde93294d7bb75aa8 // preventing process space in name by removing values before last ) (pid (name) ...) const 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 const stat = { ppid: parseInt(infos[1]), utime: parseFloat(infos[11]) * 1000 / cpuInfo.clockTick, stime: parseFloat(infos[12]) * 1000 / cpuInfo.clockTick, cutime: parseFloat(infos[13]) * 1000 / cpuInfo.clockTick, cstime: parseFloat(infos[14]) * 1000 / cpuInfo.clockTick, start: parseFloat(infos[19]) * 1000 / cpuInfo.clockTick, rss: parseFloat(infos[21]), uptime: cpuInfo.uptime * 1000, fd: fd } const memory = stat.rss * cpuInfo.pageSize // https://stackoverflow.com/a/16736599/3921589 const childrens = options.childrens ? stat.cutime + stat.cstime : 0 // process usage since last call in seconds const total = (stat.stime - (hst.stime || 0) + stat.utime - (hst.utime || 0) + childrens) // time elapsed between calls in seconds const seconds = Math.abs(hst.uptime !== undefined ? stat.uptime - hst.uptime : stat.start - stat.uptime) const cpu = seconds > 0 ? (total / seconds) * 100 : 0 history.set(pid, stat, options.maxage, close) if (again) { return readProcFile(pid, options, done) } return done(null, { cpu: cpu, memory: memory, ctime: stat.utime + stat.stime, elapsed: stat.uptime - stat.start, timestamp: date, pid: pid, ppid: stat.ppid }) }) }) } function procfile (pids, options, done) { updateCpu(cpuInfo, function (err, result) { if (err) return done(err) cpuInfo = result const fns = {} pids.forEach(function (pid, i) { fns[pid] = function (cb) { readProcFile(pid, options, cb) } }) parallel(fns, { graceful: true }, done) }) } module.exports = procfile ================================================ FILE: lib/ps.js ================================================ 'use strict' const os = require('os') const bin = require('./bin') const history = require('./history') const PLATFORM = os.platform() function parseTime (timestr, centisec) { let time = 0 const tpart = timestr.split(/-|:|\./) let i = tpart.length - 1 if (i >= 0 && centisec && PLATFORM === 'darwin') { time += parseInt(tpart[i--], 10) * 10 } if (i >= 0) { // Seconds time += parseInt(tpart[i--], 10) * 1000 } if (i >= 0) { // Minutes time += parseInt(tpart[i--], 10) * 60000 } if (i >= 0) { // Hours time += parseInt(tpart[i--], 10) * 3600000 } if (i >= 0) { // Days time += parseInt(tpart[i--], 10) * 86400000 } return time } /** * Get pid informations through ps command. * @param {Number[]} pids * @param {Object} options * @param {Function} done(err, stat) */ function ps (pids, options, done) { const pArg = pids.join(',') let args = ['-o', 'etime,pid,ppid,pcpu,rss,time', '-p', pArg] if (PLATFORM === 'aix' || PLATFORM === 'os400') { args = ['-o', 'etime,pid,ppid,pcpu,rssize,time', '-p', pArg] } bin('ps', args, function (err, stdout, code) { if (err) { if (PLATFORM === 'os390' && /no matching processes found/.test(err)) { err = new Error('No matching pid found') err.code = 'ENOENT' } return done(err) } if (code === 1) { const error = new Error('No matching pid found') error.code = 'ENOENT' return done(error) } if (code !== 0) { return done(new Error('pidusage ps command exited with code ' + code)) } const date = Date.now() // Example of stdout on *nix. // ELAPSED: format is [[dd-]hh:]mm:ss // RSS: is counted as blocks of 1024 bytes // TIME: format is [[dd-]hh:]mm:ss // %CPU: goes from 0 to vcore * 100 // // Refs: http://www.manpages.info/linux/ps.1.html // NB: The columns are returned in the order given inside the -o option // // ELAPSED PID PPID %CPU RSS TIME // 2-40:50:53 430 1 3.0 5145 1-02:03:04 // 40:50:53 432 430 0.0 2364 1-01:02:03 // 01:50:50 727 1 10.0 348932 14:27 // 00:20 7166 1 0.1 3756 0:00 // Example of stdout on Darwin // ELAPSED: format is [[dd-]hh:]mm:ss // RSS: is counted as blocks of 1024 bytes // TIME: format is [[dd-]hh:]mm:ss.cc (cc are centiseconds) // %CPU: goes from 0 to vcore * 100 // // Refs: https://ss64.com/osx/ps.html // NB: The columns are returned in the order given inside the -o option // // ELAPSED PID PPID %CPU RSS TIME // 2-40:50:53 430 1 3.0 5145 1-02:03:04.07 // 40:50:53 432 430 0.0 2364 1-01:02:03.10 // 01:50:50 727 1 10.0 348932 14:27.26 // 00:20 7166 1 0.1 3756 0:00.02 stdout = stdout.split(os.EOL) const statistics = {} for (let i = 1; i < stdout.length; i++) { const line = stdout[i].trim().split(/\s+/) if (!line || line.length !== 6) { continue } const pid = parseInt(line[1], 10) let hst = history.get(pid, options.maxage) if (hst === undefined) hst = {} const ppid = parseInt(line[2], 10) const memory = parseInt(line[4], 10) * 1024 const etime = parseTime(line[0]) const ctime = parseTime(line[5], true) const total = (ctime - (hst.ctime || 0)) // time elapsed between calls in seconds const seconds = Math.abs(hst.elapsed !== undefined ? etime - hst.elapsed : etime) const cpu = seconds > 0 ? (total / seconds) * 100 : 0 statistics[pid] = { cpu: cpu, memory: memory, ppid: ppid, pid: pid, ctime: ctime, elapsed: etime, timestamp: date } history.set(pid, statistics[pid], options.maxage) } done(null, statistics) }) } module.exports = ps ================================================ FILE: lib/stats.js ================================================ 'use strict' const fs = require('fs') const os = require('os') const spawn = require('child_process').spawn const requireMap = { ps: () => require('./ps'), procfile: () => require('./procfile'), wmic: () => require('./wmic'), gwmi: () => require('./gwmi') } const platformToMethod = { aix: 'ps', os400: 'ps', android: 'procfile', alpine: 'procfile', darwin: 'ps', freebsd: 'ps', os390: 'ps', linux: 'procfile', netbsd: 'procfile', openbsd: 'ps', sunos: 'ps', win: 'wmic' } let platform = os.platform() if (fs.existsSync('/etc/alpine-release')) { platform = 'alpine' } if (platform.match(/^win/)) { platform = 'win' } let stat try { stat = requireMap[platformToMethod[platform]]() } catch (err) {} /** * @callback pidCallback * @param {Error} err A possible error. * @param {Object} statistics The object containing the statistics. */ /** * Get pid informations. * @public * @param {Number|Number[]|String|String[]} pids A pid or a list of pids. * @param {Object} [options={}] Options object * @param {pidCallback} callback Called when the statistics are ready. */ function get (pids, options, callback) { let fn = stat if (platform !== 'win' && options.usePs === true) { fn = requireMap.ps() } if (platform === 'win') { // TODO: use https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/where to avoid try/catch let child; try { child = spawn('wmic', function (err) { if (err) throw new Error(err) }) } catch (err) { fn = requireMap.gwmi() } finally { if (child) { child.kill() } } } if (fn === undefined) { return callback(new Error(os.platform() + ' is not supported yet, please open an issue (https://github.com/soyuka/pidusage)')) } let single = false if (!Array.isArray(pids)) { single = true pids = [pids] } if (pids.length === 0) { return callback(new TypeError('You must provide at least one pid')) } for (let i = 0; i < pids.length; i++) { pids[i] = parseInt(pids[i], 10) if (isNaN(pids[i]) || pids[i] < 0) { return callback(new TypeError('One of the pids provided is invalid')) } } fn(pids, options, function (err, stats) { if (err) { return callback(err) } if (single) { callback(null, stats[pids[0]]) } else { callback(null, stats) } }) } module.exports = get ================================================ FILE: lib/wmic.js ================================================ 'use strict' const os = require('os') const bin = require('./bin') const history = require('./history') function parseDate (datestr) { const year = datestr.substring(0, 4) const month = datestr.substring(4, 6) const day = datestr.substring(6, 8) const hour = datestr.substring(8, 10) const minutes = datestr.substring(10, 12) const seconds = datestr.substring(12, 14) const useconds = datestr.substring(15, 21) const sign = datestr.substring(21, 22) const tmz = parseInt(datestr.substring(22, 25), 10) const tmzh = Math.floor(tmz / 60) const tmzm = tmz % 60 return new Date( year + '-' + month + '-' + day + 'T' + hour + ':' + minutes + ':' + seconds + '.' + useconds + sign + (tmzh > 9 ? tmzh : '0' + tmzh) + '' + (tmzm > 9 ? tmzm : '0' + tmzm) ) } /** * Get pid informations through wmic command. * @param {Number[]} pids * @param {Object} options * @param {Function} done(err, stat) */ function wmic (pids, options, done) { let whereClause = 'ProcessId=' + pids[0] for (let i = 1; i < pids.length; i++) { whereClause += ' or ' + 'ProcessId=' + pids[i] } const args = [ 'PROCESS', 'where', '"' + whereClause + '"', 'get', 'CreationDate,KernelModeTime,ParentProcessId,ProcessId,UserModeTime,WorkingSetSize' ] bin('wmic', args, { windowsHide: true, windowsVerbatimArguments: true }, function (err, stdout, code) { if (err) { if (err.message.indexOf('No Instance(s) Available.') !== -1) { const error = new Error('No matching pid found') error.code = 'ENOENT' return done(error) } return done(err) } if (code !== 0) { return done(new Error('pidusage wmic command exited with code ' + code)) } const date = Date.now() // Note: On Windows the returned value includes fractions of a second. // Use Math.floor() to get whole seconds. // Fallback on current date when uptime is not allowed (see https://github.com/soyuka/pidusage/pull/130) const uptime = Math.floor(os.uptime() || (date / 1000)) // Example of stdout on Windows 10 // CreationDate: is in the format yyyymmddHHMMSS.mmmmmmsUUU // KernelModeTime: is in units of 100 ns // UserModeTime: is in units of 100 ns // WorkingSetSize: is in bytes // // Refs: https://superuser.com/a/937401/470946 // Refs: https://msdn.microsoft.com/en-us/library/aa394372(v=vs.85).aspx // NB: The columns are returned in lexicographical order // // CreationDate KernelModeTime ParentProcessId ProcessId UserModeTime WorkingSetSize // 20150329221650.080654+060 153750000 0 777 8556250000 110821376 stdout = stdout.split(os.EOL) let again = false const statistics = {} for (let i = 1; i < stdout.length; i++) { const line = stdout[i].trim().split(/\s+/) if (!line || line.length !== 6) { continue } const creation = parseDate(line[0]) const ppid = parseInt(line[2], 10) const pid = parseInt(line[3], 10) const kerneltime = Math.round(parseInt(line[1], 10) / 10000) const usertime = Math.round(parseInt(line[4], 10) / 10000) const memory = parseInt(line[5], 10) let hst = history.get(pid, options.maxage) if (hst === undefined) { again = true hst = { ctime: kerneltime + usertime, uptime: uptime } } // process usage since last call const total = (kerneltime + usertime - hst.ctime) / 1000 // time elapsed between calls in seconds const seconds = uptime - hst.uptime const cpu = seconds > 0 ? (total / seconds) * 100 : 0 history.set(pid, { ctime: usertime + kerneltime, uptime: uptime }, options.maxage) statistics[pid] = { cpu: cpu, memory: memory, ppid: ppid, pid: pid, ctime: usertime + kerneltime, elapsed: date - creation.getTime(), timestamp: date } } if (again) { return wmic(pids, options, function (err, stats) { if (err) return done(err) done(null, Object.assign(statistics, stats)) }) } done(null, statistics) }) } module.exports = wmic ================================================ FILE: package.json ================================================ { "name": "pidusage", "version": "3.0.1", "description": "Cross-platform process cpu % and memory usage of a PID", "license": "MIT", "homepage": "https://github.com/soyuka/pidusage", "repository": "github:soyuka/pidusage", "bugs": { "url": "https://github.com/soyuka/pidusage/issues" }, "author": "soyuka", "contributors": [ "Simone Primarosa (https://simoneprimarosa.com)" ], "main": "index.js", "files": [ "lib", "index.js" ], "engines": { "node": ">=18" }, "scripts": { "lint": "standard", "test": "nyc ava -m \"!*benchmark*\"", "coverage": "c8 ava", "bench": "ava -m \"*benchmark*\"" }, "dependencies": { "safe-buffer": "^5.2.1" }, "devDependencies": { "balanced-match": "^3.0.1", "c8": "^10.1.3", "ava": "^6.2.0", "mockdate": "^2.0.5", "mockery": "^2.1.0", "nyc": "^15.1.0", "pify": "^3.0.0", "standard": "^16.0.4", "string-to-stream": "^1.1.1", "through": "^2.3.8", "time-span": "^2.0.0" }, "keywords": [ "pid", "usage", "ps", "cpu", "memory", "proc" ], "ava": { "verbose": true, "failWithoutAssertions": false }, "nyc": { "reporter": [ "lcovonly", "text" ] } } ================================================ FILE: test/bench.js ================================================ const { spawn } = require('child_process') const test = require('ava') const tspan = require('time-span') const m = require('..') async function create (pidno) { const code = ` console.log(process.pid); setInterval(function(){}, 1000); // Does nothing, but prevents exit ` let count = 0 const childs = [] return new Promise((resolve, reject) => { for (let i = 0; i < pidno; i++) { const child = spawn('node', ['-e', code], { windowsHide: true }) childs.push(child) child.stdout.on('data', function (childs) { if (++count === pidno) resolve(childs) }.bind(this, childs)) child.stderr.on('data', function (data) { reject(data.toString()) }) child.on('error', reject) } }) } async function destroy (childs) { childs.forEach(child => child.kill()) } async function execute (childs, pidno, times, options = {}) { const pids = childs.map(child => child.pid).slice(0, pidno) const end = tspan() try { for (let i = 0; i < times; i++) { await m(pids, options) } const time = end() return Promise.resolve(time) } catch (err) { end() return Promise.reject(err) } } test.serial('should execute the benchmark', async t => { const childs = await create(100) let time = await execute(childs, 1, 100, { usePs: true }) t.log(`1 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 1, 100) t.log(`(procfile) 1 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 2, 100, { usePs: true }) t.log(`2 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 2, 100) t.log(`(procfile) 2 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 5, 100, { usePs: true }) t.log(`5 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 5, 100) t.log(`(procfile) 5 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 10, 100, { usePs: true }) t.log(`10 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 10, 100) t.log(`(procfile) 10 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 25, 100, { usePs: true }) t.log(`25 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 25, 100) t.log(`(procfile) 25 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 50, 100, { usePs: true }) t.log(`50 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 50, 100) t.log(`(procfile) 50 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 100, 100, { usePs: true }) t.log(`100 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) time = await execute(childs, 100, 100) t.log(`(procfile) 100 pid 100 times done in ${time.toFixed(3)} ms (${(1000 * 100 / time).toFixed(3)} op/s)`) await destroy(childs) t.pass() }) ================================================ FILE: test/fixtures/_eventloop.js ================================================ const pidusage = require('../../') pidusage(process.pid, { maxage: 1500 }, function (err, stat) { if (err) { throw err } console.log('Got stats', stat) // clean the event loop right away if (process.clear_pidusage === '1' || process.argv[2] === '1') { pidusage.clear() } }) console.log('My pid is ' + process.pid) ================================================ FILE: test/gwmi.js ================================================ const mockery = require('mockery') const test = require('ava') const os = require('os') const mockdate = require('mockdate') const pify = require('pify') const mocks = require('./helpers/_mocks') const timeout = ms => new Promise((resolve, reject) => setTimeout(resolve, ms)) test.before(() => { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }) mockdate.set(new Date(1427749200000)) }) test.beforeEach(() => { mockery.resetCache() }) test.after(() => { mockery.disable() mockdate.reset() }) test('should parse gwmi output on Windows', async t => { const stdout = '' + 'Active code page: 936' + os.EOL + '' + os.EOL + '' + os.EOL + 'CreationDate KernelModeTime ParentProcessId ProcessId UserModeTime WorkingSetSize' + os.EOL + '------------ ----------- ----------- ------------ -------- --------' + os.EOL + '20150329221650.080654+060 153750000 0 777 8556250000 110821376' let calls = 0 mockery.registerMock('child_process', { spawn: () => { calls++ return mocks.spawn(stdout, '', null, 0, null) } }) const gwmi = require('../lib/gwmi') let result = await pify(gwmi)([6456], { maxage: 1000 }) t.deepEqual(result, { 777: { cpu: 0, memory: 110821376, ppid: 0, pid: 777, ctime: (855625 + 15375), elapsed: 1427749200000 - new Date('2015-03-29T22:16:50.080654+0100').getTime(), timestamp: 1427749200000 } }) result = await pify(gwmi)([6456], { maxage: 1000 }) t.is(calls, 3, '2 first calls to put in history + 1') mockdate.set(new Date(1427749202000)) // wait 1 second, it should do 2 calls again await timeout(1000) calls = 0 result = await pify(gwmi)([6456], { maxage: 1000 }) t.is(calls, 2, '2 first calls') mockery.deregisterMock('child_process') }) ================================================ FILE: test/helpers/_mocks.js ================================================ const EventEmitter = require('events') const streamify = require('string-to-stream') const through = require('through') module.exports = { spawn: (stdout, stderr, error, code, signal) => { const ee = new EventEmitter() ee.stdout = through(function (d) { this.queue(d) }) ee.stderr = through(function (d) { this.queue(d) }) streamify(stderr).pipe(ee.stderr) streamify(stdout).pipe(ee.stdout) if (error) { ee.emit('error', error) } else if (!stderr) { ee.stdout.on('end', () => ee.emit('close', code, signal)) } else { ee.stderr.on('end', () => ee.emit('close', code, signal)) } return ee } } ================================================ FILE: test/integration.js ================================================ const { spawn } = require('child_process') const test = require('ava') const os = require('os') const path = require('path') const m = require('..') test('should work with a single pid', async t => { const pid = process.pid const result = await m(pid) t.log(result) t.is(typeof result, 'object') t.is(typeof result, 'object', 'result') t.is(typeof result.cpu, 'number', 'cpu') t.false(isNaN(result.cpu), 'cpu') t.is(typeof result.memory, 'number', 'memory') // z/OS does not report memory if (process.platform !== 'os390') { t.false(isNaN(result.memory), 'memory') } t.is(typeof result.ppid, 'number', 'ppid') t.false(isNaN(result.ppid), 'ppid') t.is(typeof result.pid, 'number', 'pid') t.false(isNaN(result.pid), 'pid') t.is(typeof result.elapsed, 'number', 'elapsed') t.false(isNaN(result.elapsed), 'elapsed') t.is(typeof result.timestamp, 'number', 'timestamp') t.false(isNaN(result.timestamp), 'timestamp') }) test('should work with an array of pids', async t => { const child = spawn( 'node', ['-e', 'console.log(`123`); setInterval(() => {}, 1000)'], { windowsHide: true } ) const ppid = process.pid const pid = child.pid await t.notThrowsAsync( new Promise((resolve, reject) => { child.stdout.on('data', d => resolve(d.toString())) child.stderr.on('data', d => reject(d.toString())) child.on('error', reject) child.on('exit', code => reject(new Error('script exited with code ' + code))) }), 'script not executed' ) const pids = [ppid, pid] let result try { result = await m(pids) child.kill() } catch (err) { child.kill() t.notThrows(() => { throw err }) } t.log(result) t.is(typeof result, 'object') t.deepEqual(Object.keys(result).sort(), pids.map(pid => pid.toString()).sort()) pids.forEach(pid => { t.is(typeof result[pid], 'object', 'result') t.is(typeof result[pid].cpu, 'number', 'cpu') t.false(isNaN(result[pid].cpu), 'cpu') t.is(typeof result[pid].memory, 'number', 'memory') // z/OS does not report memory if (process.platform !== 'os390') { t.false(isNaN(result[pid].memory), 'memory') } t.is(typeof result[pid].ppid, 'number', 'ppid') t.false(isNaN(result[pid].ppid), 'ppid') t.is(typeof result[pid].pid, 'number', 'pid') t.false(isNaN(result[pid].pid), 'pid') t.is(typeof result[pid].elapsed, 'number', 'elapsed') t.false(isNaN(result[pid].elapsed), 'elapsed') t.is(typeof result[pid].timestamp, 'number', 'timestamp') t.false(isNaN(result[pid].timestamp), 'timestamp') }) m.clear() }) test('should throw an error if no pid is provided', async t => { const err = await t.throwsAsync(() => m([])) t.is(err.message, 'You must provide at least one pid') }) test('should throw an error if one of the pid is invalid', async t => { let err = await t.throwsAsync(() => m(null)) t.is(err.message, 'One of the pids provided is invalid') err = await t.throwsAsync(() => m([null])) t.is(err.message, 'One of the pids provided is invalid') err = await t.throwsAsync(() => m(['invalid'])) t.is(err.message, 'One of the pids provided is invalid') err = await t.throwsAsync(() => m(-1)) t.is(err.message, 'One of the pids provided is invalid') err = await t.throwsAsync(() => m([-1])) t.is(err.message, 'One of the pids provided is invalid') }) test('should not throw an error if one of the pids does not exists', async t => { await t.notThrows(() => m([process.pid, 65535])) await t.notThrows(() => m([65535, process.pid])) }) test('should throw an error if the pid does not exists', async t => { const err = await t.throwsAsync(() => m([65535])) t.is(err.message, 'No matching pid found') t.is(err.code, 'ENOENT') }) test('should throw an error if the pid is too large', async t => { await t.throwsAsync(async () => m(99999999)) }) test('should exit right away because we cleaned up the event loop', t => { if (os.platform().match(/^win/)) return t.pass() process.clear_pidusage = '1' require(path.join(__dirname, '/fixtures/_eventloop')) process.nextTick(() => { t.pass() }) }) test('should exit right away because the event loop ignores history', t => { if (os.platform().match(/^win/)) return t.pass() process.clear_pidusage = '0' require(path.join(__dirname, '/fixtures/_eventloop')) process.nextTick(() => { t.pass() }) }) test("should use the callback if it's provided", t => { m(process.pid, () => t.pass()) }) process.on('unhandledException', (e) => { console.error(e) process.exit(1) }) ================================================ FILE: test/procfile.js ================================================ const mockery = require('mockery') const test = require('ava') const os = require('os') const IS_WIN = os.platform().match(/^win/) test.before(() => { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }) }) test.beforeEach(() => { mockery.resetCache() }) test.after(() => { mockery.disable() }) test('procfile stat', async t => { if (IS_WIN) { t.pass() return } const fs = require('fs') let openCalled = 0 fs.open = function (path, mode, cb) { openCalled++ cb(null, 10) } fs.readFile = function (path, encoding, callback) { if (path === '/proc/uptime') { callback(null, '100 0') } } fs.read = function (fd, buffer, offset, length, position, callback) { // proc//stat let infos = '0 (test)' for (let i = 0; i < 22; i++) { if (i === 12) { infos += ' ' + 10000 // currentStime 10000 * clockTick } else { infos += ' 0' } } buffer.write(infos) callback(null, infos.length, buffer) } fs.existsSync = function (path) { if (path === '/etc/alpine-release') { return true } return false } const 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 }) }) const m = require('..') let stat = await m(10) t.is(stat.cpu, 0) 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') stat = await m(10) t.is(openCalled, 1) }) ================================================ FILE: test/ps.js ================================================ const mockery = require('mockery') const test = require('ava') const os = require('os') const mockdate = require('mockdate') const pify = require('pify') const mocks = require('./helpers/_mocks') test.before(() => { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }) mockdate.set(new Date(864000000)) }) test.beforeEach(() => { mockery.resetCache() }) test.after(() => { mockery.disable() mockdate.reset() }) test('should parse ps output on Darwin', async t => { const stdout = '' + ' ELAPSED PID PPID %CPU RSS TIME' + os.EOL + '2-40:50:53 430 1 3.0 5145 1-02:03:04.07' + os.EOL + ' 40:50:53 432 430 0.0 2364 1-01:02:03.10' + os.EOL + ' 01:50:50 727 1 10.0 348932 14:27.26' + os.EOL + ' 00:20 7166 1 0.1 3756 0:00.02' mockery.registerMock('child_process', { spawn: () => mocks.spawn(stdout, '', null, 0, null) }) mockery.registerMock('os', { EOL: os.EOL, platform: () => 'darwin', type: () => 'type', release: () => 'release' }) const ps = require('../lib/ps') const result = await pify(ps)([348932], {}) t.deepEqual(result, { 430: { cpu: (93784070 / 319853000) * 100, memory: 5145 * 1024, ppid: 1, pid: 430, ctime: (1 * 86400 + 2 * 3600 + 3 * 60 + 4 * 1) * 1000 + (10 * 7), elapsed: (2 * 86400 + 40 * 3600 + 50 * 60 + 53 * 1) * 1000, timestamp: 864000000 }, 432: { cpu: (90123100 / 147053000) * 100, memory: 2364 * 1024, ppid: 430, pid: 432, ctime: (1 * 86400 + 1 * 3600 + 2 * 60 + 3 * 1) * 1000 + (10 * 10), elapsed: (40 * 3600 + 50 * 60 + 53 * 1) * 1000, timestamp: 864000000 }, 727: { cpu: (867260 / 6650000) * 100, memory: 348932 * 1024, ppid: 1, pid: 727, ctime: (14 * 60 + 27 * 1) * 1000 + (10 * 26), elapsed: (1 * 3600 + 50 * 60 + 50 * 1) * 1000, timestamp: 864000000 }, 7166: { cpu: (20 / 20000) * 100, memory: 3756 * 1024, ppid: 1, pid: 7166, ctime: (10 * 2), elapsed: (20 * 1) * 1000, timestamp: 864000000 } }) mockery.deregisterMock('child_process') mockery.deregisterMock('os') }) test('should parse ps output on *nix', async t => { t.pass() // const stdout = '' + // ' ELAPSED PID PPID %CPU RSS TIME' + os.EOL + // '2-40:50:53 430 1 3.0 5145 1-02:03:04' + os.EOL + // ' 40:50:53 432 430 0.0 2364 1-01:02:03' + os.EOL + // ' 01:50:50 727 1 10.0 348932 14:27' + os.EOL + // ' 00:20 7166 1 0.1 3756 0:00' // // mockery.registerMock('child_process', { // spawn: () => mocks.spawn(stdout, '', null, 0, null) // }) // mockery.registerMock('os', { // EOL: os.EOL, // platform: () => 'linux', // type: () => 'type', // release: () => 'release' // }) // // const ps = require('../lib/ps') // // const result = await pify(ps)([11678], {}) // t.deepEqual(result, { // 430: { // cpu: 3.0, // memory: 5145 * 1024, // ppid: 1, // pid: 430, // ctime: (1 * 86400 + 2 * 3600 + 3 * 60 + 4 * 1) * 1000, // elapsed: (2 * 86400 + 40 * 3600 + 50 * 60 + 53 * 1) * 1000, // timestamp: 864000000 // }, // 432: { // cpu: 0.0, // memory: 2364 * 1024, // ppid: 430, // pid: 432, // ctime: (1 * 86400 + 1 * 3600 + 2 * 60 + 3 * 1) * 1000, // elapsed: (40 * 3600 + 50 * 60 + 53 * 1) * 1000, // timestamp: 864000000 // }, // 727: { // cpu: 10.0, // memory: 348932 * 1024, // ppid: 1, // pid: 727, // ctime: (14 * 60 + 27 * 1) * 1000, // elapsed: (1 * 3600 + 50 * 60 + 50 * 1) * 1000, // timestamp: 864000000 // }, // 7166: { // cpu: 0.1, // memory: 3756 * 1024, // ppid: 1, // pid: 7166, // ctime: 0, // elapsed: (20 * 1) * 1000, // timestamp: 864000000 // } // }) // // mockery.deregisterMock('child_process') // mockery.deregisterMock('os') }) test('should be able to set usePs from env var', async t => { let usePsFromStats mockery.registerMock('./lib/stats', (_, options) => { usePsFromStats = options.usePs }) const beforeValue = process.env.PIDUSAGE_USE_PS process.env.PIDUSAGE_USE_PS = 'true' const pidusage = require('../') pidusage(1, () => {}) t.is(usePsFromStats, true) process.env.PIDUSAGE_USE_PS = beforeValue mockery.deregisterMock('./lib/stats') }) ================================================ FILE: test/wmic.js ================================================ const mockery = require('mockery') const test = require('ava') const os = require('os') const mockdate = require('mockdate') const pify = require('pify') const mocks = require('./helpers/_mocks') const timeout = ms => new Promise((resolve, reject) => setTimeout(resolve, ms)) test.before(() => { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }) mockdate.set(new Date(1427749200000)) }) test.beforeEach(() => { mockery.resetCache() }) test.after(() => { mockery.disable() mockdate.reset() }) test('should parse wmic output on Windows', async t => { const stdout = '' + 'CreationDate KernelModeTime ParentProcessId ProcessId UserModeTime WorkingSetSize' + os.EOL + '20150329221650.080654+060 153750000 0 777 8556250000 110821376' let calls = 0 mockery.registerMock('child_process', { spawn: () => { calls++ return mocks.spawn(stdout, '', null, 0, null) } }) const wmic = require('../lib/wmic') let result = await pify(wmic)([6456], { maxage: 1000 }) t.deepEqual(result, { 777: { cpu: 0, memory: 110821376, ppid: 0, pid: 777, ctime: (855625 + 15375), elapsed: 1427749200000 - new Date('2015-03-29T22:16:50.080654+0100').getTime(), timestamp: 1427749200000 } }) result = await pify(wmic)([6456], { maxage: 1000 }) t.is(calls, 3, '2 first calls to put in history + 1') mockdate.set(new Date(1427749202000)) // wait 1 second, it should do 2 calls again await timeout(1000) calls = 0 result = await pify(wmic)([6456], { maxage: 1000 }) t.is(calls, 2, '2 first calls') mockery.deregisterMock('child_process') })