diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2019-02-26 18:57:23 +0100 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2019-02-26 18:57:23 +0100 |
commit | 682fae3600c830905c4c57341670c7c097ec6a33 (patch) | |
tree | 8d28fc114ba7eefbc5516f17dc2cfc27a37035f2 | |
parent | 62e5b208938bb0ce1b533dced5cf566ded754452 (diff) | |
download | psutil-682fae3600c830905c4c57341670c7c097ec6a33.tar.gz |
#fix 1438: do not return any parent() for PID 0 + update doc
-rw-r--r-- | CREDITS | 2 | ||||
-rw-r--r-- | docs/index.rst | 20 | ||||
-rw-r--r-- | psutil/__init__.py | 40 | ||||
-rwxr-xr-x | psutil/tests/test_process.py | 21 |
4 files changed, 47 insertions, 36 deletions
@@ -588,5 +588,5 @@ I: 1360 N: Ghislain Le Meur W: https://github.com/gigi206 -D: Process.parents() method +D: idea for Process.parents() I: 1379 diff --git a/docs/index.rst b/docs/index.rst index 012f18ae..78a88f97 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -982,19 +982,18 @@ Process class On Linux *pid* can also refer to a thread ID (the *id* field returned by :meth:`threads` method). When accessing methods of this class always be prepared to catch - :class:`NoSuchProcess`, :class:`ZombieProcess` and :class:`AccessDenied` - exceptions. + :class:`NoSuchProcess` and :class:`AccessDenied` exceptions. `hash() <https://docs.python.org/3/library/functions.html#hash>`__ builtin can be used against instances of this class in order to identify a process univocally over time (the hash is determined by mixing process PID - and creation time). As such it can also be used with + + creation time). As such it can also be used with `set()s <https://docs.python.org/3/library/stdtypes.html#types-set>`__. .. note:: In order to efficiently fetch more than one information about the process - at the same time, make sure to use either :meth:`as_dict` or - :meth:`oneshot` context manager. + at the same time, make sure to use either :meth:`oneshot` context manager + or :meth:`as_dict` utility method. .. note:: @@ -1009,6 +1008,7 @@ Process class :meth:`rlimit` (set), :meth:`children`, :meth:`parent`, + :meth:`parents`, :meth:`suspend` :meth:`resume`, :meth:`send_signal`, @@ -1108,7 +1108,7 @@ Process class call. Not on POSIX because `ppid may change <https://github.com/giampaolo/psutil/issues/321>`__ if process becomes a zombie. - See also :meth:`parent` method. + See also :meth:`parent` and :meth:`parents` methods. .. method:: name() @@ -1194,20 +1194,18 @@ Process class .. method:: parent() Utility method which returns the parent process as a :class:`Process` - object preemptively checking whether PID has been reused. If no parent + object, preemptively checking whether PID has been reused. If no parent PID is known return ``None``. - See also :meth:`ppid` method. + See also :meth:`ppid` and :meth:`parents` methods. .. method:: parents() Utility method which return the parents of this process as a list of :class:`Process` instances. If no parents are known return an empty list. + See also :meth:`ppid` and :meth:`parent` methods. .. versionadded:: 5.6.0 - .. warning:: - this API is experimental and may be removed if deemed necessary. - .. method:: status() The current process status as a string. The returned string is one of the diff --git a/psutil/__init__.py b/psutil/__init__.py index 513b2a7a..447e507b 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -45,7 +45,6 @@ from ._common import memoize_when_activated from ._common import wrap_numbers as _wrap_numbers from ._compat import long from ._compat import PY3 as _PY3 -from ._compat import lru_cache from ._common import STATUS_DEAD from ._common import STATUS_DISK_SLEEP @@ -214,15 +213,19 @@ __all__ = [ # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors "users", "boot_time", # others ] + + __all__.extend(_psplatform.__extra__all__) __author__ = "Giampaolo Rodola'" __version__ = "5.6.0" version_info = tuple([int(num) for num in __version__.split('.')]) + +_timer = getattr(time, 'monotonic', time.time) AF_LINK = _psplatform.AF_LINK POWER_TIME_UNLIMITED = _common.POWER_TIME_UNLIMITED POWER_TIME_UNKNOWN = _common.POWER_TIME_UNKNOWN _TOTAL_PHYMEM = None -_timer = getattr(time, 'monotonic', time.time) +_LOWEST_PID = None # Sanity check in case the user messed up with psutil installation # or did something weird with sys.path. In this case we might end @@ -397,11 +400,6 @@ def _pprint_secs(secs): return datetime.datetime.fromtimestamp(secs).strftime(fmt) -@lru_cache() -def _lowest_pid(): - return pids()[0] - - # ===================================================================== # --- Process class # ===================================================================== @@ -651,6 +649,9 @@ class Process(object): checking whether PID has been reused. If no parent is known return None. """ + lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0] + if self.pid == lowest_pid: + return None ppid = self.ppid() if ppid is not None: ctime = self.create_time() @@ -666,23 +667,11 @@ class Process(object): """Return the parents of this process as a list of Process instances. If no parents are known return an empty list. """ - lowest = _lowest_pid() # 1 if LINUX else 0 parents = [] proc = self.parent() - while True: - if proc is None: - break - elif proc.pid == lowest: - # Needed because on certain systems such as macOS - # Process(0).ppid() returns 0. - parents.append(proc) - break - else: - par = proc.parent() - if par is None: - break - parents.append(proc) - proc = par + while proc is not None: + parents.append(proc) + proc = proc.parent() return parents def is_running(self): @@ -1499,7 +1488,10 @@ _as_dict_attrnames = set( def pids(): """Return a list of current running PIDs.""" - return sorted(_psplatform.pids()) + global _LOWEST_PID + ret = sorted(_psplatform.pids()) + _LOWEST_PID = ret[0] + return ret def pid_exists(pid): @@ -2493,7 +2485,7 @@ def test(): # pragma: no cover p.info['name'].strip() or '?')) -del memoize, memoize_when_activated, division, deprecated_method, lru_cache +del memoize, memoize_when_activated, division, deprecated_method if sys.version_info[0] < 3: del num, x diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index bfbe9bc0..f9a91e36 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -1046,6 +1046,9 @@ class TestProcess(unittest.TestCase): p = psutil.Process(sproc.pid) self.assertEqual(p.parent().pid, this_parent) + lowest_pid = psutil.pids()[0] + self.assertIsNone(psutil.Process(lowest_pid).parent()) + def test_parent_multi(self): p1, p2 = create_proc_children_pair() self.assertEqual(p2.parent(), p1) @@ -1062,9 +1065,14 @@ class TestProcess(unittest.TestCase): def test_parents(self): assert psutil.Process().parents() p1, p2 = create_proc_children_pair() + self.assertEqual(p1.parents()[0], psutil.Process()) self.assertEqual(p2.parents()[0], p1) self.assertEqual(p2.parents()[1], psutil.Process()) + lowest_pid = psutil.pids()[0] + self.assertEqual(p1.parents()[-1].pid, lowest_pid) + self.assertEqual(p2.parents()[-1].pid, lowest_pid) + def test_children(self): reap_children(recursive=True) p = psutil.Process() @@ -1112,6 +1120,19 @@ class TestProcess(unittest.TestCase): else: self.assertEqual(len(c), len(set(c))) + def test_parents_and_children(self): + p1, p2 = create_proc_children_pair() + me = psutil.Process() + # forward + children = me.children(recursive=True) + self.assertEqual(len(children), 2) + self.assertEqual(children[0], p1) + self.assertEqual(children[1], p2) + # backward + parents = p2.parents() + self.assertEqual(parents[0], p1) + self.assertEqual(parents[1], me) + def test_suspend_resume(self): sproc = get_test_subprocess() p = psutil.Process(sproc.pid) |