summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2019-02-26 18:57:23 +0100
committerGiampaolo Rodola <g.rodola@gmail.com>2019-02-26 18:57:23 +0100
commit682fae3600c830905c4c57341670c7c097ec6a33 (patch)
tree8d28fc114ba7eefbc5516f17dc2cfc27a37035f2
parent62e5b208938bb0ce1b533dced5cf566ded754452 (diff)
downloadpsutil-682fae3600c830905c4c57341670c7c097ec6a33.tar.gz
#fix 1438: do not return any parent() for PID 0 + update doc
-rw-r--r--CREDITS2
-rw-r--r--docs/index.rst20
-rw-r--r--psutil/__init__.py40
-rwxr-xr-xpsutil/tests/test_process.py21
4 files changed, 47 insertions, 36 deletions
diff --git a/CREDITS b/CREDITS
index ea21c24a..d2477d42 100644
--- a/CREDITS
+++ b/CREDITS
@@ -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)