#!/usr/bin/python3 # pylint: disable=bad-continuation # pylint and pycodestyle appear to have different ideas of what identation should be. """ Assemble a *ix process hierarchy from ps and output with indentation. Today, most folks will just use Linux' ps axf. Also, the BSD's don't appear to support something like ps -o. """ import os import pwd import typing import collections # this time it's O(n) :) class Psentry: """Class to hold one ps entry.""" def __init__(self, euid: str, pid: str, ppid: str, command: str) -> None: # pylint: disable=too-many-arguments """Initialize.""" self.pid = int(pid) self.ppid = int(ppid) try: pwent = pwd.getpwuid(int(euid)) except KeyError: self.user = str(euid) self.user = pwent[0] self.command = command self.children = [] # type: typing.List[Psentry] def __str__(self) -> str: """Convert to string.""" return '%s %s %s %s' % (self.pid, self.ppid, self.user, self.command) __repr__ = __str__ def fill_out_tree(self, ps_entries: 'typing.Dict[int, Psentry]', pid_to_ppid: typing.Dict[int, int], ppid_to_pids: typing.Dict[int, typing.List[int]] ) -> None: """Build the tree.""" for child_pid in ppid_to_pids[self.pid]: child_psentry = ps_entries[child_pid] self.children.append(child_psentry) child_psentry.fill_out_tree(ps_entries, pid_to_ppid, ppid_to_pids) def hierarchy_as_string(self, depth: int = 0) -> str: """Format the tree as a string for output.""" result = '%s%s\n' % (' ' * depth * 2, str(self)) for child in self.children: result += child.hierarchy_as_string(depth+1) return result def get_least_pid_psentry(psentries: typing.List[Psentry]) -> Psentry: """Find the lowest pid in psentries.""" min_tuple = min((psentry.pid, psentry) for psentry in psentries) return min_tuple[1] def main() -> None: """Assemble the tree and print it.""" pipe = os.popen('ps -eo euid,pid,ppid,command', 'r') psentries_list = [] for line in pipe: fields = line.split() if fields[0] == 'EUID': continue euid = fields[0] pid = fields[1] ppid = fields[2] # This truncates commands at 100 characters - it's much more readable that way. command = ' '.join(fields[3:])[:100] psentry = Psentry(euid, pid, ppid, command) psentries_list.append(psentry) pipe.close() least_entry = get_least_pid_psentry(psentries_list) pid_to_ppid = {} ppid_to_pids = collections.defaultdict(list) # type: typing.DefaultDict[int, typing.List[int]] ps_entries = {} for psentry in psentries_list: pid_to_ppid[psentry.pid] = psentry.ppid ppid_to_pids[psentry.ppid].append(psentry.pid) ps_entries[psentry.pid] = psentry psentries_tree = least_entry psentries_tree.fill_out_tree(ps_entries, pid_to_ppid, ppid_to_pids) print(psentries_tree.hierarchy_as_string()) main()