Skip to content

Commit

Permalink
Merge pull request #778 from giampaolo/777-linux-files-pos-mode
Browse files Browse the repository at this point in the history
777 linux files pos mode
  • Loading branch information
giampaolo committed Feb 27, 2016
2 parents 8eea7d1 + 2449beb commit 3f57ee8
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 21 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Bug tracker at https://github.com/giampaolo/psutil/issues
4.1.0 - XXXX-XX-XX
==================

**Enhancements**

- #777: [Linux] Process.open_files() on Linux return 3 new fields: position,
mode and flags.

**Bug fixes**

- #776: [Linux] Process.cpu_affinity() may erroneously raise NoSuchProcess.
Expand Down
6 changes: 6 additions & 0 deletions IDEAS
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,9 @@ FEATURES
- #550: number of threads per core.

- Have psutil.Process().cpu_affinity([]) be an alias for "all CPUs"?

SIMILAR PROJECTS
================

- https://github.com/hyperic/sigar (Java)
- zabbix source code: https://zabbix.org/wiki/Get_Zabbix
21 changes: 19 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1202,8 +1202,22 @@ Process class
.. method:: open_files()

Return regular files opened by process as a list of namedtuples including
the absolute file name and the file descriptor number (on Windows this is
always ``-1``). Example:
the following fields:

- **path**: the absolute file name.
- **fd**: the file descriptor number; on Windows this is always ``-1``.
- **position** (Linux): the file (offset) position.
- **mode** (Linux): a string indicating how the file was opened, similarly
`open <https://docs.python.org/3/library/functions.html#open>`__'s
``mode`` argument. Possible values are ``'r'``, ``'w'``, ``'a'``,
``'r+'`` and ``'a+'``. There's no distinction between files opened in
bynary or text mode (``"b"`` or ``"t"``).
- **flags** (Linux): the flags which were passed to the underlying
`os.open <https://docs.python.org/2/library/os.html#os.open>`__ C call
when the file was opened (e.g.
`os.O_RDONLY <https://docs.python.org/3/library/os.html#os.O_RDONLY>`__,
`os.O_TRUNC <https://docs.python.org/3/library/os.html#os.O_TRUNC>`__,
etc).

>>> import psutil
>>> f = open('file.ext', 'w')
Expand All @@ -1228,6 +1242,9 @@ Process class

.. versionchanged:: 3.1.0 no longer hangs on Windows.

.. versionchanged:: 4.1.0 new *position*, *mode* and *flags* fields on
Linux.

.. method:: connections(kind="inet")

Return socket connections opened by process as a list of namedtuples.
Expand Down
33 changes: 26 additions & 7 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ def readlink(path):
return path


def file_flags_to_mode(flags):
md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
if flags & os.O_APPEND:
m = m.replace('w', 'a', 1)
m = m.replace('w+', 'r+')
# possible values: r, w, a, r+, a+
return m


def get_sector_size():
try:
with open(b"/sys/block/sda/queue/hw_sector_size") as f:
Expand Down Expand Up @@ -237,6 +247,8 @@ def set_scputimes_ntuple(procfs_path):
'read_merged_count', 'write_merged_count',
'busy_time'])

popenfile = namedtuple('popenfile',
['path', 'fd', 'position', 'mode', 'flags'])
pmem = namedtuple('pmem', 'rss vms shared text lib data dirty')
pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap'))

Expand Down Expand Up @@ -1296,7 +1308,7 @@ def open_files(self):
for fd in files:
file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd)
try:
file = readlink(file)
path = readlink(file)
except OSError as err:
# ENOENT == file which is gone in the meantime
if err.errno in (errno.ENOENT, errno.ESRCH):
Expand All @@ -1308,12 +1320,19 @@ def open_files(self):
else:
raise
else:
# If file is not an absolute path there's no way
# to tell whether it's a regular file or not,
# so we skip it. A regular file is always supposed
# to be absolutized though.
if file.startswith('/') and isfile_strict(file):
ntuple = _common.popenfile(file, int(fd))
# If path is not an absolute there's no way to tell
# whether it's a regular file or not, so we skip it.
# A regular file is always supposed to be have an
# absolute path though.
if path.startswith('/') and isfile_strict(path):
# Get file position and flags.
file = "%s/%s/fdinfo/%s" % (
self._procfs_path, self.pid, fd)
with open_binary(file) as f:
pos = int(f.readline().split()[1])
flags = int(f.readline().split()[1], 8)
mode = file_flags_to_mode(flags)
ntuple = popenfile(path, int(fd), int(pos), mode, flags)
retlist.append(ntuple)
if hit_enoent:
# raise NSP if the process disappeared on us
Expand Down
41 changes: 41 additions & 0 deletions psutil/tests/test_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from psutil.tests import reap_children
from psutil.tests import retry_before_failing
from psutil.tests import run_test_module_by_name
from psutil.tests import safe_remove
from psutil.tests import sh
from psutil.tests import skip_on_not_implemented
from psutil.tests import TESTFN
Expand Down Expand Up @@ -719,6 +720,11 @@ def open_mock(name, *args, **kwargs):
@unittest.skipUnless(LINUX, "not a Linux system")
class TestProcess(unittest.TestCase):

