From 09e2d2929b97c7e8de88781e5bd185c9b9a51a46 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 1 May 2016 03:53:53 +0200 Subject: [PATCH] [Linux] Process.name() is 25% faster (on python 3) --- HISTORY.rst | 1 + psutil/_pslinux.py | 30 ++++++++++++++++++++++++++---- psutil/tests/test_linux.py | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 16e026aa1..5f8003132 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,6 +11,7 @@ Bug tracker at https://github.com/giampaolo/psutil/issues - XXX: [Linux] speedup /proc parsing: - Process.ppid() is 20% faster - Process.status() is 28% faster + - Process.name() is 25% faster **Bug fixes** diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 7a5e0f189..9fa80945f 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -155,6 +155,13 @@ def open_text(fname, **kwargs): return open(fname, "rt", **kwargs) +def decode(s): + if PY3: + return s.decode(encoding=FS_ENCODING, errors=ENCODING_ERRORS_HANDLER) + else: + return s + + def get_procfs_path(): return sys.modules['psutil'].PROCFS_PATH @@ -943,12 +950,27 @@ def __init__(self, pid): self._ppid = None self._procfs_path = get_procfs_path() + def _parse_stat_file(self): + """Parse /proc/{pid}/stat file. Returns a list of fields where + process name is in position 0. + """ + with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f: + data = f.read() + # Process name is between parentheses. It can contain spaces and + # other parentheses. This is taken into account by looking for + # the first occurrence of "(" and the last occurence of ")". + rpar = data.rfind(b')') + name = data[data.find(b'(') + 1:rpar] + fields_after_name = data[rpar + 2:].split() + return [name] + fields_after_name + @wrap_exceptions def name(self): - with open_text("%s/%s/stat" % (self._procfs_path, self.pid)) as f: - data = f.read() + name = self._parse_stat_file()[0] + if PY3: + name = decode(name) # XXX - gets changed later and probably needs refactoring - return data[data.find('(') + 1:data.rfind(')')] + return name def exe(self): try: @@ -1024,7 +1046,7 @@ def io_counters(self): @wrap_exceptions def cpu_times(self): with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f: - st = f.read().strip() + st = f.read() # ignore the first two values ("pid (exe)") st = st[st.find(b')') + 2:] values = st.split(b' ') diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 613bc1f3e..f9b36ccd4 100644 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -779,6 +779,25 @@ def setUp(self): tearDown = setUp + def test_compare_stat_and_status_files(self): + # /proc/pid/stat and /proc/pid/status have many values in common. + # Whenever possible, psutil uses /proc/pid/stat (it's faster). + # For all those cases we check that the value found in + # /proc/pid/stat (by psutil) matches the one found in + # /proc/pid/status. + for p in psutil.process_iter(): + try: + f = psutil._psplatform.open_text('/proc/%s/status' % p.pid) + except IOError: + pass + else: + with f: + for line in f: + if line.startswith('Name:'): + name = line.split()[1] + # Name is truncated to 15 chars + self.assertEqual(p.name()[:15], name[:15]) + def test_memory_maps(self): src = textwrap.dedent(""" import time