Skip to content

Commit

Permalink
#1183: speedup Process.children() by 2.2x
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed Nov 30, 2017
1 parent 8f8b4d7 commit d238950
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 41 deletions.
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 around 2.2x faster on UNIX.

**Bug fixes**

Expand Down
81 changes: 40 additions & 41 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,30 @@


# =====================================================================
# --- Process class
# --- Utils
# =====================================================================


if hasattr(_psplatform, 'ppid_map'):
# Windows only (C).
_ppid_map = _psplatform.ppid_map()
else:
def _ppid_map():
"""Obtain 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):
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.
Expand All @@ -266,6 +286,11 @@ def wrapper(self, *args, **kwargs):
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.
Expand Down Expand Up @@ -848,55 +873,29 @@ def children(self, recursive=False):
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
Expand Down

0 comments on commit d238950

Please sign in to comment.