From 9d24ec29c3f865bfb4849beb57cb95255dfb4c2b Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 22 Apr 2017 23:25:56 +0200 Subject: [PATCH] fix #1015 / linux: have swap_memory() rely on /proc fs instead of sysinfo() syscall in order to be nice with Linux containers such as Docker and Heroku --- .ci/travis/install.sh | 2 +- .ci/travis/run.sh | 6 +++--- .travis.yml | 14 +------------- HISTORY.rst | 6 ++++++ docs/index.rst | 18 ++++++++++++++++-- psutil/_pslinux.py | 20 +++++++++++++++++--- psutil/tests/test_linux.py | 22 ++++++++++++++++++++-- 7 files changed, 64 insertions(+), 24 deletions(-) diff --git a/.ci/travis/install.sh b/.ci/travis/install.sh index 677dc4653..6563251c2 100755 --- a/.ci/travis/install.sh +++ b/.ci/travis/install.sh @@ -51,4 +51,4 @@ elif [[ $TRAVIS_PYTHON_VERSION == '3.3' ]] || [[ $PYVER == 'py33' ]]; then pip install -U ipaddress fi -pip install -U coverage coveralls flake8 pep8 setuptools +pip install --upgrade coverage coveralls flake8 pep8 setuptools diff --git a/.ci/travis/run.sh b/.ci/travis/run.sh index b3a6a4a09..aaef347a7 100755 --- a/.ci/travis/run.sh +++ b/.ci/travis/run.sh @@ -24,10 +24,10 @@ else python psutil/tests/runner.py fi -if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.5" ]; then - # run mem leaks test +# Run memory leak tests and linter only on Linux and latest major Python +# versions. +if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.6" ]; then python psutil/tests/test_memory_leaks.py - # run linter (on Linux only) if [[ "$(uname -s)" != 'Darwin' ]]; then python -m flake8 fi diff --git a/.travis.yml b/.travis.yml index 48c84a7b3..2d05de174 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,30 +10,18 @@ matrix: - python: 3.5 - python: 3.6 - "pypy" - # XXX - commented because OSX builds are deadly slow - # - language: generic - # os: osx - # env: PYVER=py26 - language: generic os: osx env: PYVER=py27 - # XXX - commented because OSX builds are deadly slow - # - language: generic - # os: osx - # env: PYVER=py33 - language: generic os: osx env: PYVER=py34 - # XXX - not supported yet - # - language: generic - # os: osx - # env: PYVER=py35 install: - ./.ci/travis/install.sh script: - ./.ci/travis/run.sh after_success: - # upload reports to coveralls.io + # upload test reports to coveralls.io - | if [ "$(uname -s)" != 'Darwin' ]; then coveralls diff --git a/HISTORY.rst b/HISTORY.rst index db5bb70ce..c08104584 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,12 @@ 5.2.3 ===== +**Enhancements** + +- 1015_: swap_memory() now relies on /proc/meminfo instead of sysinfo() syscall + so that it can be used in conjunction with PROCFS_PATH in order to retrieve + memory info about Linux containers such as Docker and Heroku. + **Bug fixes** - 1014_: Linux can mask legitimate ENOENT exceptions as NoSuchProcess. diff --git a/docs/index.rst b/docs/index.rst index 5c3096871..fabfb22b9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -302,6 +302,11 @@ Memory >>> psutil.swap_memory() sswap(total=2097147904L, used=886620160L, free=1210527744L, percent=42.3, sin=1050411008, sout=1906720768) + .. versionchanged:: 5.2.3 on Linux this function relies on /proc fs instead + of sysinfo() syscall so that it can be used in conjunction with + :const:`psutil.PROCFS_PATH` in order to retrieve memory info about + Linux containers such as Docker and Heroku. + Disks ----- @@ -1979,9 +1984,18 @@ Constants .. _const-procfs_path: .. data:: PROCFS_PATH - The path of the /proc filesystem on Linux and Solaris (defaults to "/proc"). + The path of the /proc filesystem on Linux and Solaris (defaults to + ``"/proc"``). You may want to re-set this constant right after importing psutil in case - your /proc filesystem is mounted elsewhere. + your /proc filesystem is mounted elsewhere or if you want to retrieve + information about Linux containers such as + `Docker `__, + `Heroku `__ or + `LXC `__ (see + `here `__ + for more info). + It must be noted that this trick works only for APIs which rely on /proc + filesystem (e.g. `memory`_ APIs and most :class:`Process` class methods). Availability: Linux, Solaris diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index fa4299edf..6b3a951c2 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -489,9 +489,23 @@ def virtual_memory(): def swap_memory(): """Return swap memory metrics.""" - _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() - total *= unit_multiplier - free *= unit_multiplier + mems = {} + with open_binary('%s/meminfo' % get_procfs_path()) as f: + for line in f: + fields = line.split() + mems[fields[0]] = int(fields[1]) * 1024 + # We prefer /proc/meminfo over sysinfo() syscall so that + # psutil.PROCFS_PATH can be used in order to allow retrieval + # for linux containers, see: + # https://github.com/giampaolo/psutil/issues/1015 + try: + total = mems['SwapTotal:'] + free = mems['SwapFree:'] + except KeyError: + _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() + total *= unit_multiplier + free *= unit_multiplier + used = total - free percent = usage_percent(used, total, _round=1) # get pgin/pgouts diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index eec33546a..3c4da84c0 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -421,8 +421,15 @@ def test_warnings_mocked(self): def test_no_vmstat_mocked(self): # see https://github.com/giampaolo/psutil/issues/722 - with mock.patch('psutil._pslinux.open', create=True, - side_effect=IOError) as m: + def open_mock(name, *args, **kwargs): + if name == "/proc/vmstat": + raise IOError(errno.ENOENT, 'no such file or directory') + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, create=True, side_effect=open_mock) as m: with warnings.catch_warnings(record=True) as ws: warnings.simplefilter("always") ret = psutil.swap_memory() @@ -437,6 +444,17 @@ def test_no_vmstat_mocked(self): self.assertEqual(ret.sin, 0) self.assertEqual(ret.sout, 0) + def test_against_sysinfo(self): + with mock.patch('psutil._pslinux.cext.linux_sysinfo') as m: + swap = psutil.swap_memory() + assert not m.called + import psutil._psutil_linux as cext + _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() + total *= unit_multiplier + free *= unit_multiplier + self.assertEqual(swap.total, total) + self.assertEqual(swap.free, free) + # ===================================================================== # --- system CPU