ps.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /**
  2. * 本文件修改至 https://github.com/neekey/ps
  3. * 原因是:
  4. * 1. lookup 只提供了异步方式
  5. * 2. lookup 性能太差
  6. */
  7. var ChildProcess = require("child_process");
  8. var IS_WIN = process.platform === "win32";
  9. var TableParser = require("table-parser");
  10. /**
  11. * End of line.
  12. * Basically, the EOL should be:
  13. * - windows: \r\n
  14. * - *nix: \n
  15. * But i'm trying to get every possibilities covered.
  16. */
  17. var EOL = /(\r\n)|(\n\r)|\n|\r/;
  18. var SystemEOL = require("os").EOL;
  19. /**
  20. * Execute child process
  21. * @type {Function}
  22. * @param {String[]} args
  23. * @param {String} where
  24. * @param {Function} callback
  25. * @param {Object=null} callback.err
  26. * @param {Object[]} callback.stdout
  27. */
  28. var Exec = function (args, where) {
  29. var spawnSync = ChildProcess.spawnSync;
  30. var execSync = ChildProcess.execSync;
  31. // on windows, if use ChildProcess.exec(`wmic process get`), the stdout will gives you nothing
  32. // that's why I use `cmd` instead
  33. if (IS_WIN) {
  34. const cmd = `wmic process where ${where} get ProcessId,ParentProcessId,CommandLine \n`;
  35. const result = execSync(cmd);
  36. if (!result) {
  37. throw new Error(result);
  38. }
  39. var stdout = result.toString();
  40. var beginRow;
  41. stdout = stdout.split(EOL);
  42. // Find the line index for the titles
  43. stdout.forEach(function (out, index) {
  44. if (
  45. out &&
  46. typeof beginRow == "undefined" &&
  47. out.indexOf("CommandLine") === 0
  48. ) {
  49. beginRow = index;
  50. }
  51. });
  52. // get rid of the start (copyright) and the end (current pwd)
  53. stdout.splice(stdout.length - 1, 1);
  54. stdout.splice(0, beginRow);
  55. return stdout.join(SystemEOL) || false;
  56. } else {
  57. if (typeof args === "string") {
  58. args = args.split(/\s+/);
  59. }
  60. const result = spawnSync("ps", args);
  61. if (result.stderr && !!result.stderr.toString()) {
  62. throw new Error(result.stderr);
  63. } else {
  64. return result.stdout.toString();
  65. }
  66. }
  67. };
  68. /**
  69. * Query Process: Focus on pid & cmd
  70. * @param query
  71. * @param {String|String[]} query.pid
  72. * @param {String} query.command RegExp String
  73. * @param {String} query.arguments RegExp String
  74. * @param {String|array} query.psargs
  75. * @param {String|array} query.where where 条件
  76. * @param {Function} callback
  77. * @param {Object=null} callback.err
  78. * @param {Object[]} callback.processList
  79. * @return {Object}
  80. */
  81. exports.lookup = function (query) {
  82. /**
  83. * add 'lx' as default ps arguments, since the default ps output in linux like "ubuntu", wont include command arguments
  84. */
  85. var exeArgs = query.psargs || ["lx"];
  86. var where = query.where || 'name="javaw.exe"';
  87. var filter = {};
  88. var idList;
  89. // Lookup by PID
  90. if (query.pid) {
  91. if (Array.isArray(query.pid)) {
  92. idList = query.pid;
  93. } else {
  94. idList = [query.pid];
  95. }
  96. // Cast all PIDs as Strings
  97. idList = idList.map(function (v) {
  98. return String(v);
  99. });
  100. }
  101. if (query.command) {
  102. filter["command"] = new RegExp(query.command, "i");
  103. }
  104. if (query.arguments) {
  105. filter["arguments"] = new RegExp(query.arguments, "i");
  106. }
  107. if (query.ppid) {
  108. filter["ppid"] = new RegExp(query.ppid);
  109. }
  110. const result = Exec(exeArgs, where);
  111. var processList = parseGrid(result);
  112. var resultList = [];
  113. processList.forEach(function (p) {
  114. var flt;
  115. var type;
  116. var result = true;
  117. if (idList && idList.indexOf(String(p.pid)) < 0) {
  118. return;
  119. }
  120. for (type in filter) {
  121. flt = filter[type];
  122. result = flt.test(p[type]) ? result : false;
  123. }
  124. if (result) {
  125. resultList.push(p);
  126. }
  127. });
  128. return resultList;
  129. };
  130. /**
  131. * Kill process
  132. * @param pid
  133. * @param {Object|String} signal
  134. * @param {String} signal.signal
  135. * @param {number} signal.timeout
  136. * @param next
  137. */
  138. exports.kill = function (pid, signal, next) {
  139. //opts are optional
  140. if (arguments.length == 2 && typeof signal == "function") {
  141. next = signal;
  142. signal = undefined;
  143. }
  144. var checkTimeoutSeconds = (signal && signal.timeout) || 30;
  145. if (typeof signal === "object") {
  146. signal = signal.signal;
  147. }
  148. try {
  149. process.kill(pid, signal);
  150. } catch (e) {
  151. return next && next(e);
  152. }
  153. var checkConfident = 0;
  154. var checkTimeoutTimer = null;
  155. var checkIsTimeout = false;
  156. function checkKilled(finishCallback) {
  157. exports.lookup({ pid: pid }, function (err, list) {
  158. if (checkIsTimeout) return;
  159. if (err) {
  160. clearTimeout(checkTimeoutTimer);
  161. finishCallback && finishCallback(err);
  162. } else if (list.length > 0) {
  163. checkConfident = checkConfident - 1 || 0;
  164. checkKilled(finishCallback);
  165. } else {
  166. checkConfident++;
  167. if (checkConfident === 5) {
  168. clearTimeout(checkTimeoutTimer);
  169. finishCallback && finishCallback();
  170. } else {
  171. checkKilled(finishCallback);
  172. }
  173. }
  174. });
  175. }
  176. next && checkKilled(next);
  177. checkTimeoutTimer =
  178. next &&
  179. setTimeout(function () {
  180. checkIsTimeout = true;
  181. next(new Error("Kill process timeout"));
  182. }, checkTimeoutSeconds * 1000);
  183. };
  184. /**
  185. * Parse the stdout into readable object.
  186. * @param {String} output
  187. */
  188. function parseGrid(output) {
  189. if (!output) {
  190. return [];
  191. }
  192. return formatOutput(TableParser.parse(output));
  193. }
  194. /**
  195. * format the structure, extract pid, command, arguments, ppid
  196. * @param data
  197. * @return {Array}
  198. */
  199. function formatOutput(data) {
  200. var formatedData = [];
  201. data.forEach(function (d) {
  202. var pid =
  203. (d.PID && d.PID[0]) || (d.ProcessId && d.ProcessId[0]) || undefined;
  204. var cmd = d.CMD || d.CommandLine || d.COMMAND || undefined;
  205. var ppid =
  206. (d.PPID && d.PPID[0]) ||
  207. (d.ParentProcessId && d.ParentProcessId[0]) ||
  208. undefined;
  209. if (pid && cmd) {
  210. var command = cmd[0];
  211. var args = "";
  212. if (cmd.length > 1) {
  213. args = cmd.slice(1);
  214. }
  215. formatedData.push({
  216. pid: pid,
  217. command: command,
  218. arguments: args,
  219. ppid: ppid,
  220. });
  221. }
  222. });
  223. return formatedData;
  224. }