diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2017-12-02 09:47:46 +0100 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2017-12-02 09:47:46 +0100 |
commit | f3029f9a0f23d9d6dafaed341e21994a1f343ddd (patch) | |
tree | 77421ddf6b10692ce8f636d4635d18ceb8209bff | |
parent | 8f8b4d79b1b2f0d6a94107f0038796efcc098278 (diff) | |
parent | f0094db79ad9e4f2246997cb8c2046b71c465a29 (diff) | |
download | psutil-f3029f9a0f23d9d6dafaed341e21994a1f343ddd.tar.gz |
merge form master
-rw-r--r-- | HISTORY.rst | 1 | ||||
-rw-r--r-- | docs/index.rst | 3 | ||||
-rw-r--r-- | psutil/__init__.py | 82 | ||||
-rw-r--r-- | psutil/_pslinux.py | 24 |
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. |