From 735954d924880981dfcc24d7abc2d454fadc6830 Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 15 Apr 2023 13:48:28 +0200 Subject: [PATCH 01/15] detect ps in bash --- pgtree/pgtree.py | 84 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index e65cd5c..089dc5c 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -2,9 +2,18 @@ # coding: utf-8 # pylint: disable=C0114,C0413,R0902,C0209 # determine available python executable +# determine ps -o options _='''' #[ "$1" = -W ] && shift && exec watch -x -c -- "$0" -C y "$@" -export PGT_PGREP=$(type -p pgrep) +PGT_PGREP=$(type -p pgrep) +ps -p $$ -o ucomm >/dev/null 2>&1 && PGT_COMM=ucomm +[ ! "$PGT_COMM" ] && ps -p $$ -o comm >/dev/null 2>&1 && PGT_COMM=comm +[ "$PGT_COMM" ] && { + ps -p $$ -o stime >/dev/null 2>&1 && PGT_STIME=stime + [ ! "$PGT_STIME" ] && ps -p $$ -o start >/dev/null 2>&1 && PGT_STIME=start + [ ! "$PGT_STIME" ] && PGT_STIME=time +} +export PGT_COMM PGT_STIME PGT_PGREP python=$(type -p python || type -p python3 || type -p python2) [ "$python" ] && exec $python "$0" "$@" echo "ERROR: cannot find python interpreter" >&2 @@ -46,10 +55,11 @@ import sys import os import getopt -import platform import re -import time - +try: + import time +except ImportError: + pass # pylint: disable=E0602 # pylint: disable=E1101 if sys.version_info < (3, 0): @@ -58,10 +68,10 @@ def runcmd(cmd): """run command""" - pipe = os.popen('"' + '" "'.join(cmd) + '"', 'r') + pipe = os.popen(cmd, 'r') std_out = pipe.read() - pipe.close() - return std_out.rstrip('\n') + res = pipe.close() + return res, std_out.rstrip('\n') def ask(prompt): """input text""" @@ -133,39 +143,58 @@ def __init__(self, use_uid=False, use_ascii=False, use_color=False, def get_fields(self, opt_fields=None, use_uid=False): """ Get ps fields from OS / optionnal fields """ - osname = platform.system() - if not opt_fields: - if osname in ['AIX', 'Darwin']: - opt_fields = ['start'] - else: - opt_fields = ['stime'] if use_uid: user = 'uid' else: user = 'user' - if osname == 'SunOS': - comm = 'comm' - else: - comm = 'ucomm' - return ['pid', 'ppid', user, comm] + opt_fields + if not opt_fields: + opt_fields = [os.environ.get('PGT_STIME', 'stime')] + + return ['pid', 'ppid', user, os.environ.get('PGT_COMM', 'ucomm')] + opt_fields + + def run_ps(self, widths): + """ ps command not supporting -o (mingw/msys2) / guess output """ + if os.environ.get('PGT_COMM'): + ps_cmd = 'ps -e ' + ' '.join( + ['-o '+ o +'='+ widths[i]*'-' for i,o in enumerate(self.ps_fields)] + ) + ' -o args 2>/dev/null' + _, ps_out = runcmd(ps_cmd) + return ps_out.splitlines() + + _, out = runcmd('ps -ef') # user pid ppid tty stime command + ps_out = [] + out = out.splitlines() + fields = {} + for i,field in enumerate(out[0].strip().lower().split()): + field = re.sub("command|cmd", "args", field) + field = re.sub("uid", "user", field) + fields[field] = i + fields["ucomm"] = len(fields) + for line in out: + ps_info = line.strip().split(None, len(fields)-2) + if "stime" in fields: + if ps_info[fields["stime"]] in ["Jan","Feb","Mar","Apr","May","Jun", + "Jui","Aug","Sep","Oct","Nov","Dec"]: + ps_info = line.strip().split(None, len(fields)) + ps_info[fields["stime"]] += ps_info.pop(fields["stime"]-1) + ps_info.append(os.path.basename(ps_info[fields["args"]])) + ps_out.append(' '.join( + [('%-'+ str(widths[i]) +'s') % ps_info[fields[opt]] + for i,opt in enumerate(self.ps_fields)] + [ps_info[fields["args"]]] + )) + return ps_out def get_psinfo(self, pid_zero): """parse unix ps command""" widths = [30, 30, 30, 130] + [50 for i in self.ps_fields[4:]] - ps_cmd = 'ps -e ' + ' '.join( - ['-o '+ o +'='+ widths[i]*'-' for i,o in enumerate(self.ps_fields)] - ) + ' -o args' - # print(ps_cmd) - ps_out = runcmd(ps_cmd.split(' ')).split('\n') + ps_out = self.run_ps(widths) pid_z = ["0", "0"] + self.ps_fields[2:] ps_out[0] = ' '.join( [('%-'+ str(widths[i]) +'s') % opt for i,opt in enumerate(pid_z)] + ['args'] ) ps_opts = ['pid', 'ppid', 'user', 'comm'] + self.ps_fields[4:] - # print(ps_out[0]) for line in ps_out: - # print(line) infos = {} col = 0 for i,field in enumerate(ps_opts): @@ -173,7 +202,6 @@ def get_psinfo(self, pid_zero): col = col + widths[i] + 1 infos['args'] = line[col:len(line)] infos['comm'] = os.path.basename(infos['comm']) - # print(infos) pid = infos['pid'] ppid = infos['ppid'] if pid == str(os.getpid()): @@ -185,6 +213,8 @@ def get_psinfo(self, pid_zero): self.children[ppid] = [] self.children[ppid].append(pid) self.ps_info[pid] = infos + if not self.ps_info.get('1'): + self.ps_info['1'] = self.ps_info['0'] if not pid_zero: del self.ps_info['0'] del self.children['0'] @@ -193,7 +223,7 @@ def pgrep(self, argv): """mini built-in pgrep if pgrep command not available [-f] [-x] [-i] [-u ] [pattern]""" if "PGT_PGREP" not in os.environ or os.environ["PGT_PGREP"]: - pgrep = runcmd(['pgrep'] + argv) + _, pgrep = runcmd('pgrep ' +' '.join(argv)) return pgrep.split("\n") try: From b2041c53bc82d0bc4d2586e13669f00eb48b5982 Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 15 Apr 2023 14:23:01 +0200 Subject: [PATCH 02/15] busybox ps detect --- pgtree/pgtree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index 089dc5c..cd179d2 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -13,6 +13,8 @@ [ ! "$PGT_STIME" ] && ps -p $$ -o start >/dev/null 2>&1 && PGT_STIME=start [ ! "$PGT_STIME" ] && PGT_STIME=time } +# busybox no -p option +[ ! "$PGT_COMM" ] && ! ps -p $$ >/dev/null 2>&1 && PGT_COMM=comm && PGT_STIME=time export PGT_COMM PGT_STIME PGT_PGREP python=$(type -p python || type -p python3 || type -p python2) [ "$python" ] && exec $python "$0" "$@" From 7036770cccc75e1a76088933b5b01b58ce78a123 Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 15 Apr 2023 22:50:38 +0200 Subject: [PATCH 03/15] fix + coverage update --- .travis.yml | 6 ++++-- pgtree/pgtree.py | 7 ++++--- tests/test_pgtree.py | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ad1edf..4981372 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,11 @@ script: - python pgtree/pgtree.py - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then exit 0; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then exit 0; fi - - pip install lint codecov incremental + - pip install lint coverage pytest pytest-cov incremental - pylint pgtree/pgtree.py ; echo done - python -m unittest discover -s tests/ - - coverage run tests/test_pgtree.py + - pytest --cov + - curl -Os https://uploader.codecov.io/latest/linux/codecov + - chmod +x codecov after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index cd179d2..ac0a0b5 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -178,9 +178,10 @@ def run_ps(self, widths): if "stime" in fields: if ps_info[fields["stime"]] in ["Jan","Feb","Mar","Apr","May","Jun", "Jui","Aug","Sep","Oct","Nov","Dec"]: - ps_info = line.strip().split(None, len(fields)) - ps_info[fields["stime"]] += ps_info.pop(fields["stime"]-1) - ps_info.append(os.path.basename(ps_info[fields["args"]])) + ps_info = line.strip().split(None, len(fields)-1) + ps_info[fields["stime"]] += ps_info.pop(fields["stime"]+1) + ps_info.append(os.path.basename(ps_info[fields["args"]].split()[0])) + print(self.ps_fields) ps_out.append(' '.join( [('%-'+ str(widths[i]) +'s') % ps_info[fields[opt]] for i,opt in enumerate(self.ps_fields)] + [ps_info[fields["args"]]] diff --git a/tests/test_pgtree.py b/tests/test_pgtree.py index e8dd56c..39bbe6f 100644 --- a/tests/test_pgtree.py +++ b/tests/test_pgtree.py @@ -6,6 +6,8 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import pgtree #from unittest.mock import MagicMock, Mock, patch +os.environ['PGT_COMM'] = 'ucomm' +os.environ['PGT_STIME'] = 'stime' class ProctreeTest(unittest.TestCase): """tests for pgtree""" @@ -22,7 +24,7 @@ def test_tree1(self, mock_runcmd, mock_kill): ps_out += f'{"30":>30} {"10":>30} {"joknarf":<30} {"top":<130} {"10:10":<50} /bin/top\n' ps_out += f'{"40":>30} {"1":>30} {"root":<30} {"bash":<130} {"11:01":<50} -bash' print(ps_out) - mock_runcmd.return_value = ps_out + mock_runcmd.return_value = 0, ps_out mock_kill.return_value = True ptree = pgtree.Proctree() From f47010cdd8ea402b1d5a7434fc05d2765fd42b0c Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 15 Apr 2023 23:29:52 +0200 Subject: [PATCH 04/15] coverage tests simple ps --- .gitignore | 2 ++ pgtree/pgtree.py | 4 ++-- tests/test_pgtree.py | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d75364e..b968cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ dist pgtree.egg-info __pycache__ *.pyc +.coverage +coverage.xml diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index ac0a0b5..16dab14 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -151,9 +151,9 @@ def get_fields(self, opt_fields=None, use_uid=False): user = 'user' if not opt_fields: - opt_fields = [os.environ.get('PGT_STIME', 'stime')] + opt_fields = [os.environ.get('PGT_STIME') or 'stime'] - return ['pid', 'ppid', user, os.environ.get('PGT_COMM', 'ucomm')] + opt_fields + return ['pid', 'ppid', user, os.environ.get('PGT_COMM') or 'ucomm'] + opt_fields def run_ps(self, widths): """ ps command not supporting -o (mingw/msys2) / guess output """ diff --git a/tests/test_pgtree.py b/tests/test_pgtree.py index 39bbe6f..78a52ac 100644 --- a/tests/test_pgtree.py +++ b/tests/test_pgtree.py @@ -167,6 +167,9 @@ def test_watch(self, mock_sleep): mock_sleep.return_value = True pgtree.main(['-W', 'bash']) + @patch.dict(os.environ, {"PGT_COMM": "", "PGT_STIME": ""}) + def test_simpleps(self): + pgtree.main([]) if __name__ == "__main__": unittest.main(failfast=True) From 07552fe17bf1c125f66add5be73d828bee39101d Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 15 Apr 2023 23:50:22 +0200 Subject: [PATCH 05/15] check ps exit code --- pgtree/pgtree.py | 11 ++++++----- tests/test_pgtree.py | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index 16dab14..ec65c55 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -150,7 +150,7 @@ def get_fields(self, opt_fields=None, use_uid=False): else: user = 'user' - if not opt_fields: + if not opt_fields or not os.environ.get('PGT_COMM'): opt_fields = [os.environ.get('PGT_STIME') or 'stime'] return ['pid', 'ppid', user, os.environ.get('PGT_COMM') or 'ucomm'] + opt_fields @@ -160,10 +160,12 @@ def run_ps(self, widths): if os.environ.get('PGT_COMM'): ps_cmd = 'ps -e ' + ' '.join( ['-o '+ o +'='+ widths[i]*'-' for i,o in enumerate(self.ps_fields)] - ) + ' -o args 2>/dev/null' - _, ps_out = runcmd(ps_cmd) + ) + ' -o args' + err, ps_out = runcmd(ps_cmd) + if err: + print(f'Error: executing ps -e -o {",".join(self.ps_fields)}') + sys.exit(1) return ps_out.splitlines() - _, out = runcmd('ps -ef') # user pid ppid tty stime command ps_out = [] out = out.splitlines() @@ -181,7 +183,6 @@ def run_ps(self, widths): ps_info = line.strip().split(None, len(fields)-1) ps_info[fields["stime"]] += ps_info.pop(fields["stime"]+1) ps_info.append(os.path.basename(ps_info[fields["args"]].split()[0])) - print(self.ps_fields) ps_out.append(' '.join( [('%-'+ str(widths[i]) +'s') % ps_info[fields[opt]] for i,opt in enumerate(self.ps_fields)] + [ps_info[fields["args"]]] diff --git a/tests/test_pgtree.py b/tests/test_pgtree.py index 78a52ac..d542038 100644 --- a/tests/test_pgtree.py +++ b/tests/test_pgtree.py @@ -171,5 +171,14 @@ def test_watch(self, mock_sleep): def test_simpleps(self): pgtree.main([]) + def test_psfail(self): + """test""" + print('psfail ========') + try: + pgtree.main(['-O abcd']) + except SystemExit: + pass + + if __name__ == "__main__": unittest.main(failfast=True) From d9d07bc782e156da355bae95b2d3cceb4abb5dff Mon Sep 17 00:00:00 2001 From: joknarf Date: Sun, 16 Apr 2023 00:14:10 +0200 Subject: [PATCH 06/15] no main test --- tests/test_pgtree.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_pgtree.py b/tests/test_pgtree.py index d542038..5d07418 100644 --- a/tests/test_pgtree.py +++ b/tests/test_pgtree.py @@ -179,6 +179,3 @@ def test_psfail(self): except SystemExit: pass - -if __name__ == "__main__": - unittest.main(failfast=True) From 9b06dc411a816bbb19b169670e32cf807577b851 Mon Sep 17 00:00:00 2001 From: joknarf Date: Sun, 16 Apr 2023 01:10:14 +0200 Subject: [PATCH 07/15] add PYTHONUTF8 / comment setdefaultencoding --- pgtree/pgtree.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index ec65c55..0fbfc79 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -5,6 +5,7 @@ # determine ps -o options _='''' #[ "$1" = -W ] && shift && exec watch -x -c -- "$0" -C y "$@" +export LANG=en_US.UTF-8 PYTHONUTF8=1 PYTHONIOENCODING=utf8 PGT_PGREP=$(type -p pgrep) ps -p $$ -o ucomm >/dev/null 2>&1 && PGT_COMM=ucomm [ ! "$PGT_COMM" ] && ps -p $$ -o comm >/dev/null 2>&1 && PGT_COMM=comm @@ -62,11 +63,13 @@ import time except ImportError: pass + +# To test with python2 # pylint: disable=E0602 # pylint: disable=E1101 -if sys.version_info < (3, 0): - reload(sys) - sys.setdefaultencoding('utf8') +#if sys.version_info < (3, 0): +# reload(sys) +# sys.setdefaultencoding('utf8') def runcmd(cmd): """run command""" @@ -163,7 +166,7 @@ def run_ps(self, widths): ) + ' -o args' err, ps_out = runcmd(ps_cmd) if err: - print(f'Error: executing ps -e -o {",".join(self.ps_fields)}') + print('Error: executing ps -e -o ' + ",".join(self.ps_fields)) sys.exit(1) return ps_out.splitlines() _, out = runcmd('ps -ef') # user pid ppid tty stime command From da4c78109119ed80dda2fc7412e554257f317d60 Mon Sep 17 00:00:00 2001 From: joknarf Date: Mon, 17 Apr 2023 21:17:01 +0200 Subject: [PATCH 08/15] AIX/MacOS start for stime --- pgtree/pgtree.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index 0fbfc79..ea29b99 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -57,6 +57,7 @@ import sys import os +import platform import getopt import re try: @@ -64,12 +65,9 @@ except ImportError: pass -# To test with python2 -# pylint: disable=E0602 -# pylint: disable=E1101 -#if sys.version_info < (3, 0): -# reload(sys) -# sys.setdefaultencoding('utf8') +# impossible detection using ps for AIX/MacOS +if platform.system() in ['AIX', 'Darwin']: + os.environ['PGT_STIME'] = 'start' def runcmd(cmd): """run command""" From af18d20c0d3bfaa139f6b2a517d71a9b894ff1f3 Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 29 Jul 2023 10:59:05 +0200 Subject: [PATCH 09/15] uses ps ax instead of ps -ef (bsd) --- README.md | 4 ++-- pgtree/pgtree.py | 23 +++++++++++++++++------ tests/test_pgtree.py | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 44179f9..f2752ab 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,12 @@ The code must be compatible with python 2.x + 3.x Should work on any Unix that can execute : ``` # /usr/bin/pgrep -# /usr/bin/ps -e -o pid,ppid,stime,user,ucomm,args +# /usr/bin/ps ax -o pid,ppid,stime,user,ucomm,args ``` if `pgrep` command not available (AIX), pgtree uses built-in pgrep (`-f -i -x -u ` supported). -_Tested on various versions of RedHat / CentOS / Ubuntu / Debian / Suse / MacOS / Solaris / AIX including old versions_ +_Tested on various versions of RedHat / CentOS / Ubuntu / Debian / Suse / FreeBSD / MacOS / Solaris / AIX including old versions_ _(uses -o comm on Solaris)_ diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index ea29b99..2febf92 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -29,7 +29,7 @@ hierarchy of matching processes (parents and children) should work on any Unix supporting commands : # pgrep -# ps -e -o pid,ppid,comm,args +# ps ax -o pid,ppid,comm,args (RedHat/CentOS/Fedora/Ubuntu/Suse/Solaris...) Compatible python 2 / 3 @@ -66,6 +66,7 @@ pass # impossible detection using ps for AIX/MacOS +# stime is not start time of process if platform.system() in ['AIX', 'Darwin']: os.environ['PGT_STIME'] = 'start' @@ -157,24 +158,33 @@ def get_fields(self, opt_fields=None, use_uid=False): return ['pid', 'ppid', user, os.environ.get('PGT_COMM') or 'ucomm'] + opt_fields def run_ps(self, widths): - """ ps command not supporting -o (mingw/msys2) / guess output """ + """ + run ps command detected setting columns widths + guess columns for ps command not supporting -o (mingw/msys2) + """ if os.environ.get('PGT_COMM'): - ps_cmd = 'ps -e ' + ' '.join( + ps_cmd = 'ps ax ' + ' '.join( ['-o '+ o +'='+ widths[i]*'-' for i,o in enumerate(self.ps_fields)] ) + ' -o args' err, ps_out = runcmd(ps_cmd) if err: - print('Error: executing ps -e -o ' + ",".join(self.ps_fields)) + print('Error: executing ps ax -o ' + ",".join(self.ps_fields)) sys.exit(1) return ps_out.splitlines() - _, out = runcmd('ps -ef') # user pid ppid tty stime command - ps_out = [] + _, out = runcmd('ps aux') # try to use header to guess columns out = out.splitlines() + if not 'PPID' in out[0]: + _, out = runcmd('ps -ef') + out = out.splitlines() + ps_out = [] fields = {} for i,field in enumerate(out[0].strip().lower().split()): field = re.sub("command|cmd", "args", field) field = re.sub("uid", "user", field) fields[field] = i + if not 'ppid' in fields: + print("Error: command 'ps aux' does not provides PPID") + sys.exit(1) fields["ucomm"] = len(fields) for line in out: ps_info = line.strip().split(None, len(fields)-2) @@ -267,6 +277,7 @@ def pgrep(self, argv): def get_parents(self): """get parents list of pids""" + last_ppid = None for pid in self.pids: if pid not in self.ps_info: continue diff --git a/tests/test_pgtree.py b/tests/test_pgtree.py index 5d07418..4c6fd52 100644 --- a/tests/test_pgtree.py +++ b/tests/test_pgtree.py @@ -142,7 +142,7 @@ def test_main6(self): def test_main7(self): """test""" print('main7 ========') - pgtree.main(['-O', '%cpu', 'bash']) + pgtree.main(['-C', 'y', '-O', '%cpu', 'init']) def test_ospgrep(self): """pgrep os""" From 569b73865e47a553a90717807e2d705953d43512 Mon Sep 17 00:00:00 2001 From: joknarf Date: Sat, 29 Jul 2023 19:11:47 +0200 Subject: [PATCH 10/15] SunOS ps ax -o not supported, use ps -e --- pgtree/pgtree.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index 2febf92..bf9a163 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -67,8 +67,13 @@ # impossible detection using ps for AIX/MacOS # stime is not start time of process -if platform.system() in ['AIX', 'Darwin']: +system = platform.system() +PS_OPTION = 'ax' +if system in ['AIX', 'Darwin']: os.environ['PGT_STIME'] = 'start' +elif system == 'SunOS': # ps ax -o not supported + PS_OPTION = '-e' + os.environ['PGT_COMM'] = 'fname' # comm header width not respected def runcmd(cmd): """run command""" @@ -163,12 +168,12 @@ def run_ps(self, widths): guess columns for ps command not supporting -o (mingw/msys2) """ if os.environ.get('PGT_COMM'): - ps_cmd = 'ps ax ' + ' '.join( + ps_cmd = 'ps ' + PS_OPTION + ' ' + ' '.join( ['-o '+ o +'='+ widths[i]*'-' for i,o in enumerate(self.ps_fields)] ) + ' -o args' err, ps_out = runcmd(ps_cmd) if err: - print('Error: executing ps ax -o ' + ",".join(self.ps_fields)) + print('Error: executing ps ' + PS_OPTION + ' -o ' + ",".join(self.ps_fields)) sys.exit(1) return ps_out.splitlines() _, out = runcmd('ps aux') # try to use header to guess columns From 29c4676b88701851f3e66809c8cc01d9d52570fb Mon Sep 17 00:00:00 2001 From: joknarf Date: Sun, 30 Jul 2023 18:47:57 +0200 Subject: [PATCH 11/15] update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2752ab..5d6c5f9 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Should work on any Unix that can execute : if `pgrep` command not available (AIX), pgtree uses built-in pgrep (`-f -i -x -u ` supported). -_Tested on various versions of RedHat / CentOS / Ubuntu / Debian / Suse / FreeBSD / MacOS / Solaris / AIX including old versions_ +_Tested on various versions of RedHat / CentOS / Ubuntu / Debian / Suse / FreeBSD / ArchLinux / MacOS / Solaris / AIX including old versions_ -_(uses -o comm on Solaris)_ +_(uses -o fname on Solaris)_ ## Installation FYI, the `pgtree/pgtree.py` is standalone and can be directly copied/used anywhere without any installation. From b2012eb059a39a530fb15894a8f6e6e9faf36131 Mon Sep 17 00:00:00 2001 From: joknarf Date: Thu, 4 Jul 2024 20:59:20 +0200 Subject: [PATCH 12/15] add -T option to display threads --- pgtree/pgtree.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index bf9a163..25997b1 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -138,7 +138,7 @@ class Proctree: # pylint: disable=R0913 def __init__(self, use_uid=False, use_ascii=False, use_color=False, - pid_zero=True, opt_fields=None): + pid_zero=True, opt_fields=None, threads=False): """constructor""" self.pids = [] self.ps_info = {} # ps command info stored @@ -147,20 +147,26 @@ def __init__(self, use_uid=False, use_ascii=False, use_color=False, self.pids_tree = {} self.top_parents = [] self.treedisp = Treedisplay(use_ascii, use_color) - self.ps_fields = self.get_fields(opt_fields, use_uid) + self.threads = threads + self.ps_fields = self.get_fields(opt_fields, use_uid, threads) self.get_psinfo(pid_zero) - def get_fields(self, opt_fields=None, use_uid=False): + def get_fields(self, opt_fields=None, use_uid=False, threads=False): """ Get ps fields from OS / optionnal fields """ + global PS_OPTION if use_uid: user = 'uid' else: user = 'user' - + if threads: + PS_OPTION += " -T" + pid = 'spid' + else: + pid = 'pid' if not opt_fields or not os.environ.get('PGT_COMM'): opt_fields = [os.environ.get('PGT_STIME') or 'stime'] - return ['pid', 'ppid', user, os.environ.get('PGT_COMM') or 'ucomm'] + opt_fields + return [pid, 'ppid', user, os.environ.get('PGT_COMM') or 'ucomm'] + opt_fields def run_ps(self, widths): """ @@ -412,7 +418,8 @@ def pgtree(options, psfields, pgrep_args): use_ascii='-a' in options, use_color=colored(options['-C']), pid_zero='-1' not in options, - opt_fields=psfields) + opt_fields=psfields, + threads='-T' in options) found = None if '-p' in options: @@ -454,6 +461,7 @@ def main(argv): -w : tty wrap text : y/yes or n/no (default y) -W : watch and follow process tree every 2s -a : use ascii characters + -T : display threads (ps -T) -O [,psfield,...] : display multiple instead of 'stime' in output must be valid with ps -o command @@ -473,7 +481,7 @@ def main(argv): argv = os.environ["PGTREE"].split(' ') + argv try: opts, args = getopt.getopt(argv, - "W1IRckKfxvinoyap:u:U:g:G:P:s:t:F:O:C:w:", + "W1IRckKfxvinoyaTp:u:U:g:G:P:s:t:F:O:C:w:", ["ns=", "nslist="]) except getopt.GetoptError: print(usage) From 30034b3c98d136bd4d6af1ed75250f709d6213fa Mon Sep 17 00:00:00 2001 From: joknarf Date: Thu, 4 Jul 2024 21:11:52 +0200 Subject: [PATCH 13/15] PS_OPTION in main --- pgtree/pgtree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pgtree/pgtree.py b/pgtree/pgtree.py index 25997b1..0952878 100755 --- a/pgtree/pgtree.py +++ b/pgtree/pgtree.py @@ -147,19 +147,16 @@ def __init__(self, use_uid=False, use_ascii=False, use_color=False, self.pids_tree = {} self.top_parents = [] self.treedisp = Treedisplay(use_ascii, use_color) - self.threads = threads self.ps_fields = self.get_fields(opt_fields, use_uid, threads) self.get_psinfo(pid_zero) def get_fields(self, opt_fields=None, use_uid=False, threads=False): """ Get ps fields from OS / optionnal fields """ - global PS_OPTION if use_uid: user = 'uid' else: user = 'user' if threads: - PS_OPTION += " -T" pid = 'spid' else: pid = 'pid' @@ -447,6 +444,7 @@ def watch_pgtree(options, psfields, pgrep_args, sig): def main(argv): """pgtree command line""" + global PS_OPTION usage = """ usage: pgtree.py [-W] [-RIya] [-C ] [-O ] [-c|-k|-K] [-1|-p ,...|] @@ -503,6 +501,8 @@ def main(argv): psfields = arg.split(',') elif opt == "-R": os.environ["PGT_PGREP"] = "" + elif opt == "-T": + PS_OPTION += " -T" elif opt in ("-f", "-x", "-v", "-i", "-n", "-o"): pgrep_args.append(opt) elif opt in ("-u", "-U", "-g", "-G", "-P", "-s", "-t", "-F", "--ns", "--nslist"): From 22c7430a6747914d61fa4d085fe657b3cf14bbf0 Mon Sep 17 00:00:00 2001 From: joknarf Date: Thu, 4 Jul 2024 21:57:52 +0200 Subject: [PATCH 14/15] test threads --- tests/test_pgtree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_pgtree.py b/tests/test_pgtree.py index 4c6fd52..3303946 100644 --- a/tests/test_pgtree.py +++ b/tests/test_pgtree.py @@ -179,3 +179,5 @@ def test_psfail(self): except SystemExit: pass + def test_threads(self): + pgtree.main(["-T"]) From 2bce77d7c89040b47f7e79611cef632b22a36198 Mon Sep 17 00:00:00 2001 From: joknarf Date: Thu, 4 Jul 2024 22:21:07 +0200 Subject: [PATCH 15/15] update README --- README.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0145675..e1d01c0 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,9 @@ Should work on any Unix that can execute : if `pgrep` command not available (AIX), pgtree uses built-in pgrep (`-f -i -x -u ` supported). -_Tested on various versions of RedHat / CentOS / Ubuntu / Debian / Suse / FreeBSD / ArchLinux / MacOS / Solaris / AIX including old versions_ +`-T` option to display threads only works if `ps ax -T -o spid,ppid` available on system (ubuntu/redhat...) + +_pgtree Tested on various versions of RedHat / CentOS / Ubuntu / Debian / Suse / FreeBSD / ArchLinux / MacOS / Solaris / AIX including old versions_ _(uses -o fname on Solaris)_ @@ -34,14 +36,7 @@ installation using pip: ``` # pip install pgtree ``` -installation using setup.py, root install in `/usr/local/bin`: -``` -# ./setup.py install -``` -installation using setup.py, user install in `~/.local/bin`: -``` -# ./setup.py install --prefix=~/.local -``` + ## Usage ``` # pgtree -h @@ -58,6 +53,7 @@ installation using setup.py, user install in `~/.local/bin`: -w : tty wrap text : y/yes or n/no (default y) -W : watch and follow process tree every 2s -a : use ascii characters + -T : display threads (ps -T) -O [,psfield,...] : display multiple instead of 'stime' in output must be valid with ps -o command