ps.js 5.8 KB

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