Skip to content

Commit

Permalink
Remove linux import-time dependency on /proc/stat
Browse files Browse the repository at this point in the history
The very excellent work that went in under giampaolo#558
gave us a psutil that can be used on systems with a moved /proc.

However, on some systems (e.g. test harnesses, containers) may not
have any files mounted at /proc at all. In these cases, psutil
fails at import time with an IOError trying to access /proc/stat.

This patch removes the import-time dependency on a live procfs
mounted at /proc, and validates the behavior of some of the
details we were hoping to read at import time (i.e. cpu % stats).
  • Loading branch information
sethp-jive committed Dec 3, 2015
1 parent a05dccb commit eb73ec6
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 7 deletions.
14 changes: 9 additions & 5 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,9 +1411,13 @@ def cpu_times(percpu=False):
else:
return _psplatform.per_cpu_times()


_last_cpu_times = cpu_times()
_last_per_cpu_times = cpu_times(percpu=True)
try:
_last_cpu_times = cpu_times()
_last_per_cpu_times = cpu_times(percpu=True)
except IOError:
from collections import namedtuple
_last_cpu_times = namedtuple('emptycpu', 'idle')(0)
_last_per_cpu_times = []


def cpu_percent(interval=None, percpu=False):
Expand Down Expand Up @@ -1521,8 +1525,8 @@ def cpu_times_percent(interval=None, percpu=False):
def calculate(t1, t2):
nums = []
all_delta = sum(t2) - sum(t1)
for field in t1._fields:
field_delta = getattr(t2, field) - getattr(t1, field)
for field in t2._fields:
field_delta = getattr(t2, field) - getattr(t1, field, 0)
try:
field_perc = (100 * field_delta) / all_delta
except ZeroDivisionError:
Expand Down
3 changes: 1 addition & 2 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ def set_scputimes_ntuple(procfs_path):
scputimes = namedtuple('scputimes', fields)
return scputimes


scputimes = set_scputimes_ntuple('/proc')
scputimes = None

svmem = namedtuple(
'svmem', ['total', 'available', 'percent', 'used', 'free',
Expand Down
64 changes: 64 additions & 0 deletions test/_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os
import pprint
import re
import shutil
import socket
import struct
import sys
Expand Down Expand Up @@ -478,6 +479,69 @@ def test_procfs_path(self):
def test_psutil_is_reloadable(self):
imp.reload(psutil)

def test_no_procfs_for_import(self):
my_procfs = tempfile.mkdtemp()

with open(os.path.join(my_procfs, 'stat'), 'w') as f:
f.write('cpu 0 0 0 0 0 0 0 0 0 0\n')
f.write('cpu0 0 0 0 0 0 0 0 0 0 0\n')
f.write('cpu1 0 0 0 0 0 0 0 0 0 0\n')

self.assertNotAlmostEqual(psutil.cpu_percent(), 0)
self.assertNotAlmostEqual(sum(psutil.cpu_times_percent()), 0)
try:
orig_open = open

def open_mock(name, *args):
if name.startswith('/proc'):
# simulate an ENOENT
raise IOError('rejecting access to /proc')
return orig_open(name, *args)
with mock.patch('__builtin__.open', side_effect=open_mock):
imp.reload(psutil)

self.assertRaises(IOError, psutil.cpu_times)
self.assertRaises(IOError, psutil.cpu_times, percpu=True)
self.assertRaises(IOError, psutil.cpu_percent)
self.assertRaises(IOError, psutil.cpu_percent, percpu=True)
self.assertRaises(IOError, psutil.cpu_times_percent)
self.assertRaises(
IOError, psutil.cpu_times_percent, percpu=True)

psutil.PROCFS_PATH = my_procfs

self.assertAlmostEqual(psutil.cpu_percent(), 0)
self.assertAlmostEqual(sum(psutil.cpu_times_percent()), 0)

# since we don't know the number of CPUs at import time,
# we awkwardly say there are none until the second call
per_cpu_percent = psutil.cpu_percent(percpu=True)
self.assertEqual(len(per_cpu_percent), 0)
self.assertAlmostEqual(sum(per_cpu_percent), 0)

# ditto awkward length
per_cpu_times_percent = psutil.cpu_times_percent(percpu=True)
self.assertEqual(len(per_cpu_times_percent), 0)
self.assertAlmostEqual(sum(map(sum, per_cpu_percent)), 0)

# much user, very busy
with open(os.path.join(my_procfs, 'stat'), 'w') as f:
f.write('cpu 1 0 0 0 0 0 0 0 0 0\n')
f.write('cpu0 1 0 0 0 0 0 0 0 0 0\n')
f.write('cpu1 1 0 0 0 0 0 0 0 0 0\n')

self.assertNotAlmostEqual(psutil.cpu_percent(), 0)
self.assertNotAlmostEqual(
sum(psutil.cpu_percent(percpu=True)), 0)
self.assertNotAlmostEqual(sum(psutil.cpu_times_percent()), 0)
self.assertNotAlmostEqual(
sum(map(sum, psutil.cpu_times_percent(percpu=True))), 0)
finally:
shutil.rmtree(my_procfs)
imp.reload(psutil)

assert psutil.PROCFS_PATH == '/proc'

# --- tests for specific kernel versions

@unittest.skipUnless(
Expand Down

0 comments on commit eb73ec6

Please sign in to comment.