indexzero/ps-tree

View on GitHub
index.js

Summary

Maintainability
A
1 hr
Test Coverage
'use strict';

var spawn = require('child_process').spawn,
    es    = require('event-stream');

module.exports = function childrenOfPid(pid, callback) {
  var headers = null;

  if (typeof callback !== 'function') {
    throw new Error('childrenOfPid(pid, callback) expects callback');
  }

  if (typeof pid === 'number') {
    pid = pid.toString();
  }

  //
  // The `ps-tree` module behaves differently on *nix vs. Windows
  // by spawning different programs and parsing their output.
  //
  // Linux:
  // 1. " <defunct> " need to be striped
  // ```bash
  // $ ps -A -o comm,ppid,pid,stat
  // COMMAND          PPID   PID STAT
  // bbsd             2899 16958 Ss
  // watch <defunct>  1914 16964 Z
  // ps              20688 16965 R+
  // ```
  //
  // Darwin:
  // $ ps -A -o comm,ppid,pid,stat
  // COMM              PPID   PID STAT
  // /sbin/launchd        0     1 Ss
  // /usr/libexec/Use     1    43 Ss
  //
  // Win32:
  // 1. wmic PROCESS WHERE ParentProcessId=4604 GET Name,ParentProcessId,ProcessId,Status)
  // 2. The order of head columns is fixed
  // ```shell
  // > wmic PROCESS GET Name,ProcessId,ParentProcessId,Status
  // Name                          ParentProcessId  ProcessId   Status
  // System Idle Process           0                0
  // System                        0                4
  // smss.exe                      4                228
  // ```

  var processLister;
  if (process.platform === 'win32') {
    // See also: https://github.com/nodejs/node-v0.x-archive/issues/2318
    processLister = spawn('wmic.exe', ['PROCESS', 'GET', 'Name,ProcessId,ParentProcessId,Status']);
  } else {
    processLister = spawn('ps', ['-A', '-o', 'ppid,pid,stat,comm']);
  }

  es.connect(
    // spawn('ps', ['-A', '-o', 'ppid,pid,stat,comm']).stdout,
    processLister.stdout,
    es.split(),
    es.map(function (line, cb) { //this could parse alot of unix command output
      var columns = line.trim().split(/\s+/);
      if (!headers) {
        headers = columns;

        //
        // Rename Win32 header name, to as same as the linux, for compatible.
        //
        headers = headers.map(normalizeHeader);
        return cb();
      }

      var row = {};
      // For each header
      var h = headers.slice();
      while (h.length) {
        row[h.shift()] = h.length ? columns.shift() : columns.join(' ');
      }

      return cb(null, row);
    }),
    es.writeArray(function (err, ps) {
      var parents = {},
          children = [];

      parents[pid] = true;
      ps.forEach(function (proc) {
        if (parents[proc.PPID]) {
          parents[proc.PID] = true;
          children.push(proc)
        }
      });

      callback(null, children);
    })
  ).on('error', callback)
}

/**
 * Normalizes the given header `str` from the Windows
 * title to the *nix title.
 *
 * @param {string} str Header string to normalize
 */
function normalizeHeader(str) {
  switch (str) {
    case 'Name':  // for win32
    case 'COMM':  // for darwin
      return 'COMMAND';
      break;
    case 'ParentProcessId':
      return 'PPID';
      break;
    case 'ProcessId':
      return 'PID';
      break;
    case 'Status':
      return 'STAT';
      break;
    default:
      return str
  }
}