Skip to content

Commit

Permalink
[Linux] Process.name() is 25% faster (on python 3)
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed May 1, 2016
1 parent b14593b commit 09e2d29
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 4 deletions.
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down
30 changes: 26 additions & 4 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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' ')
Expand Down
19 changes: 19 additions & 0 deletions psutil/tests/test_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 09e2d29

Please sign in to comment.