-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(wmic): add gwmi command to be compatible with deleted wmic #143
base: main
Are you sure you want to change the base?
Changes from all commits
d8e061b
e60fe06
9fa2dce
39037f1
eb98d93
4a38b1e
95e0e70
d215cf8
0e045b3
5cae75f
d6b77d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
### 3.0.1 | ||
|
||
- fix wmic removed on Windows 11 and add gwmi support | ||
|
||
### 3.0 | ||
|
||
- removes node 8 support | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
'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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
const fs = require('fs') | ||
const os = require('os') | ||
const spawn = require('child_process').spawn | ||
|
||
const platformToMethod = { | ||
aix: 'ps', | ||
|
@@ -17,6 +18,7 @@ const platformToMethod = { | |
} | ||
|
||
const ps = require('./ps') | ||
const gwmi = require('./gwmi') | ||
let platform = os.platform() | ||
|
||
if (fs.existsSync('/etc/alpine-release')) { | ||
|
@@ -50,6 +52,15 @@ function get (pids, options, callback) { | |
if (platform !== 'win' && options.usePs === true) { | ||
fn = ps | ||
} | ||
if (platform === 'win') { | ||
try { | ||
spawn('wmic', function (err) { | ||
if (err) throw new Error(err) | ||
}) | ||
} catch (err) { | ||
fn = gwmi | ||
} | ||
Comment on lines
+56
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this, isn't there another way to try and see if wmic is installed? I'd even say that we should check if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this PR still wants to be pursued, but leaving a comment in any case: On Windows there's the FootnotesThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to be able to continue, but I haven't been following this for a long time and have been a little busy lately with my new job, so I may tackle this again in the next two weeks! |
||
} | ||
|
||
if (stat === undefined) { | ||
return callback(new Error(os.platform() + ' is not supported yet, please open an issue (https://github.com/soyuka/pidusage)')) | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
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') | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome, I'll suggest however to target a 4.x as its a new feature and we follow semver. I'll merge some pending patches but I'd love to see this being added, many thanks for the work!
P.S.: I had a windows environment at the time of coding the first version but now I don't anymore so its hard for me to test this.