summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2017-12-02 09:47:46 +0100
committerGiampaolo Rodola <g.rodola@gmail.com>2017-12-02 09:47:46 +0100
commitf3029f9a0f23d9d6dafaed341e21994a1f343ddd (patch)
tree77421ddf6b10692ce8f636d4635d18ceb8209bff
parent8f8b4d79b1b2f0d6a94107f0038796efcc098278 (diff)
parentf0094db79ad9e4f2246997cb8c2046b71c465a29 (diff)
downloadpsutil-f3029f9a0f23d9d6dafaed341e21994a1f343ddd.tar.gz
merge form master
-rw-r--r--HISTORY.rst1
-rw-r--r--docs/index.rst3
-rw-r--r--psutil/__init__.py82
-rw-r--r--psutil/_pslinux.py24
4 files changed, 68 insertions, 42 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index f8a7d301..8bc3f784 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -10,6 +10,7 @@
- 1173_: introduced PSUTIL_DEBUG environment variable which can be set in order
to print useful debug messages on stderr (useful in case of nasty errors).
- 1177_: added support for sensors_battery() on OSX. (patch by Arnon Yaari)
+- 1183_: Process.children() is 2x faster on UNIX and 2.4x faster on Linux.
**Bug fixes**
diff --git a/docs/index.rst b/docs/index.rst
index 0119b423..ea2384bb 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1716,7 +1716,8 @@ Process class
.. method:: children(recursive=False)
- Return the children of this process as a list of :Class:`Process` objects.
+ Return the children of this process as a list of :class:`Process`
+ instances.
If recursive is `True` return all the parent descendants.
Pseudo code example assuming *A == this process*:
::
diff --git a/psutil/__init__.py b/psutil/__init__.py
index c8da0a3d..a8447973 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -250,10 +250,31 @@ if (int(__version__.replace('.', '')) !=
# =====================================================================
-# --- Process class
+# --- Utils
# =====================================================================
+if hasattr(_psplatform, 'ppid_map'):
+ # Faster version (Windows and Linux).
+ _ppid_map = _psplatform.ppid_map
+else:
+ def _ppid_map():
+ """Return a {pid: ppid, ...} dict for all running processes in
+ one shot. Used to speed up Process.children().
+ """
+ ret = {}
+ for pid in pids():
+ try:
+ proc = _psplatform.Process(pid)
+ ppid = proc.ppid()
+ except (NoSuchProcess, AccessDenied):
+ # Note: AccessDenied is unlikely to happen.
+ pass
+ else:
+ ret[pid] = ppid
+ return ret
+
+
def _assert_pid_not_reused(fun):
"""Decorator which raises NoSuchProcess in case a process is no
longer running or its PID has been reused.
@@ -266,6 +287,11 @@ def _assert_pid_not_reused(fun):
return wrapper
+# =====================================================================
+# --- Process class
+# =====================================================================
+
+
class Process(object):
"""Represents an OS process with the given PID.
If PID is omitted current process PID (os.getpid()) is used.
@@ -848,55 +874,29 @@ class Process(object):
process Y won't be listed as the reference to process A
is lost.
"""
- if hasattr(_psplatform, 'ppid_map'):
- # Windows only: obtain a {pid:ppid, ...} dict for all running
- # processes in one shot (faster).
- ppid_map = _psplatform.ppid_map()
- else:
- ppid_map = None
-
+ ppid_map = _ppid_map()
ret = []
if not recursive:
- if ppid_map is None:
- # 'slow' version, common to all platforms except Windows
- for p in process_iter():
+ for pid, ppid in ppid_map.items():
+ if ppid == self.pid:
try:
- if p.ppid() == self.pid:
- # if child happens to be older than its parent
- # (self) it means child's PID has been reused
- if self.create_time() <= p.create_time():
- ret.append(p)
+ child = Process(pid)
+ # if child happens to be older than its parent
+ # (self) it means child's PID has been reused
+ if self.create_time() <= child.create_time():
+ ret.append(child)
except (NoSuchProcess, ZombieProcess):
pass
- else: # pragma: no cover
- # Windows only (faster)
- for pid, ppid in ppid_map.items():
- if ppid == self.pid:
- try:
- child = Process(pid)
- # if child happens to be older than its parent
- # (self) it means child's PID has been reused
- if self.create_time() <= child.create_time():
- ret.append(child)
- except (NoSuchProcess, ZombieProcess):
- pass
else:
# construct a dict where 'values' are all the processes
# having 'key' as their parent
table = collections.defaultdict(list)
- if ppid_map is None:
- for p in process_iter():
- try:
- table[p.ppid()].append(p)
- except (NoSuchProcess, ZombieProcess):
- pass
- else: # pragma: no cover
- for pid, ppid in ppid_map.items():
- try:
- p = Process(pid)
- table[ppid].append(p)
- except (NoSuchProcess, ZombieProcess):
- pass
+ for pid, ppid in ppid_map.items():
+ try:
+ p = Process(pid)
+ table[ppid].append(p)
+ except (NoSuchProcess, ZombieProcess):
+ pass
# At this point we have a mapping table where table[self.pid]
# are the current process' children.
# Below, we look for all descendants recursively, similarly
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index a5a3fd89..b57adb34 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -1356,6 +1356,30 @@ def pid_exists(pid):
return pid in pids()
+def ppid_map():
+ """Obtain a {pid: ppid, ...} dict for all running processes in
+ one shot. Used to speed up Process.children().
+ """
+ ret = {}
+ procfs_path = get_procfs_path()
+ for pid in pids():
+ try:
+ with open_binary("%s/%s/stat" % (procfs_path, pid)) as f:
+ data = f.read()
+ except EnvironmentError as err:
+ # Note: we should be able to access /stat for all processes
+ # so we won't bump into EPERM, which is good.
+ if err.errno not in (errno.ENOENT, errno.ESRCH,
+ errno.EPERM, errno.EACCES):
+ raise
+ else:
+ rpar = data.rfind(b')')
+ dset = data[rpar + 2:].split()
+ ppid = int(dset[1])
+ ret[pid] = ppid
+ return ret
+
+
def wrap_exceptions(fun):
"""Decorator which translates bare OSError and IOError exceptions
into NoSuchProcess and AccessDenied.