summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2016-02-28 00:48:46 +0100
committerGiampaolo Rodola <g.rodola@gmail.com>2016-02-28 00:48:46 +0100
commit3f57ee88a64233855e492b25bae7f554ee595c46 (patch)
tree33e723ab5f27c7a7e096a5760b8d092e8d8b2ce3
parent8eea7d10e25b8cd3920a5f635791043f85b94184 (diff)
parent2449beb7e6c671e20d34bfebcaadaf34180b4b31 (diff)
downloadpsutil-3f57ee88a64233855e492b25bae7f554ee595c46.tar.gz
Merge pull request #778 from giampaolo/777-linux-files-pos-mode
777 linux files pos mode
-rw-r--r--HISTORY.rst5
-rw-r--r--IDEAS6
-rw-r--r--docs/index.rst21
-rw-r--r--psutil/_pslinux.py33
-rw-r--r--psutil/tests/test_linux.py41
-rw-r--r--psutil/tests/test_process.py36
6 files changed, 121 insertions, 21 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index 296dc85f..3c4dfa7a 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -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.
diff --git a/IDEAS b/IDEAS
index 26a9ce25..b8b3a17b 100644
--- a/IDEAS
+++ b/IDEAS
@@ -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
diff --git a/docs/index.rst b/docs/index.rst
index 286f8a6a..429e76b3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -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')
@@ -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.
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 882cfac8..324ff5cb 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -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:
@@ -237,6 +247,8 @@ sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
'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'))
@@ -1296,7 +1308,7 @@ class Process(object):
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):
@@ -1308,12 +1320,19 @@ class Process(object):
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
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
index 7bf753b2..a9545991 100644
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -37,6 +37,7 @@ from psutil.tests import pyrun
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
@@ -719,6 +720,11 @@ class TestMisc(unittest.TestCase):
@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
@@ -765,6 +771,41 @@ class TestProcess(unittest.TestCase):
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
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index f1cc1762..c556caf0 100644
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -876,13 +876,20 @@ class TestProcess(unittest.TestCase):
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
@@ -903,20 +910,20 @@ class TestProcess(unittest.TestCase):
@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)
@@ -1694,6 +1701,11 @@ class TestFetchAllProcesses(unittest.TestCase):
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