def setUp(self):
safe_remove(TESTFN)

tearDown = setUp

def test_memory_maps(self):
src = textwrap.dedent("""
import time
Expand Down Expand Up @@ -765,6 +771,41 @@ def test_memory_full_info(self):
self.assertEqual(
mem.swap, sum([x.swap for x in maps]))

def test_open_files_mode(self):
def get_test_file():
p = psutil.Process()
giveup_at = time.time() + 2
while True:
for file in p.open_files():
if file.path == os.path.abspath(TESTFN):
return file
elif time.time() > giveup_at:
break
raise RuntimeError("timeout looking for test file")

#
with open(TESTFN, "w"):
self.assertEqual(get_test_file().mode, "w")
with open(TESTFN, "r"):
self.assertEqual(get_test_file().mode, "r")
with open(TESTFN, "a"):
self.assertEqual(get_test_file().mode, "a")
#
with open(TESTFN, "r+"):
self.assertEqual(get_test_file().mode, "r+")
with open(TESTFN, "w+"):
self.assertEqual(get_test_file().mode, "r+")
with open(TESTFN, "a+"):
self.assertEqual(get_test_file().mode, "a+")
# note: "x" bit is not supported
if PY3:
safe_remove(TESTFN)
with open(TESTFN, "x"):
self.assertEqual(get_test_file().mode, "w")
safe_remove(TESTFN)
with open(TESTFN, "x+"):
self.assertEqual(get_test_file().mode, "r+")

def test_open_files_file_gone(self):
# simulates a file which gets deleted during open_files()
# execution
Expand Down
36 changes: 24 additions & 12 deletions psutil/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,13 +876,20 @@ def test_open_files(self):
p = psutil.Process()
files = p.open_files()
self.assertFalse(TESTFN in files)
with open(TESTFN, 'w'):
with open(TESTFN, 'wb') as f:
f.write(b'x' * 1024)
f.flush()
# give the kernel some time to see the new file
call_until(p.open_files, "len(ret) != %i" % len(files))
filenames = [x.path for x in p.open_files()]
self.assertIn(TESTFN, filenames)
for file in filenames:
assert os.path.isfile(file), file
files = call_until(p.open_files, "len(ret) != %i" % len(files))
for file in files:
if file.path == TESTFN:
if LINUX:
self.assertEqual(file.position, 1024)
break
else:
self.fail("no file found; files=%s" % repr(files))
for file in files:
assert os.path.isfile(file.path), file

# another process
cmdline = "import time; f = open(r'%s', 'r'); time.sleep(60);" % TESTFN
Expand All @@ -903,20 +910,20 @@ def test_open_files(self):
@unittest.skipIf(BSD, "broken on BSD, see #595")
@unittest.skipIf(APPVEYOR,
"can't find any process file on Appveyor")
def test_open_files2(self):
def test_open_files_2(self):
# test fd and path fields
with open(TESTFN, 'w') as fileobj:
p = psutil.Process()
for path, fd in p.open_files():
if path == fileobj.name or fd == fileobj.fileno():
for file in p.open_files():
if file.path == fileobj.name or file.fd == fileobj.fileno():
break
else:
self.fail("no file found; files=%s" % repr(p.open_files()))
self.assertEqual(path, fileobj.name)
self.assertEqual(file.path, fileobj.name)
if WINDOWS:
self.assertEqual(fd, -1)
self.assertEqual(file.fd, -1)
else:
self.assertEqual(fd, fileobj.fileno())
self.assertEqual(file.fd, fileobj.fileno())
# test positions
ntuple = p.open_files()[0]
self.assertEqual(ntuple[0], ntuple.path)
Expand Down Expand Up @@ -1694,6 +1701,11 @@ def open_files(self, ret, proc):
assert f.fd == -1, f
else:
self.assertIsInstance(f.fd, int)
if LINUX:
self.assertIsInstance(f.position, int)
self.assertGreaterEqual(f.position, 0)
self.assertIn(f.mode, ('r', 'w', 'a', 'r+', 'a+'))
self.assertGreater(f.flags, 0)
if BSD and not f.path:
# XXX see: https://github.com/giampaolo/psutil/issues/595
continue
Expand Down

0 comments on commit 3f57ee8

Please sign in to comment.